Merge "Fix a NPE in ViewRootImpl"
diff --git a/api/current.txt b/api/current.txt
index 9d46863..38a721f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -480,6 +480,10 @@
     field public static final int dashGap = 16843175; // 0x10101a7
     field public static final int dashWidth = 16843174; // 0x10101a6
     field public static final int data = 16842798; // 0x101002e
+    field public static final int dataRetentionTime = 16844189; // 0x101059d
+    field public static final int dataSentOffDevice = 16844186; // 0x101059a
+    field public static final int dataSharedWithThirdParty = 16844187; // 0x101059b
+    field public static final int dataUsedForMonetization = 16844188; // 0x101059c
     field public static final int datePickerDialogTheme = 16843948; // 0x10104ac
     field public static final int datePickerMode = 16843955; // 0x10104b3
     field public static final int datePickerStyle = 16843612; // 0x101035c
@@ -1312,7 +1316,6 @@
     field public static final int summaryColumn = 16843426; // 0x10102a2
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
-    field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
     field public static final int supportsLocalInteraction = 16844047; // 0x101050f
@@ -1496,6 +1499,7 @@
     field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
     field public static final int unselectedAlpha = 16843278; // 0x101020e
     field public static final int updatePeriodMillis = 16843344; // 0x1010250
+    field public static final int usageInfoRequired = 16844185; // 0x1010599
     field public static final int use32bitAbi = 16844053; // 0x1010515
     field public static final int useAppZygote = 16844184; // 0x1010598
     field public static final int useDefaultMargins = 16843641; // 0x1010379
@@ -6354,7 +6358,6 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
     method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager);
-    method public boolean supportsAmbientMode();
     method public boolean supportsMultipleDisplays();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR;
@@ -11173,8 +11176,8 @@
     field public android.content.pm.ProviderInfo[] providers;
     field public android.content.pm.ActivityInfo[] receivers;
     field public android.content.pm.FeatureInfo[] reqFeatures;
-    field public java.lang.String[] requestedPermissions;
-    field public int[] requestedPermissionsFlags;
+    field public deprecated java.lang.String[] requestedPermissions;
+    field public deprecated int[] requestedPermissionsFlags;
     field public android.content.pm.ServiceInfo[] services;
     field public java.lang.String sharedUserId;
     field public int sharedUserLabel;
@@ -11182,6 +11185,7 @@
     field public android.content.pm.SigningInfo signingInfo;
     field public java.lang.String[] splitNames;
     field public int[] splitRevisionCodes;
+    field public android.content.pm.UsesPermissionInfo[] usesPermissions;
     field public deprecated int versionCode;
     field public java.lang.String versionName;
   }
@@ -11657,6 +11661,7 @@
     field public java.lang.String group;
     field public java.lang.CharSequence nonLocalizedDescription;
     field public deprecated int protectionLevel;
+    field public boolean usageInfoRequired;
   }
 
   public final class ProviderInfo extends android.content.pm.ComponentInfo implements android.os.Parcelable {
@@ -11836,6 +11841,28 @@
     field public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
   }
 
+  public final class UsesPermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getDataRetention();
+    method public int getDataRetentionWeeks();
+    method public int getDataSentOffDevice();
+    method public int getDataSharedWithThirdParty();
+    method public int getDataUsedForMonetization();
+    method public int getFlags();
+    method public java.lang.String getPermission();
+    field public static final android.os.Parcelable.Creator<android.content.pm.UsesPermissionInfo> CREATOR;
+    field public static final int FLAG_REQUESTED_PERMISSION_GRANTED = 2; // 0x2
+    field public static final int RETENTION_NOT_RETAINED = 1; // 0x1
+    field public static final int RETENTION_SPECIFIED = 4; // 0x4
+    field public static final int RETENTION_UNDEFINED = 0; // 0x0
+    field public static final int RETENTION_UNLIMITED = 3; // 0x3
+    field public static final int RETENTION_USER_SELECTED = 2; // 0x2
+    field public static final int USAGE_NO = 3; // 0x3
+    field public static final int USAGE_UNDEFINED = 0; // 0x0
+    field public static final int USAGE_USER_TRIGGERED = 2; // 0x2
+    field public static final int USAGE_YES = 1; // 0x1
+  }
+
   public final class VersionedPackage implements android.os.Parcelable {
     ctor public VersionedPackage(java.lang.String, int);
     ctor public VersionedPackage(java.lang.String, long);
@@ -14698,6 +14725,7 @@
     method public float getTranslationX();
     method public float getTranslationY();
     method public float getTranslationZ();
+    method public long getUniqueId();
     method public int getWidth();
     method public boolean hasDisplayList();
     method public boolean hasIdentityMatrix();
@@ -15051,6 +15079,7 @@
     method public void invalidateSelf();
     method public boolean isAutoMirrored();
     method public boolean isFilterBitmap();
+    method public boolean isProjected();
     method public boolean isStateful();
     method public final boolean isVisible();
     method public void jumpToCurrentState();
@@ -29414,11 +29443,24 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.wifi.p2p.WifiP2pConfig> CREATOR;
+    field public static final int GROUP_OWNER_BAND_2GHZ = 1; // 0x1
+    field public static final int GROUP_OWNER_BAND_5GHZ = 2; // 0x2
+    field public static final int GROUP_OWNER_BAND_AUTO = 0; // 0x0
     field public java.lang.String deviceAddress;
     field public int groupOwnerIntent;
     field public android.net.wifi.WpsInfo wps;
   }
 
+  public static final class WifiP2pConfig.Builder {
+    ctor public WifiP2pConfig.Builder();
+    method public android.net.wifi.p2p.WifiP2pConfig build();
+    method public android.net.wifi.p2p.WifiP2pConfig.Builder enablePersistentMode(boolean);
+    method public android.net.wifi.p2p.WifiP2pConfig.Builder setDeviceAddress(android.net.MacAddress);
+    method public android.net.wifi.p2p.WifiP2pConfig.Builder setGroupOwnerBand(int);
+    method public android.net.wifi.p2p.WifiP2pConfig.Builder setNetworkName(java.lang.String);
+    method public android.net.wifi.p2p.WifiP2pConfig.Builder setPassphrase(java.lang.String);
+  }
+
   public class WifiP2pDevice implements android.os.Parcelable {
     ctor public WifiP2pDevice();
     ctor public WifiP2pDevice(android.net.wifi.p2p.WifiP2pDevice);
@@ -40899,11 +40941,9 @@
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
     method public android.view.SurfaceHolder getSurfaceHolder();
-    method public boolean isInAmbientMode();
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
-    method public void onAmbientModeChanged(boolean, boolean);
     method public void onApplyWindowInsets(android.view.WindowInsets);
     method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
     method public android.app.WallpaperColors onComputeColors();
diff --git a/api/system-current.txt b/api/system-current.txt
index 257b4f6..b4c7336 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -17,6 +17,7 @@
     field public static final java.lang.String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final java.lang.String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
     field public static final java.lang.String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK";
+    field public static final java.lang.String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
     field public static final java.lang.String BACKUP = "android.permission.BACKUP";
     field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -224,6 +225,7 @@
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
     field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
+    field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
   }
 
@@ -555,6 +557,10 @@
     method public void onVrStateChanged(boolean);
   }
 
+  public final class WallpaperInfo implements android.os.Parcelable {
+    method public boolean supportsAmbientMode();
+  }
+
   public class WallpaperManager {
     method public void clearWallpaper(int, int);
     method public void setDisplayOffset(android.os.IBinder, int, int);
@@ -3679,6 +3685,10 @@
 
   public class WifiManager {
     method public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
+    method public void connect(int, android.net.wifi.WifiManager.ActionListener);
+    method public void disable(int, android.net.wifi.WifiManager.ActionListener);
+    method public void disableEphemeralNetwork(java.lang.String);
+    method public void forget(int, android.net.wifi.WifiManager.ActionListener);
     method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>);
     method public java.util.List<android.net.wifi.hotspot2.OsuProvider> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
     method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
@@ -3690,6 +3700,7 @@
     method public boolean isWifiApEnabled();
     method public boolean isWifiScannerSupported();
     method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler);
+    method public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
     method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
     method public boolean startScan(android.os.WorkSource);
     method public void unregisterNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback);
@@ -5334,6 +5345,15 @@
 
 }
 
+package android.service.wallpaper {
+
+  public class WallpaperService.Engine {
+    method public boolean isInAmbientMode();
+    method public void onAmbientModeChanged(boolean, long);
+  }
+
+}
+
 package android.telecom {
 
   public deprecated class AudioState implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 8c710c1..c0f7ab6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -61,6 +61,7 @@
   }
 
   public class ActivityTaskManager {
+    method public void clearLaunchParamsForPackages(java.util.List<java.lang.String>);
     method public java.lang.String listAllStacks();
     method public void moveTaskToStack(int, int, boolean);
     method public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index b9732a5..8d61aba 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -679,7 +679,7 @@
 
 string LogEvent::ToString() const {
     string result;
-    result += StringPrintf("{ %lld %lld (%d)", (long long)mLogdTimestampNs,
+    result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
                            (long long)mElapsedTimestampNs, mTagId);
     for (const auto& value : mValues) {
         result +=
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 504c586..f1310db 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -343,9 +343,11 @@
                         }
                     }
                     if (isBytesField) {
-                        protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum,
-                                           (const char*)dim.mValue.str_value.c_str(),
-                                           dim.mValue.str_value.length());
+                        if (dim.mValue.str_value.length() > 0) {
+                            protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum,
+                                               (const char*)dim.mValue.str_value.c_str(),
+                                               dim.mValue.str_value.length());
+                        }
                     } else {
                         protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
                     }
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 90dfa87..3a5be43 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -605,7 +605,44 @@
     EXPECT_EQ(orig_str, result_str);
 }
 
+TEST(LogEventTest, TestBinaryFieldAtom_empty) {
+    Atom launcherAtom;
+    auto launcher_event = launcherAtom.mutable_launcher_event();
+    launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS);
+    launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW);
+    launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS);
 
+    // empty string.
+    string extension_str;
+
+    LogEvent event1(Atom::kLauncherEventFieldNumber, 1000);
+
+    event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS);
+    event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW);
+    event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS);
+    event1.write(extension_str);
+    event1.init();
+
+    ProtoOutputStream proto;
+    event1.ToProto(proto);
+
+    std::vector<uint8_t> outData;
+    outData.resize(proto.size());
+    size_t pos = 0;
+    auto iter = proto.data();
+    while (iter.readBuffer() != NULL) {
+        size_t toRead = iter.currentToRead();
+        std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
+        pos += toRead;
+        iter.rp()->move(toRead);
+    }
+
+    std::string result_str(outData.begin(), outData.end());
+    std::string orig_str;
+    launcherAtom.SerializeToString(&orig_str);
+
+    EXPECT_EQ(orig_str, result_str);
+}
 
 }  // namespace statsd
 }  // namespace os
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 5940c45..3095925 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4117,6 +4117,10 @@
 com.android.internal.util.VirtualRefBasePtr
 com.android.internal.util.XmlUtils
 com.android.internal.util.XmlUtils$WriteMapCallback
+com.android.internal.util.function.NonaConsumer
+com.android.internal.util.function.NonaFunction
+com.android.internal.util.function.OctConsumer
+com.android.internal.util.function.OctFunction
 com.android.internal.util.function.HeptConsumer
 com.android.internal.util.function.HeptFunction
 com.android.internal.util.function.HexConsumer
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 56ccf6f..6fdf7c8 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -433,4 +433,18 @@
         }
         return sb.toString();
     }
+
+    /**
+     * Clears launch params for the given package.
+     * @param packageNames the names of the packages of which the launch params are to be cleared
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void clearLaunchParamsForPackages(List<String> packageNames) {
+        try {
+            getService().clearLaunchParamsForPackages(packageNames);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 09b77d5..777a494 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -445,4 +445,9 @@
     void setPackageScreenCompatMode(in String packageName, int mode);
     boolean getPackageAskScreenCompat(in String packageName);
     void setPackageAskScreenCompat(in String packageName, boolean ask);
+
+    /**
+     * Clears launch params for given packages.
+     */
+    void clearLaunchParamsForPackages(in List<String> packageNames);
 }
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 00547b4..3a2038d 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -159,5 +159,5 @@
     /**
      * Called from SystemUI when it shows the AoD UI.
      */
-    oneway void setInAmbientMode(boolean inAmbientMode, boolean animated);
+    oneway void setInAmbientMode(boolean inAmbientMode, long animationDuration);
 }
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 3ea3da2..f0f7d89 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.SystemApi;
 import android.app.slice.Slice;
 import android.content.ComponentName;
 import android.content.Context;
@@ -330,7 +331,9 @@
      * @see WallpaperService.Engine#onAmbientModeChanged(boolean, boolean)
      * @see WallpaperService.Engine#isInAmbientMode()
      * @return {@code true} if wallpaper can draw when in ambient mode.
+     * @hide
      */
+    @SystemApi
     public boolean supportsAmbientMode() {
         return mSupportsAmbientMode;
     }
diff --git a/core/java/android/app/backup/OWNERS b/core/java/android/app/backup/OWNERS
index 1c9a43a..673d85f 100644
--- a/core/java/android/app/backup/OWNERS
+++ b/core/java/android/app/backup/OWNERS
@@ -1,7 +1,6 @@
-artikz@google.com
+anniemeng@google.com
 brufino@google.com
 bryanmawhinney@google.com
 ctate@google.com
 jorlow@google.com
-mkarpinski@google.com
 
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ecdd810..099d15a 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -22,6 +22,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Overall information about the contents of a package.  This corresponds
  * to all of the information collected from AndroidManifest.xml.
@@ -204,7 +207,10 @@
      * {@link PackageManager#GET_PERMISSIONS} was set.  This list includes
      * all permissions requested, even those that were not granted or known
      * by the system at install time.
+     *
+     * @deprecated Use {@link #usesPermissions}
      */
+    @Deprecated
     public String[] requestedPermissions;
 
     /**
@@ -214,10 +220,23 @@
      * {@link PackageManager#GET_PERMISSIONS} was set.  Each value matches
      * the corresponding entry in {@link #requestedPermissions}, and will have
      * the flag {@link #REQUESTED_PERMISSION_GRANTED} set as appropriate.
+     *
+     * @deprecated Use {@link #usesPermissions}
      */
+    @Deprecated
     public int[] requestedPermissionsFlags;
 
     /**
+     * Array of all {@link android.R.styleable#AndroidManifestUsesPermission
+     * &lt;uses-permission&gt;} tags included under &lt;manifest&gt;,
+     * or null if there were none.  This is only filled in if the flag
+     * {@link PackageManager#GET_PERMISSIONS} was set.  This list includes
+     * all permissions requested, even those that were not granted or known
+     * by the system at install time.
+     */
+    public UsesPermissionInfo[] usesPermissions;
+
+    /**
      * Flag for {@link #requestedPermissionsFlags}: the requested permission
      * is required for the application to run; the user can not optionally
      * disable it.  Currently all permissions are required.
@@ -456,6 +475,7 @@
         dest.writeTypedArray(permissions, parcelableFlags);
         dest.writeStringArray(requestedPermissions);
         dest.writeIntArray(requestedPermissionsFlags);
+        dest.writeTypedArray(usesPermissions, parcelableFlags);
         dest.writeTypedArray(signatures, parcelableFlags);
         dest.writeTypedArray(configPreferences, parcelableFlags);
         dest.writeTypedArray(reqFeatures, parcelableFlags);
@@ -520,6 +540,7 @@
         permissions = source.createTypedArray(PermissionInfo.CREATOR);
         requestedPermissions = source.createStringArray();
         requestedPermissionsFlags = source.createIntArray();
+        usesPermissions = source.createTypedArray(UsesPermissionInfo.CREATOR);
         signatures = source.createTypedArray(Signature.CREATOR);
         configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
         reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d00c9a0..49189e5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -785,18 +785,23 @@
                     pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
                 }
             }
-            N = p.requestedPermissions.size();
+            N = p.usesPermissionInfos.size();
             if (N > 0) {
                 pi.requestedPermissions = new String[N];
                 pi.requestedPermissionsFlags = new int[N];
+                pi.usesPermissions = new UsesPermissionInfo[N];
                 for (int i=0; i<N; i++) {
-                    final String perm = p.requestedPermissions.get(i);
+                    UsesPermissionInfo info = p.usesPermissionInfos.get(i);
+                    final String perm = info.getPermission();
                     pi.requestedPermissions[i] = perm;
+                    int permissionFlags = 0;
                     // The notion of required permissions is deprecated but for compatibility.
-                    pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+                    permissionFlags |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
                     if (grantedPermissions != null && grantedPermissions.contains(perm)) {
-                        pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
+                        permissionFlags |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
                     }
+                    pi.requestedPermissionsFlags[i] = permissionFlags;
+                    pi.usesPermissions[i] = new UsesPermissionInfo(info, permissionFlags);
                 }
             }
         }
@@ -2114,12 +2119,12 @@
                     return null;
                 }
             } else if (tagName.equals(TAG_USES_PERMISSION)) {
-                if (!parseUsesPermission(pkg, res, parser)) {
+                if (!parseUsesPermission(pkg, res, parser, outError)) {
                     return null;
                 }
             } else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
                     || tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
-                if (!parseUsesPermission(pkg, res, parser)) {
+                if (!parseUsesPermission(pkg, res, parser, outError)) {
                     return null;
                 }
             } else if (tagName.equals(TAG_USES_CONFIGURATION)) {
@@ -2442,7 +2447,7 @@
                     newPermsMsg.append(' ');
                 }
                 newPermsMsg.append(npi.name);
-                pkg.requestedPermissions.add(npi.name);
+                addRequestedPermission(pkg, npi.name);
                 pkg.implicitPermissions.add(npi.name);
             }
         }
@@ -2463,7 +2468,7 @@
             for (int in = 0; in < newPerms.size(); in++) {
                 final String perm = newPerms.get(in);
                 if (!pkg.requestedPermissions.contains(perm)) {
-                    pkg.requestedPermissions.add(perm);
+                    addRequestedPermission(pkg, perm);
                     pkg.implicitPermissions.add(perm);
                 }
             }
@@ -2543,13 +2548,13 @@
             }
         } else {
             if (FORCE_AUDIO_PACKAGES.contains(pkg.packageName)) {
-                pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_AUDIO);
+                addRequestedPermission(pkg, android.Manifest.permission.READ_MEDIA_AUDIO);
             }
             if (FORCE_VIDEO_PACKAGES.contains(pkg.packageName)) {
-                pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_VIDEO);
+                addRequestedPermission(pkg, android.Manifest.permission.READ_MEDIA_VIDEO);
             }
             if (FORCE_IMAGES_PACKAGES.contains(pkg.packageName)) {
-                pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_IMAGES);
+                addRequestedPermission(pkg, android.Manifest.permission.READ_MEDIA_IMAGES);
             }
         }
 
@@ -2589,6 +2594,14 @@
     }
 
     /**
+     * Helper method for adding a requested permission to a package outside of a uses-permission.
+     */
+    private void addRequestedPermission(Package pkg, String permission) {
+        pkg.requestedPermissions.add(permission);
+        pkg.usesPermissionInfos.add(new UsesPermissionInfo(permission));
+    }
+
+    /**
      * Computes the targetSdkVersion to use at runtime. If the package is not
      * compatible with this platform, populates {@code outError[0]} with an
      * error message.
@@ -2845,8 +2858,8 @@
         return certSha256Digests;
     }
 
-    private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
-            throws XmlPullParserException, IOException {
+    private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser,
+            String[] outError) throws XmlPullParserException, IOException {
         TypedArray sa = res.obtainAttributes(parser,
                 com.android.internal.R.styleable.AndroidManifestUsesPermission);
 
@@ -2870,6 +2883,44 @@
         final String requiredNotfeature = sa.getNonConfigurationString(
                 com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredNotFeature, 0);
 
+        int dataSentOffDevice = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestUsesPermission_dataSentOffDevice, 0);
+
+        int dataSharedWithThirdParty = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestUsesPermission_dataSharedWithThirdParty, 0);
+
+        int dataUsedForMonetization = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestUsesPermission_dataUsedForMonetization, 0);
+
+        int retentionWeeks = -1;
+        int retention;
+
+        String rawRetention = sa.getString(
+                com.android.internal.R.styleable.AndroidManifestUsesPermission_dataRetentionTime);
+
+        if (rawRetention == null) {
+            retention = UsesPermissionInfo.RETENTION_UNDEFINED;
+        } else if ("notRetained".equals(rawRetention)) {
+            retention = UsesPermissionInfo.RETENTION_NOT_RETAINED;
+        } else if ("userSelected".equals(rawRetention)) {
+            retention = UsesPermissionInfo.RETENTION_USER_SELECTED;
+        } else if ("unlimited".equals(rawRetention)) {
+            retention = UsesPermissionInfo.RETENTION_UNLIMITED;
+        } else {
+            // A number of weeks was specified
+            retention = UsesPermissionInfo.RETENTION_SPECIFIED;
+            retentionWeeks = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestUsesPermission_dataRetentionTime,
+                -1);
+
+            if (retentionWeeks < 0) {
+                outError[0] = "Bad value provided for dataRetentionTime.";
+                mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+                XmlUtils.skipCurrentTag(parser);
+                sa.recycle();
+                return false;
+            }
+        }
         sa.recycle();
 
         XmlUtils.skipCurrentTag(parser);
@@ -2902,6 +2953,10 @@
                     + parser.getPositionDescription());
         }
 
+        UsesPermissionInfo info = new UsesPermissionInfo(name, dataSentOffDevice,
+                dataSharedWithThirdParty, dataUsedForMonetization, retention, retentionWeeks);
+        pkg.usesPermissionInfos.add(info);
+
         return true;
     }
 
@@ -3236,6 +3291,10 @@
         perm.info.flags = sa.getInt(
                 com.android.internal.R.styleable.AndroidManifestPermission_permissionFlags, 0);
 
+        perm.info.usageInfoRequired = sa.getInt(
+                com.android.internal.R.styleable.AndroidManifestPermission_usageInfoRequired, 0)
+                != 0;
+
         sa.recycle();
 
         if (perm.info.protectionLevel == -1) {
@@ -6370,6 +6429,9 @@
         @UnsupportedAppUsage
         public final ArrayList<String> requestedPermissions = new ArrayList<String>();
 
+        public final ArrayList<UsesPermissionInfo> usesPermissionInfos =
+                new ArrayList<>();
+
         /** Permissions requested but not in the manifest. */
         public final ArrayList<String> implicitPermissions = new ArrayList<>();
 
@@ -6900,6 +6962,7 @@
 
             dest.readStringList(requestedPermissions);
             internStringArrayList(requestedPermissions);
+            dest.readParcelableList(usesPermissionInfos, boot);
             dest.readStringList(implicitPermissions);
             internStringArrayList(implicitPermissions);
             protectedBroadcasts = dest.createStringArrayList();
@@ -7066,6 +7129,7 @@
             dest.writeParcelableList(instrumentation, flags);
 
             dest.writeStringList(requestedPermissions);
+            dest.writeParcelableList(usesPermissionInfos, flags);
             dest.writeStringList(implicitPermissions);
             dest.writeStringList(protectedBroadcasts);
 
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index e21c33a..be6ed51 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -21,7 +21,6 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
@@ -130,9 +129,6 @@
      * </p>
      */
     public boolean isMatch(ComponentInfo componentInfo, int flags) {
-        if ((flags & MATCH_ALL) != 0) {
-            return true;
-        }
         final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp();
         final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
         if (!isAvailable(flags)
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 60c06a1..d9d6b5f 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -308,6 +309,12 @@
      */
     public CharSequence nonLocalizedDescription;
 
+    /**
+     * If {@code true} an application targeting {@link Build.VERSION_CODES#Q} <em>must</em>
+     * include permission data usage information in order to be able to be granted this permission.
+     */
+    public boolean usageInfoRequired;
+
     /** @hide */
     public static int fixProtectionLevel(int level) {
         if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -394,6 +401,7 @@
         descriptionRes = orig.descriptionRes;
         requestRes = orig.requestRes;
         nonLocalizedDescription = orig.nonLocalizedDescription;
+        usageInfoRequired = orig.usageInfoRequired;
     }
 
     /**
@@ -458,6 +466,7 @@
         dest.writeInt(descriptionRes);
         dest.writeInt(requestRes);
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
+        dest.writeInt(usageInfoRequired ? 1 : 0);
     }
 
     /** @hide */
@@ -498,5 +507,6 @@
         descriptionRes = source.readInt();
         requestRes = source.readInt();
         nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        usageInfoRequired = source.readInt() != 0;
     }
 }
diff --git a/core/java/android/content/pm/UsesPermissionInfo.java b/core/java/android/content/pm/UsesPermissionInfo.java
new file mode 100644
index 0000000..d08548f
--- /dev/null
+++ b/core/java/android/content/pm/UsesPermissionInfo.java
@@ -0,0 +1,275 @@
+/*
+ * 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 android.content.pm;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.RetentionPolicy;
+/**
+ * Information you can retrive about a particular application requested permission. This
+ * corresponds to information collected from the AndroidManifest.xml's &lt;uses-permission&gt;
+ * tags.
+ */
+public final class UsesPermissionInfo extends PackageItemInfo implements Parcelable {
+
+    /**
+     * Flag for {@link #getFlags()}: the requested permission is currently granted to the
+     * application.
+     */
+    public static final int FLAG_REQUESTED_PERMISSION_GRANTED = 1 << 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_REQUESTED_PERMISSION_GRANTED})
+    @java.lang.annotation.Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {}
+
+    /** An unset value for {@link #getDataSentOffDevice()},
+     * {@link #getDataSharedWithThirdParty()}, and {@link #getDataUsedForMonetization()}
+     */
+    public static final int USAGE_UNDEFINED = 0;
+
+    /**
+     * A yes value for {@link #getDataSentOffDevice()}, {@link #getDataSharedWithThirdParty()},
+     * and {@link #getDataUsedForMonetization()} corresponding to the <code>yes</code> value of
+     * {@link android.R.attr#dataSentOffDevice}, {@link android.R.attr#dataSharedWithThirdParty},
+     * and {@link android.R.attr#dataUsedForMonetization} attributes.
+     */
+    public static final int USAGE_YES = 1;
+
+    /**
+     * A user triggered only value for {@link #getDataSentOffDevice()},
+     * {@link #getDataSharedWithThirdParty()}, and {@link #getDataUsedForMonetization()}
+     * corresponding to the <code>userTriggered</code> value of
+     * {@link android.R.attr#dataSentOffDevice}, {@link android.R.attr#dataSharedWithThirdParty},
+     * and {@link android.R.attr#dataUsedForMonetization} attributes.
+     */
+    public static final int USAGE_USER_TRIGGERED = 2;
+
+    /**
+     * A no value for {@link #getDataSentOffDevice()}, {@link #getDataSharedWithThirdParty()},
+     * and {@link #getDataUsedForMonetization()} corresponding to the <code>no</code> value of
+     * {@link android.R.attr#dataSentOffDevice}, {@link android.R.attr#dataSharedWithThirdParty},
+     * and {@link android.R.attr#dataUsedForMonetization} attributes.
+     */
+    public static final int USAGE_NO = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"USAGE_"}, value = {
+        USAGE_UNDEFINED,
+        USAGE_YES,
+        USAGE_USER_TRIGGERED,
+        USAGE_NO})
+    @java.lang.annotation.Retention(RetentionPolicy.SOURCE)
+    public @interface Usage {}
+
+    /**
+     * An unset value for {@link #getDataRetention}.
+     */
+    public static final int RETENTION_UNDEFINED = 0;
+
+    /**
+     * A data not retained value for {@link #getDataRetention()} corresponding to the
+     * <code>notRetained</code> value of {@link android.R.attr#dataRetentionTime}.
+     */
+    public static final int RETENTION_NOT_RETAINED = 1;
+
+    /**
+     * A user selected value for {@link #getDataRetention()} corresponding to the
+     * <code>userSelected</code> value of {@link android.R.attr#dataRetentionTime}.
+     */
+    public static final int RETENTION_USER_SELECTED = 2;
+
+    /**
+     * An unlimited value for {@link #getDataRetention()} corresponding to the
+     * <code>unlimited</code> value of {@link android.R.attr#dataRetentionTime}.
+     */
+    public static final int RETENTION_UNLIMITED = 3;
+
+    /**
+     * A specified value for {@link #getDataRetention()} corresponding to providing the number of
+     * weeks data is retained in {@link android.R.attr#dataRetentionTime}. The number of weeks
+     * is available in {@link #getDataRetentionWeeks()}.
+     */
+    public static final int RETENTION_SPECIFIED = 4;
+
+    /** @hide */
+    @IntDef(prefix = {"RETENTION_"}, value = {
+        RETENTION_UNDEFINED,
+        RETENTION_NOT_RETAINED,
+        RETENTION_USER_SELECTED,
+        RETENTION_UNLIMITED,
+        RETENTION_SPECIFIED})
+    @java.lang.annotation.Retention(RetentionPolicy.SOURCE)
+    public @interface Retention {}
+
+    private final String mPermission;
+    private final @Flags int mFlags;
+    private final @Usage int mDataSentOffDevice;
+    private final @Usage int mDataSharedWithThirdParty;
+    private final @Usage int mDataUsedForMonetization;
+    private final @Retention int mDataRetention;
+    private final int mDataRetentionWeeks;
+
+    /** @hide */
+    public UsesPermissionInfo(String permission) {
+        mPermission = permission;
+        mDataSentOffDevice = USAGE_UNDEFINED;
+        mDataSharedWithThirdParty = USAGE_UNDEFINED;
+        mDataUsedForMonetization = USAGE_UNDEFINED;
+        mDataRetention = RETENTION_UNDEFINED;
+        mDataRetentionWeeks = -1;
+        mFlags = 0;
+    }
+
+    /** @hide */
+    public UsesPermissionInfo(String permission,
+            @Usage int dataSentOffDevice, @Usage int dataSharedWithThirdParty,
+            @Usage int dataUsedForMonetization, @Retention int dataRetention,
+            int dataRetentionWeeks) {
+        mPermission = permission;
+        mDataSentOffDevice = dataSentOffDevice;
+        mDataSharedWithThirdParty = dataSharedWithThirdParty;
+        mDataUsedForMonetization = dataUsedForMonetization;
+        mDataRetention = dataRetention;
+        mDataRetentionWeeks = dataRetentionWeeks;
+        mFlags = 0;
+    }
+
+    /** @hide */
+    public UsesPermissionInfo(UsesPermissionInfo orig) {
+        this(orig, orig.mFlags);
+    }
+
+    /** @hide */
+    public UsesPermissionInfo(UsesPermissionInfo orig, int flags) {
+        super(orig);
+        mPermission = orig.mPermission;
+        mFlags = flags;
+        mDataSentOffDevice = orig.mDataSentOffDevice;
+        mDataSharedWithThirdParty = orig.mDataSharedWithThirdParty;
+        mDataUsedForMonetization = orig.mDataUsedForMonetization;
+        mDataRetention = orig.mDataRetention;
+        mDataRetentionWeeks = orig.mDataRetentionWeeks;
+    }
+
+    /**
+     * The name of the requested permission.
+     */
+    public String getPermission() {
+        return mPermission;
+    }
+
+    public @Flags int getFlags() {
+        return mFlags;
+    }
+
+    /**
+     * If the application sends the data guarded by this permission off the device.
+     *
+     * See {@link android.R.attr#dataSentOffDevice}
+     */
+    public @Usage int getDataSentOffDevice() {
+        return mDataSentOffDevice;
+    }
+
+    /**
+     * If the application or its services shares the data guarded by this permission with third
+     * parties.
+     *
+     * See {@link android.R.attr#dataSharedWithThirdParty}
+     */
+    public @Usage int getDataSharedWithThirdParty() {
+        return mDataSharedWithThirdParty;
+    }
+
+    /**
+     * If the application or its services use the data guarded by this permission for monetization
+     * purposes.
+     *
+     * See {@link android.R.attr#dataUsedForMonetization}
+     */
+    public @Usage int getDataUsedForMonetization() {
+        return mDataUsedForMonetization;
+    }
+
+    /**
+     * How long the application or its services store the data guarded by this permission.
+     * If set to {@link #RETENTION_SPECIFIED} {@link #getDataRetentionWeeks()} will contain the
+     * number of weeks the data is stored.
+     *
+     * See {@link android.R.attr#dataRetentionTime}
+     */
+    public @Retention int getDataRetention() {
+        return mDataRetention;
+    }
+
+    /**
+     * If {@link #getDataRetention()} is {@link #RETENTION_SPECIFIED} the number of weeks the
+     * application or its services store data guarded by this permission.
+     *
+     * @throws IllegalStateException if {@link #getDataRetention} is not
+     * {@link #RETENTION_SPECIFIED}.
+     */
+    public int getDataRetentionWeeks() {
+        if (mDataRetention != RETENTION_SPECIFIED) {
+            throw new IllegalStateException("Data retention weeks not specified");
+        }
+        return mDataRetentionWeeks;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mPermission);
+        dest.writeInt(mFlags);
+        dest.writeInt(mDataSentOffDevice);
+        dest.writeInt(mDataSharedWithThirdParty);
+        dest.writeInt(mDataUsedForMonetization);
+        dest.writeInt(mDataRetention);
+        dest.writeInt(mDataRetentionWeeks);
+    }
+
+    private UsesPermissionInfo(Parcel source) {
+        super(source);
+        mPermission = source.readString();
+        mFlags = source.readInt();
+        mDataSentOffDevice = source.readInt();
+        mDataSharedWithThirdParty = source.readInt();
+        mDataUsedForMonetization = source.readInt();
+        mDataRetention = source.readInt();
+        mDataRetentionWeeks = source.readInt();
+    }
+
+    public static final Creator<UsesPermissionInfo> CREATOR =
+            new Creator<UsesPermissionInfo>() {
+                @Override
+                public UsesPermissionInfo createFromParcel(Parcel source) {
+                    return new UsesPermissionInfo(source);
+                }
+                @Override
+                public UsesPermissionInfo[] newArray(int size) {
+                    return new UsesPermissionInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 651caec..2abcb4c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1055,6 +1055,9 @@
      */
     public static final native long getPss(int pid);
 
+    /** @hide */
+    public static final native long[] getRss(int pid);
+
     /**
      * Specifies the outcome of having started a process.
      * @hide
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index dccce40..ebce484 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -27,7 +27,7 @@
     void setDesiredSize(int width, int height);
     void setDisplayPadding(in Rect padding);
     void setVisibility(boolean visible);
-    void setInAmbientMode(boolean inAmbientDisplay, boolean animated);
+    void setInAmbientMode(boolean inAmbientDisplay, long animationDuration);
     void dispatchPointer(in MotionEvent event);
     void dispatchWallpaperCommand(String action, int x, int y,
             int z, in Bundle extras);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 45d53f3..a095b0d 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Service;
 import android.app.WallpaperColors;
@@ -442,7 +443,9 @@
         /**
          * Returns true if this engine is running in ambient mode -- that is,
          * it is being shown in low power mode, on always on display.
+         * @hide
          */
+        @SystemApi
         public boolean isInAmbientMode() {
             return mIsInAmbientMode;
         }
@@ -568,14 +571,16 @@
          * Called when the device enters or exits ambient mode.
          *
          * @param inAmbientMode {@code true} if in ambient mode.
-         * @param animated {@code true} if you'll have the opportunity of animating your transition
-         *                 {@code false} when the wallpaper should present its ambient version
-         *                 immediately.
+         * @param animationDuration How long the transition animation to change the ambient state
+         *                          should run, in milliseconds. If 0 is passed as the argument
+         *                          here, the state should be switched immediately.
          *
          * @see #isInAmbientMode()
          * @see WallpaperInfo#supportsAmbientMode()
+         * @hide
          */
-        public void onAmbientModeChanged(boolean inAmbientMode, boolean animated) {
+        @SystemApi
+        public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
         }
 
         /**
@@ -1049,19 +1054,19 @@
          * message sent from handler.
          *
          * @param inAmbientMode {@code true} if in ambient mode.
-         * @param animated {@code true} if the transition will be animated.
+         * @param animationDuration For how long the transition will last, in ms.
          * @hide
          */
         @VisibleForTesting
-        public void doAmbientModeChanged(boolean inAmbientMode, boolean animated) {
+        public void doAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
             if (!mDestroyed) {
                 if (DEBUG) {
                     Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + ", "
-                            + animated + "): " + this);
+                            + animationDuration + "): " + this);
                 }
                 mIsInAmbientMode = inAmbientMode;
                 if (mCreated) {
-                    onAmbientModeChanged(inAmbientMode, animated);
+                    onAmbientModeChanged(inAmbientMode, animationDuration);
                 }
             }
         }
@@ -1320,10 +1325,10 @@
         }
 
         @Override
-        public void setInAmbientMode(boolean inAmbientDisplay, boolean animated)
+        public void setInAmbientMode(boolean inAmbientDisplay, long animationDuration)
                 throws RemoteException {
-            Message msg = mCaller.obtainMessageII(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0,
-                    animated ? 1 : 0);
+            Message msg = mCaller.obtainMessageIO(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0,
+                    animationDuration);
             mCaller.sendMessage(msg);
         }
 
@@ -1394,7 +1399,7 @@
                     return;
                 }
                 case DO_IN_AMBIENT_MODE: {
-                    mEngine.doAmbientModeChanged(message.arg1 != 0, message.arg2 != 0);
+                    mEngine.doAmbientModeChanged(message.arg1 != 0, (Long) message.obj);
                     return;
                 }
                 case MSG_UPDATE_SURFACE:
diff --git a/core/java/com/android/internal/util/function/NonaConsumer.java b/core/java/com/android/internal/util/function/NonaConsumer.java
new file mode 100644
index 0000000..3e7ce2b
--- /dev/null
+++ b/core/java/com/android/internal/util/function/NonaConsumer.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.util.function;
+
+import java.util.function.Consumer;
+
+/**
+ * A 9-argument {@link Consumer}
+ *
+ * @hide
+ */
+public interface NonaConsumer<A, B, C, D, E, F, G, H, I> {
+    void accept(A a, B b, C c, D d, E e, F f, G g, H h, I i);
+}
diff --git a/core/java/com/android/internal/util/function/NonaFunction.java b/core/java/com/android/internal/util/function/NonaFunction.java
new file mode 100644
index 0000000..560b4f1
--- /dev/null
+++ b/core/java/com/android/internal/util/function/NonaFunction.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.util.function;
+
+import java.util.function.Function;
+
+/**
+ * A 9-argument {@link Function}
+ *
+ * @hide
+ */
+public interface NonaFunction<A, B, C, D, E, F, G, H, I, R> {
+    R apply(A a, B b, C c, D d, E e, F f, G g, H h, I i);
+}
diff --git a/core/java/com/android/internal/util/function/NonaPredicate.java b/core/java/com/android/internal/util/function/NonaPredicate.java
new file mode 100644
index 0000000..c1e6f37
--- /dev/null
+++ b/core/java/com/android/internal/util/function/NonaPredicate.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.util.function;
+
+import java.util.function.Predicate;
+
+/**
+ * A 9-argument {@link Predicate}
+ *
+ * @hide
+ */
+public interface NonaPredicate<A, B, C, D, E, F, G, H, I> {
+    boolean test(A a, B b, C c, D d, E e, F f, G g, H h, I i);
+}
diff --git a/core/java/com/android/internal/util/function/OctConsumer.java b/core/java/com/android/internal/util/function/OctConsumer.java
new file mode 100644
index 0000000..83ee305
--- /dev/null
+++ b/core/java/com/android/internal/util/function/OctConsumer.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.util.function;
+
+import java.util.function.Consumer;
+
+/**
+ * A 8-argument {@link Consumer}
+ *
+ * @hide
+ */
+public interface OctConsumer<A, B, C, D, E, F, G, H> {
+    void accept(A a, B b, C c, D d, E e, F f, G g, H h);
+}
diff --git a/core/java/com/android/internal/util/function/OctFunction.java b/core/java/com/android/internal/util/function/OctFunction.java
new file mode 100644
index 0000000..cb16624
--- /dev/null
+++ b/core/java/com/android/internal/util/function/OctFunction.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.util.function;
+
+import java.util.function.Function;
+
+/**
+ * A 8-argument {@link Function}
+ *
+ * @hide
+ */
+public interface OctFunction<A, B, C, D, E, F, G, H, R> {
+    R apply(A a, B b, C c, D d, E e, F f, G g, H h);
+}
diff --git a/core/java/com/android/internal/util/function/OctPredicate.java b/core/java/com/android/internal/util/function/OctPredicate.java
new file mode 100644
index 0000000..7f36d6ac
--- /dev/null
+++ b/core/java/com/android/internal/util/function/OctPredicate.java
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.util.function;
+
+import java.util.function.Predicate;
+
+/**
+ * A 8-argument {@link Predicate}
+ *
+ * @hide
+ */
+public interface OctPredicate<A, B, C, D, E, F, G, H> {
+    boolean test(A a, B b, C c, D d, E e, F f, G g, H h);
+}
diff --git a/core/java/com/android/internal/util/function/pooled/OmniFunction.java b/core/java/com/android/internal/util/function/pooled/OmniFunction.java
index 4ffe441..d74e715 100755
--- a/core/java/com/android/internal/util/function/pooled/OmniFunction.java
+++ b/core/java/com/android/internal/util/function/pooled/OmniFunction.java
@@ -22,6 +22,10 @@
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.NonaConsumer;
+import com.android.internal.util.function.NonaFunction;
+import com.android.internal.util.function.OctConsumer;
+import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadConsumer;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintConsumer;
@@ -39,61 +43,62 @@
  *
  * @hide
  */
-abstract class OmniFunction<A, B, C, D, E, F, G, R> implements
+abstract class OmniFunction<A, B, C, D, E, F, G, H, I, R> implements
         PooledFunction<A, R>, BiFunction<A, B, R>, TriFunction<A, B, C, R>,
         QuadFunction<A, B, C, D, R>, QuintFunction<A, B, C, D, E, R>,
         HexFunction<A, B, C, D, E, F, R>, HeptFunction<A, B, C, D, E, F, G, R>,
+        OctFunction<A, B, C, D, E, F, G, H, R>, NonaFunction<A, B, C, D, E, F, G, H, I, R>,
         PooledConsumer<A>, BiConsumer<A, B>, TriConsumer<A, B, C>, QuadConsumer<A, B, C, D>,
         QuintConsumer<A, B, C, D, E>, HexConsumer<A, B, C, D, E, F>,
-        HeptConsumer<A, B, C, D, E, F, G>,
-        PooledPredicate<A>, BiPredicate<A, B>,
+        HeptConsumer<A, B, C, D, E, F, G>, OctConsumer<A, B, C, D, E, F, G, H>,
+        NonaConsumer<A, B, C, D, E, F, G, H, I>, PooledPredicate<A>, BiPredicate<A, B>,
         PooledSupplier<R>, PooledRunnable, ThrowingRunnable, ThrowingSupplier<R>,
         PooledSupplier.OfInt, PooledSupplier.OfLong, PooledSupplier.OfDouble {
 
-    abstract R invoke(A a, B b, C c, D d, E e, F f, G g);
+    abstract R invoke(A a, B b, C c, D d, E e, F f, G g, H h, I i);
 
     @Override
     public R apply(A o, B o2) {
-        return invoke(o, o2, null, null, null, null, null);
+        return invoke(o, o2, null, null, null, null, null, null, null);
     }
 
     @Override
     public R apply(A o) {
-        return invoke(o, null, null, null, null, null, null);
+        return invoke(o, null, null, null, null, null, null, null, null);
     }
 
-    public abstract <V> OmniFunction<A, B, C, D, E, F, G, V> andThen(
+    public abstract <V> OmniFunction<A, B, C, D, E, F, G, H, I, V> andThen(
             Function<? super R, ? extends V> after);
-    public abstract OmniFunction<A, B, C, D, E, F, G, R> negate();
+    public abstract OmniFunction<A, B, C, D, E, F, G, H, I, R> negate();
 
     @Override
     public void accept(A o, B o2) {
-        invoke(o, o2, null, null, null, null, null);
+        invoke(o, o2, null, null, null, null, null, null, null);
     }
 
     @Override
     public void accept(A o) {
-        invoke(o, null, null, null, null, null, null);
+        invoke(o, null, null, null, null, null, null, null, null);
     }
 
     @Override
     public void run() {
-        invoke(null, null, null, null, null, null, null);
+        invoke(null, null, null, null, null, null, null, null, null);
     }
 
     @Override
     public R get() {
-        return invoke(null, null, null, null, null, null, null);
+        return invoke(null, null, null, null, null, null, null, null, null);
     }
 
     @Override
     public boolean test(A o, B o2) {
-        return (Boolean) invoke(o, o2, null, null, null, null, null);
+        return (Boolean) invoke(o, o2, null, null, null, null, null, null, null);
     }
 
     @Override
     public boolean test(A o) {
-        return (Boolean) invoke(o, null, null, null, null, null, null);
+        return (Boolean) invoke(o, null, null, null, null, null, null, null, null);
     }
 
     @Override
@@ -108,52 +113,72 @@
 
     @Override
     public R apply(A a, B b, C c) {
-        return invoke(a, b, c, null, null, null, null);
+        return invoke(a, b, c, null, null, null, null, null, null);
     }
 
     @Override
     public void accept(A a, B b, C c) {
-        invoke(a, b, c, null, null, null, null);
+        invoke(a, b, c, null, null, null, null, null, null);
     }
 
     @Override
     public R apply(A a, B b, C c, D d) {
-        return invoke(a, b, c, d, null, null, null);
+        return invoke(a, b, c, d, null, null, null, null, null);
     }
 
     @Override
     public R apply(A a, B b, C c, D d, E e) {
-        return invoke(a, b, c, d, e, null, null);
+        return invoke(a, b, c, d, e, null, null, null, null);
     }
 
     @Override
     public R apply(A a, B b, C c, D d, E e, F f) {
-        return invoke(a, b, c, d, e, f, null);
+        return invoke(a, b, c, d, e, f, null, null, null);
     }
 
     @Override
     public R apply(A a, B b, C c, D d, E e, F f, G g) {
-        return invoke(a, b, c, d, e, f, g);
+        return invoke(a, b, c, d, e, f, g, null, null);
+    }
+
+    @Override
+    public R apply(A a, B b, C c, D d, E e, F f, G g, H h) {
+        return invoke(a, b, c, d, e, f, g, h, null);
+    }
+
+    @Override
+    public R apply(A a, B b, C c, D d, E e, F f, G g, H h, I i) {
+        return invoke(a, b, c, d, e, f, g, h, i);
     }
 
     @Override
     public void accept(A a, B b, C c, D d) {
-        invoke(a, b, c, d, null, null, null);
+        invoke(a, b, c, d, null, null, null, null, null);
     }
 
     @Override
     public void accept(A a, B b, C c, D d, E e) {
-        invoke(a, b, c, d, e, null, null);
+        invoke(a, b, c, d, e, null, null, null, null);
     }
 
     @Override
     public void accept(A a, B b, C c, D d, E e, F f) {
-        invoke(a, b, c, d, e, f, null);
+        invoke(a, b, c, d, e, f, null, null, null);
     }
 
     @Override
     public void accept(A a, B b, C c, D d, E e, F f, G g) {
-        invoke(a, b, c, d, e, f, g);
+        invoke(a, b, c, d, e, f, g, null, null);
+    }
+
+    @Override
+    public void accept(A a, B b, C c, D d, E e, F f, G g, H h) {
+        invoke(a, b, c, d, e, f, g, h, null);
+    }
+
+    @Override
+    public void accept(A a, B b, C c, D d, E e, F f, G g, H h, I i) {
+        invoke(a, b, c, d, e, f, g, h, i);
     }
 
     @Override
@@ -167,5 +192,5 @@
     }
 
     @Override
-    public abstract OmniFunction<A, B, C, D, E, F, G, R> recycleOnUse();
+    public abstract OmniFunction<A, B, C, D, E, F, G, H, I, R> recycleOnUse();
 }
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index af3c752..c00932e 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -25,6 +25,10 @@
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
+import com.android.internal.util.function.NonaConsumer;
+import com.android.internal.util.function.NonaFunction;
+import com.android.internal.util.function.OctConsumer;
+import com.android.internal.util.function.OctFunction;
 import com.android.internal.util.function.QuadConsumer;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuintConsumer;
@@ -176,7 +180,8 @@
             Consumer<? super A> function,
             A arg1) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 1, 0, ReturnType.VOID, arg1, null, null, null, null, null, null);
+                function, 1, 0, ReturnType.VOID, arg1, null, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -192,7 +197,8 @@
             Predicate<? super A> function,
             A arg1) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 1, 0, ReturnType.BOOLEAN, arg1, null, null, null, null, null, null);
+                function, 1, 0, ReturnType.BOOLEAN, arg1, null, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -208,7 +214,8 @@
             Function<? super A, ? extends R> function,
             A arg1) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 1, 0, ReturnType.OBJECT, arg1, null, null, null, null, null, null);
+                function, 1, 0, ReturnType.OBJECT, arg1, null, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -238,7 +245,8 @@
             A arg1) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 1, 0, ReturnType.VOID, arg1, null, null, null, null, null, null);
+                    function, 1, 0, ReturnType.VOID, arg1, null, null, null, null, null, null, null,
+                    null);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
@@ -257,7 +265,8 @@
             BiConsumer<? super A, ? super B> function,
             A arg1, B arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 0, ReturnType.VOID, arg1, arg2, null, null, null, null, null);
+                function, 2, 0, ReturnType.VOID, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -274,7 +283,8 @@
             BiPredicate<? super A, ? super B> function,
             A arg1, B arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 0, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null);
+                function, 2, 0, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -291,7 +301,8 @@
             BiFunction<? super A, ? super B, ? extends R> function,
             A arg1, B arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 0, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null);
+                function, 2, 0, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -308,7 +319,8 @@
             BiConsumer<? super A, ? super B> function,
             ArgumentPlaceholder<A> arg1, B arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.VOID, arg1, arg2, null, null, null, null, null);
+                function, 2, 1, ReturnType.VOID, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -325,7 +337,8 @@
             BiPredicate<? super A, ? super B> function,
             ArgumentPlaceholder<A> arg1, B arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null);
+                function, 2, 1, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -342,7 +355,8 @@
             BiFunction<? super A, ? super B, ? extends R> function,
             ArgumentPlaceholder<A> arg1, B arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null);
+                function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -359,7 +373,8 @@
             BiConsumer<? super A, ? super B> function,
             A arg1, ArgumentPlaceholder<B> arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.VOID, arg1, arg2, null, null, null, null, null);
+                function, 2, 1, ReturnType.VOID, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -376,7 +391,8 @@
             BiPredicate<? super A, ? super B> function,
             A arg1, ArgumentPlaceholder<B> arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null);
+                function, 2, 1, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -393,7 +409,8 @@
             BiFunction<? super A, ? super B, ? extends R> function,
             A arg1, ArgumentPlaceholder<B> arg2) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null);
+                function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -424,7 +441,8 @@
             A arg1, B arg2) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 2, 0, ReturnType.VOID, arg1, arg2, null, null, null, null, null);
+                    function, 2, 0, ReturnType.VOID, arg1, arg2, null, null, null, null, null, null,
+                    null);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
@@ -444,7 +462,8 @@
             TriConsumer<? super A, ? super B, ? super C> function,
             A arg1, B arg2, C arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 0, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 0, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -462,7 +481,8 @@
             TriFunction<? super A, ? super B, ? super C, ? extends R> function,
             A arg1, B arg2, C arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 0, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 0, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -480,7 +500,8 @@
             TriConsumer<? super A, ? super B, ? super C> function,
             ArgumentPlaceholder<A> arg1, B arg2, C arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 1, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -498,7 +519,8 @@
             TriFunction<? super A, ? super B, ? super C, ? extends R> function,
             ArgumentPlaceholder<A> arg1, B arg2, C arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -516,7 +538,8 @@
             TriConsumer<? super A, ? super B, ? super C> function,
             A arg1, ArgumentPlaceholder<B> arg2, C arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 1, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -534,7 +557,8 @@
             TriFunction<? super A, ? super B, ? super C, ? extends R> function,
             A arg1, ArgumentPlaceholder<B> arg2, C arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -552,7 +576,8 @@
             TriConsumer<? super A, ? super B, ? super C> function,
             A arg1, B arg2, ArgumentPlaceholder<C> arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 1, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -570,7 +595,8 @@
             TriFunction<? super A, ? super B, ? super C, ? extends R> function,
             A arg1, B arg2, ArgumentPlaceholder<C> arg3) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null);
+                function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
+                null);
     }
 
     /**
@@ -602,7 +628,8 @@
             A arg1, B arg2, C arg3) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 3, 0, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null);
+                    function, 3, 0, ReturnType.VOID, arg1, arg2, arg3, null, null, null, null, null,
+                    null);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
@@ -623,7 +650,8 @@
             QuadConsumer<? super A, ? super B, ? super C, ? super D> function,
             A arg1, B arg2, C arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -642,7 +670,8 @@
             QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
             A arg1, B arg2, C arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -661,7 +690,8 @@
             QuadConsumer<? super A, ? super B, ? super C, ? super D> function,
             ArgumentPlaceholder<A> arg1, B arg2, C arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -680,7 +710,8 @@
             QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
             ArgumentPlaceholder<A> arg1, B arg2, C arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -699,7 +730,8 @@
             QuadConsumer<? super A, ? super B, ? super C, ? super D> function,
             A arg1, ArgumentPlaceholder<B> arg2, C arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -718,7 +750,8 @@
             QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
             A arg1, ArgumentPlaceholder<B> arg2, C arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -737,7 +770,8 @@
             QuadConsumer<? super A, ? super B, ? super C, ? super D> function,
             A arg1, B arg2, ArgumentPlaceholder<C> arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -756,7 +790,8 @@
             QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
             A arg1, B arg2, ArgumentPlaceholder<C> arg3, D arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -775,7 +810,8 @@
             QuadConsumer<? super A, ? super B, ? super C, ? super D> function,
             A arg1, B arg2, C arg3, ArgumentPlaceholder<D> arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -794,7 +830,8 @@
             QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
             A arg1, B arg2, C arg3, ArgumentPlaceholder<D> arg4) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null);
+                function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
+                null);
     }
 
     /**
@@ -827,7 +864,8 @@
             A arg1, B arg2, C arg3, D arg4) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 4, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null);
+                    function, 4, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, null, null, null, null,
+                    null);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
@@ -849,7 +887,8 @@
             QuintConsumer<? super A, ? super B, ? super C, ? super D, ? super E> function,
             A arg1, B arg2, C arg3, D arg4, E arg5) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 5, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, null, null);
+                function, 5, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, null, null, null,
+                null);
     }
 
     /**
@@ -869,7 +908,8 @@
             QuintFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? extends R>
                     function, A arg1, B arg2, C arg3, D arg4, E arg5) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 5, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, null, null);
+                function, 5, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, null, null, null,
+                null);
     }
 
     /**
@@ -904,7 +944,8 @@
             A arg1, B arg2, C arg3, D arg4, E arg5) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 5, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, null, null);
+                    function, 5, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, null, null, null,
+                    null);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
@@ -927,7 +968,8 @@
             HexConsumer<? super A, ? super B, ? super C, ? super D, ? super E, ? super F> function,
             A arg1, B arg2, C arg3, D arg4, E arg5, F arg6) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 6, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, null);
+                function, 6, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, null, null,
+                null);
     }
 
     /**
@@ -948,7 +990,8 @@
             HexFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
                     ? extends R> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 6, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, null);
+                function, 6, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, null, null,
+                null);
     }
 
     /**
@@ -984,7 +1027,8 @@
             A arg1, B arg2, C arg3, D arg4, E arg5, F arg6) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 6, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, null);
+                    function, 6, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, null, null,
+                    null);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
@@ -1008,7 +1052,8 @@
             HeptConsumer<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
                     ? super G> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 7, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+                function, 7, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, null,
+                null);
     }
 
     /**
@@ -1031,7 +1076,8 @@
                     ? super G, ? extends R> function,
             A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7) {
         return acquire(PooledLambdaImpl.sPool,
-                function, 7, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+                function, 7, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, null,
+                null);
     }
 
     /**
@@ -1068,7 +1114,195 @@
                     ? super G> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 7, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+                    function, 7, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, null,
+                    null);
+            return Message.obtain().setCallback(callback.recycleOnUse());
+        }
+    }
+
+    /**
+     * {@link PooledRunnable} factory
+     *
+     * @param function non-capturing lambda(typically an unbounded method reference)
+     *                 to be invoked on call
+     * @param arg1 parameter supplied to {@code function} on call
+     * @param arg2 parameter supplied to {@code function} on call
+     * @param arg3 parameter supplied to {@code function} on call
+     * @param arg4 parameter supplied to {@code function} on call
+     * @param arg5 parameter supplied to {@code function} on call
+     * @param arg6 parameter supplied to {@code function} on call
+     * @param arg7 parameter supplied to {@code function} on call
+     * @param arg8 parameter supplied to {@code function} on call
+     * @return a {@link PooledRunnable}, equivalent to lambda:
+     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) }
+     */
+    static <A, B, C, D, E, F, G, H> PooledRunnable obtainRunnable(
+            OctConsumer<? super A, ? super B, ? super C, ? super D, ? super E, ? super F, ? super G,
+                    ? super H> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7,
+            H arg8) {
+        return acquire(PooledLambdaImpl.sPool,
+                function, 8, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
+                null);
+    }
+
+    /**
+     * {@link PooledSupplier} factory
+     *
+     * @param function non-capturing lambda(typically an unbounded method reference)
+     *                 to be invoked on call
+     * @param arg1 parameter supplied to {@code function} on call
+     * @param arg2 parameter supplied to {@code function} on call
+     * @param arg3 parameter supplied to {@code function} on call
+     * @param arg4 parameter supplied to {@code function} on call
+     * @param arg5 parameter supplied to {@code function} on call
+     * @param arg6 parameter supplied to {@code function} on call
+     * @param arg7 parameter supplied to {@code function} on call
+     * @param arg8 parameter supplied to {@code function} on call
+     * @return a {@link PooledSupplier}, equivalent to lambda:
+     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) }
+     */
+    static <A, B, C, D, E, F, G, H, R> PooledSupplier<R> obtainSupplier(
+            OctFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
+                                ? super G, ? super H, ? extends R> function,
+            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8) {
+        return acquire(PooledLambdaImpl.sPool,
+                function, 8, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
+                null);
+    }
+
+    /**
+     * Factory of {@link Message}s that contain an
+     * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
+     * {@link Message#getCallback internal callback}.
+     *
+     * The callback is equivalent to one obtainable via
+     * {@link #obtainRunnable(QuintConsumer, Object, Object, Object, Object, Object)}
+     *
+     * Note that using this method with {@link android.os.Handler#handleMessage}
+     * is more efficient than the alternative of {@link android.os.Handler#post}
+     * with a {@link PooledRunnable} due to the lack of 2 separate synchronization points
+     * when obtaining {@link Message} and {@link PooledRunnable} from pools separately
+     *
+     * You may optionally set a {@link Message#what} for the message if you want to be
+     * able to cancel it via {@link android.os.Handler#removeMessages}, but otherwise
+     * there's no need to do so
+     *
+     * @param function non-capturing lambda(typically an unbounded method reference)
+     *                 to be invoked on call
+     * @param arg1 parameter supplied to {@code function} on call
+     * @param arg2 parameter supplied to {@code function} on call
+     * @param arg3 parameter supplied to {@code function} on call
+     * @param arg4 parameter supplied to {@code function} on call
+     * @param arg5 parameter supplied to {@code function} on call
+     * @param arg6 parameter supplied to {@code function} on call
+     * @param arg7 parameter supplied to {@code function} on call
+     * @param arg8 parameter supplied to {@code function} on call
+     * @return a {@link Message} invoking {@code function(arg1, arg2, arg3, arg4, arg5, arg6,
+     * arg7, arg8) } when handled
+     */
+    static <A, B, C, D, E, F, G, H> Message obtainMessage(
+            OctConsumer<? super A, ? super B, ? super C, ? super D, ? super E, ? super F, ? super G,
+                    ? super H> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7,
+            H arg8) {
+        synchronized (Message.sPoolSync) {
+            PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
+                    function, 8, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
+                    null);
+            return Message.obtain().setCallback(callback.recycleOnUse());
+        }
+    }
+
+    /**
+     * {@link PooledRunnable} factory
+     *
+     * @param function non-capturing lambda(typically an unbounded method reference)
+     *                 to be invoked on call
+     * @param arg1 parameter supplied to {@code function} on call
+     * @param arg2 parameter supplied to {@code function} on call
+     * @param arg3 parameter supplied to {@code function} on call
+     * @param arg4 parameter supplied to {@code function} on call
+     * @param arg5 parameter supplied to {@code function} on call
+     * @param arg6 parameter supplied to {@code function} on call
+     * @param arg7 parameter supplied to {@code function} on call
+     * @param arg8 parameter supplied to {@code function} on call
+     * @param arg9 parameter supplied to {@code function} on call
+     * @return a {@link PooledRunnable}, equivalent to lambda:
+     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) }
+     */
+    static <A, B, C, D, E, F, G, H, I> PooledRunnable obtainRunnable(
+            NonaConsumer<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
+                    ? super G, ? super H, ? super I> function, A arg1, B arg2, C arg3, D arg4,
+            E arg5, F arg6, G arg7, H arg8, I arg9) {
+        return acquire(PooledLambdaImpl.sPool,
+                function, 9, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
+                arg9);
+    }
+
+    /**
+     * {@link PooledSupplier} factory
+     *
+     * @param function non-capturing lambda(typically an unbounded method reference)
+     *                 to be invoked on call
+     * @param arg1 parameter supplied to {@code function} on call
+     * @param arg2 parameter supplied to {@code function} on call
+     * @param arg3 parameter supplied to {@code function} on call
+     * @param arg4 parameter supplied to {@code function} on call
+     * @param arg5 parameter supplied to {@code function} on call
+     * @param arg6 parameter supplied to {@code function} on call
+     * @param arg7 parameter supplied to {@code function} on call
+     * @param arg8 parameter supplied to {@code function} on call
+     * @param arg9 parameter supplied to {@code function} on call
+     * @return a {@link PooledSupplier}, equivalent to lambda:
+     *         {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) }
+     */
+    static <A, B, C, D, E, F, G, H, I, R> PooledSupplier<R> obtainSupplier(
+            NonaFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
+                                ? super G, ? super H, ? super I, ? extends R> function,
+            A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9) {
+        return acquire(PooledLambdaImpl.sPool,
+                function, 9, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
+                arg9);
+    }
+
+    /**
+     * Factory of {@link Message}s that contain an
+     * ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
+     * {@link Message#getCallback internal callback}.
+     *
+     * The callback is equivalent to one obtainable via
+     * {@link #obtainRunnable(QuintConsumer, Object, Object, Object, Object, Object)}
+     *
+     * Note that using this method with {@link android.os.Handler#handleMessage}
+     * is more efficient than the alternative of {@link android.os.Handler#post}
+     * with a {@link PooledRunnable} due to the lack of 2 separate synchronization points
+     * when obtaining {@link Message} and {@link PooledRunnable} from pools separately
+     *
+     * You may optionally set a {@link Message#what} for the message if you want to be
+     * able to cancel it via {@link android.os.Handler#removeMessages}, but otherwise
+     * there's no need to do so
+     *
+     * @param function non-capturing lambda(typically an unbounded method reference)
+     *                 to be invoked on call
+     * @param arg1 parameter supplied to {@code function} on call
+     * @param arg2 parameter supplied to {@code function} on call
+     * @param arg3 parameter supplied to {@code function} on call
+     * @param arg4 parameter supplied to {@code function} on call
+     * @param arg5 parameter supplied to {@code function} on call
+     * @param arg6 parameter supplied to {@code function} on call
+     * @param arg7 parameter supplied to {@code function} on call
+     * @param arg8 parameter supplied to {@code function} on call
+     * @param arg9 parameter supplied to {@code function} on call
+     * @return a {@link Message} invoking {@code function(arg1, arg2, arg3, arg4, arg5, arg6,
+     * arg7, arg8, arg9) } when handled
+     */
+    static <A, B, C, D, E, F, G, H, I> Message obtainMessage(
+            NonaConsumer<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
+                    ? super G, ? super H, ? super I> function, A arg1, B arg2, C arg3, D arg4,
+            E arg5, F arg6, G arg7, H arg8, I arg9) {
+        synchronized (Message.sPoolSync) {
+            PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
+                    function, 9, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
+                    arg9);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
     }
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index eea1e5f..6be626a 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -30,6 +30,12 @@
 import com.android.internal.util.function.HexConsumer;
 import com.android.internal.util.function.HexFunction;
 import com.android.internal.util.function.HexPredicate;
+import com.android.internal.util.function.NonaConsumer;
+import com.android.internal.util.function.NonaFunction;
+import com.android.internal.util.function.NonaPredicate;
+import com.android.internal.util.function.OctConsumer;
+import com.android.internal.util.function.OctFunction;
+import com.android.internal.util.function.OctPredicate;
 import com.android.internal.util.function.QuadConsumer;
 import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.QuadPredicate;
@@ -54,12 +60,12 @@
  * @hide
  */
 final class PooledLambdaImpl<R> extends OmniFunction<Object,
-        Object, Object, Object, Object, Object, Object, R> {
+        Object, Object, Object, Object, Object, Object, Object, Object, R> {
 
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "PooledLambdaImpl";
 
-    private static final int MAX_ARGS = 7;
+    private static final int MAX_ARGS = 9;
 
     private static final int MAX_POOL_SIZE = 50;
 
@@ -125,7 +131,7 @@
 
     /**
      * Bit schema:
-     * AAAAAAABCDEEEEEEFFFFFF
+     * AAAAAAAAABCDEEEEEEFFFFFF
      *
      * Where:
      * A - whether {@link #mArgs arg} at corresponding index was specified at
@@ -161,17 +167,19 @@
     }
 
     @Override
-    R invoke(Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) {
+    R invoke(Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7,
+            Object a8, Object a9) {
         checkNotRecycled();
         if (DEBUG) {
             Log.i(LOG_TAG, this + ".invoke("
                     + commaSeparateFirstN(
-                            new Object[] { a1, a2, a3, a4, a5, a6, a7 },
+                            new Object[] { a1, a2, a3, a4, a5, a6, a7, a8, a9 },
                             LambdaType.decodeArgCount(getFlags(MASK_EXPOSED_AS)))
                     + ")");
         }
-        final boolean notUsed = fillInArg(a1) && fillInArg(a2) && fillInArg(a3)
-                && fillInArg(a4) && fillInArg(a5) && fillInArg(a6) && fillInArg(a7);
+        final boolean notUsed = fillInArg(a1) && fillInArg(a2) && fillInArg(a3) && fillInArg(a4)
+                && fillInArg(a5) && fillInArg(a6) && fillInArg(a7) && fillInArg(a8)
+                && fillInArg(a9);
         int argCount = LambdaType.decodeArgCount(getFlags(MASK_FUNC_TYPE));
         if (argCount != LambdaType.MASK_ARG_COUNT) {
             for (int i = 0; i < argCount; i++) {
@@ -335,7 +343,7 @@
                                 popArg(2), popArg(3), popArg(4), popArg(5));
                     }
                 }
-            }
+            } break;
 
             case 7: {
                 switch (returnType) {
@@ -356,7 +364,49 @@
                                 popArg(5), popArg(6));
                     }
                 }
-            }
+            } break;
+
+            case 8: {
+                switch (returnType) {
+                    case LambdaType.ReturnType.VOID: {
+                        ((OctConsumer) mFunc).accept(popArg(0), popArg(1),
+                                popArg(2), popArg(3), popArg(4),
+                                popArg(5), popArg(6), popArg(7));
+                        return null;
+                    }
+                    case LambdaType.ReturnType.BOOLEAN: {
+                        return (R) (Object) ((OctPredicate) mFunc).test(popArg(0),
+                                popArg(1), popArg(2), popArg(3),
+                                popArg(4), popArg(5), popArg(6), popArg(7));
+                    }
+                    case LambdaType.ReturnType.OBJECT: {
+                        return (R) ((OctFunction) mFunc).apply(popArg(0), popArg(1),
+                                popArg(2), popArg(3), popArg(4),
+                                popArg(5), popArg(6), popArg(7));
+                    }
+                }
+            } break;
+
+            case 9: {
+                switch (returnType) {
+                    case LambdaType.ReturnType.VOID: {
+                        ((NonaConsumer) mFunc).accept(popArg(0), popArg(1),
+                                popArg(2), popArg(3), popArg(4), popArg(5),
+                                popArg(6), popArg(7), popArg(8));
+                        return null;
+                    }
+                    case LambdaType.ReturnType.BOOLEAN: {
+                        return (R) (Object) ((NonaPredicate) mFunc).test(popArg(0),
+                                popArg(1), popArg(2), popArg(3), popArg(4),
+                                popArg(5), popArg(6), popArg(7), popArg(8));
+                    }
+                    case LambdaType.ReturnType.OBJECT: {
+                        return (R) ((NonaFunction) mFunc).apply(popArg(0), popArg(1),
+                                popArg(2), popArg(3), popArg(4), popArg(5),
+                                popArg(6), popArg(7), popArg(8));
+                    }
+                }
+            } break;
         }
         throw new IllegalStateException("Unknown function type: " + LambdaType.toString(funcType));
     }
@@ -419,8 +469,8 @@
      * Internal non-typesafe factory method for {@link PooledLambdaImpl}
      */
     static <E extends PooledLambda> E acquire(Pool pool, Object func,
-            int fNumArgs, int numPlaceholders, int fReturnType,
-            Object a, Object b, Object c, Object d, Object e, Object f, Object g) {
+            int fNumArgs, int numPlaceholders, int fReturnType, Object a, Object b, Object c,
+            Object d, Object e, Object f, Object g, Object h, Object i) {
         PooledLambdaImpl r = acquire(pool);
         if (DEBUG) {
             Log.i(LOG_TAG,
@@ -436,6 +486,8 @@
                             + ", e = " + e
                             + ", f = " + f
                             + ", g = " + g
+                            + ", h = " + h
+                            + ", i = " + i
                             + ")");
         }
         r.mFunc = func;
@@ -449,6 +501,8 @@
         setIfInBounds(r.mArgs, 4, e);
         setIfInBounds(r.mArgs, 5, f);
         setIfInBounds(r.mArgs, 6, g);
+        setIfInBounds(r.mArgs, 7, h);
+        setIfInBounds(r.mArgs, 8, i);
         return (E) r;
     }
 
@@ -474,13 +528,14 @@
     }
 
     @Override
-    public OmniFunction<Object, Object, Object, Object, Object, Object, Object, R> negate() {
+    public OmniFunction<Object, Object, Object, Object, Object, Object, Object, Object, Object,
+            R> negate() {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public <V> OmniFunction<Object, Object, Object, Object, Object, Object, Object, V> andThen(
-            Function<? super R, ? extends V> after) {
+    public <V> OmniFunction<Object, Object, Object, Object, Object, Object, Object, Object, Object,
+            V> andThen(Function<? super R, ? extends V> after) {
         throw new UnsupportedOperationException();
     }
 
@@ -500,7 +555,8 @@
     }
 
     @Override
-    public OmniFunction<Object, Object, Object, Object, Object, Object, Object, R> recycleOnUse() {
+    public OmniFunction<Object, Object, Object, Object, Object, Object, Object, Object, Object,
+            R> recycleOnUse() {
         if (DEBUG) Log.i(LOG_TAG, this + ".recycleOnUse()");
         mFlags |= FLAG_RECYCLE_ON_USE;
         return this;
@@ -584,6 +640,8 @@
                 case 5: return "Quint";
                 case 6: return "Hex";
                 case 7: return "Hept";
+                case 8: return "Oct";
+                case 9: return "Nona";
                 default: throw new IllegalArgumentException("" + argCount);
             }
         }
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 49d5007..fa1da4b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -33,8 +33,6 @@
 #include <iomanip>
 #include <string>
 
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
 #include <debuggerd/client.h>
 #include <log/log.h>
 #include <utils/misc.h>
@@ -50,10 +48,6 @@
 namespace android
 {
 
-static inline UniqueFile MakeUniqueFile(const char* path, const char* mode) {
-    return UniqueFile(fopen(path, mode), safeFclose);
-}
-
 enum {
     HEAP_UNKNOWN,
     HEAP_DALVIK,
diff --git a/core/jni/android_os_Debug.h b/core/jni/android_os_Debug.h
index 81270ca..c7b731b 100644
--- a/core/jni/android_os_Debug.h
+++ b/core/jni/android_os_Debug.h
@@ -19,6 +19,8 @@
 
 #include <memory>
 #include <stdio.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 
 namespace android {
 
@@ -27,6 +29,11 @@
 }
 
 using UniqueFile = std::unique_ptr<FILE, decltype(&safeFclose)>;
+
+inline UniqueFile MakeUniqueFile(const char* path, const char* mode) {
+    return UniqueFile(fopen(path, mode), safeFclose);
+}
+
 UniqueFile OpenSmapsOrRollup(int pid);
 
 }  // namespace android
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 4c7defb..377e65c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1128,6 +1128,39 @@
     return pss * 1024;
 }
 
+static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid)
+{
+    // total, file, anon, swap
+    jlong rss[4] = {0, 0, 0, 0};
+    std::string status_path =
+            android::base::StringPrintf("/proc/%d/status", pid);
+    UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
+
+    char line[256];
+    while (fgets(line, sizeof(line), file.get())) {
+        jlong v;
+        if ( sscanf(line, "VmRSS: %" SCNd64 " kB", &v) == 1) {
+            rss[0] = v;
+        } else if ( sscanf(line, "RssFile: %" SCNd64 " kB", &v) == 1) {
+            rss[1] = v;
+        } else if ( sscanf(line, "RssAnon: %" SCNd64 " kB", &v) == 1) {
+            rss[2] = v;
+        } else if ( sscanf(line, "VmSwap: %" SCNd64 " kB", &v) == 1) {
+            rss[3] = v;
+        }
+    }
+
+    jlongArray rssArray = env->NewLongArray(4);
+    if (rssArray == NULL) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return NULL;
+    }
+
+    env->SetLongArrayRegion(rssArray, 0, 4, rss);
+
+    return rssArray;
+}
+
 jintArray android_os_Process_getPidsForCommands(JNIEnv* env, jobject clazz,
         jobjectArray commandNames)
 {
@@ -1253,6 +1286,7 @@
     {"parseProcLine", "([BII[I[Ljava/lang/String;[J[F)Z", (void*)android_os_Process_parseProcLine},
     {"getElapsedCpuTime", "()J", (void*)android_os_Process_getElapsedCpuTime},
     {"getPss", "(I)J", (void*)android_os_Process_getPss},
+    {"getRss", "(I)[J", (void*)android_os_Process_getRss},
     {"getPidsForCommands", "([Ljava/lang/String;)[I", (void*)android_os_Process_getPidsForCommands},
     //{"setApplicationObject", "(Landroid/os/IBinder;)V", (void*)android_os_Process_setApplicationObject},
     {"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index e89b593..752624b 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -468,6 +468,10 @@
     return reinterpret_cast<RenderNode*>(renderNodePtr)->stagingProperties().getAllowForceDark();
 }
 
+static jlong android_view_RenderNode_getUniqueId(jlong renderNodePtr) {
+    return reinterpret_cast<RenderNode*>(renderNodePtr)->uniqueId();
+}
+
 // ----------------------------------------------------------------------------
 // RenderProperties - Animations
 // ----------------------------------------------------------------------------
@@ -694,6 +698,7 @@
     { "nGetHeight",                "(J)I",  (void*) android_view_RenderNode_getHeight },
     { "nSetAllowForceDark",        "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark },
     { "nGetAllowForceDark",        "(J)Z",  (void*) android_view_RenderNode_getAllowForceDark },
+    { "nGetUniqueId",              "(J)J",  (void*) android_view_RenderNode_getUniqueId },
 };
 
 int register_android_view_RenderNode(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6c3085f..8b66be3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4217,6 +4217,10 @@
          @hide -->
     <permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"
         android:protectionLevel="signature|preinstalled" />
+    <!-- @SystemApi Allows wallpaper to be rendered in ambient mode.
+         @hide -->
+    <permission android:name="android.permission.AMBIENT_WALLPAPER"
+                android:protectionLevel="signature|preinstalled" />
 
     <application android:process="system"
                  android:persistent="true"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 470e1cc..918070c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7935,7 +7935,9 @@
              wallpaper. -->
         <attr name="showMetadataInPreview" format="boolean" />
 
-        <!-- Wallpapers optimized and capable of drawing in ambient mode will return true. -->
+        <!-- Wallpapers optimized and capable of drawing in ambient mode will return true.
+             This feature requires the android.permission.AMBIENT_WALLPAPER permission.
+             @hide @SystemApi -->
         <attr name="supportsAmbientMode" format="boolean" />
 
         <!-- Uri that specifies a settings Slice for this wallpaper. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 18d1d5d..089c59f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1601,6 +1601,10 @@
         <attr name="request" />
         <attr name="protectionLevel" />
         <attr name="permissionFlags" />
+        <!-- If {@code true} applications that target Q <em>must</em> specify the permission usage
+             attributes in their {@code uses-permission} elements or the permission will not be
+             granted. -->
+        <attr name="usageInfoRequired" format="boolean" />
     </declare-styleable>
 
     <!-- The <code>permission-group</code> tag declares a logical grouping of
@@ -1700,6 +1704,81 @@
         requested.  If it does support the feature, it will be as if the manifest didn't
         request it at all. -->
         <attr name="requiredNotFeature" format="string" />
+
+        <!-- Specify if the app uploads data, or derived data, guarded by this permission.
+
+             If the permission is defined with {@link android.R.attr#usageInfoRequired}
+             {@code true} this <em>must</em> be specified by apps that target Android Q or the
+             permission will not be granted, it will be as if the manifest didn't request it at all.
+        -->
+        <attr name="dataSentOffDevice">
+          <!-- The application may send data, or derived data, guarded by this permission off of the
+               device. -->
+          <enum name="yes" value="1" />
+          <!-- The application may send data, or derived data, guarded by this permission off of the
+               device, however it will only do so when explicitly triggered by a user action. -->
+          <enum name="userTriggered" value="2" />
+          <!-- The application does not send data, or derived data, guarded by this permission off
+               of the device. -->
+          <enum name="no" value="3" />
+        </attr>
+
+        <!-- Specify if the application or its related off-device services provide data,
+             or derived data, guarded by this permission to third parties outside of the developer's
+             organization that do not qualify as data processors.
+
+             If the permission is defined with {@link android.R.attr#usageInfoRequired}
+             {@code true} this <em>must</em> be specified by apps that target Android Q or the
+             permission will not be granted, it will be as if the manifest didn't request it at all.
+             -->
+        <attr name="dataSharedWithThirdParty">
+          <!-- The application or its services may provide data, or derived data, guarded by this
+               permission to third party organizations. -->
+          <enum name="yes" value="1" />
+          <!-- The application or its services may provide data, or derived data, guarded by this
+               permission to third party organizations, however it will only do so when explicitly
+               triggered by a user action. -->
+          <enum name="userTriggered" value="2" />
+          <!-- The application or its services does not provide data, or derived data, guarded by
+               this permission to third party organizations. -->
+          <enum name="no" value="3" />
+        </attr>
+
+        <!-- Specify if the application or its related off-device services use data,
+             or derived data, guarded by this permission for monetization purposes.
+
+             For example, if the data is sold to another party or used for targeting advertisements
+             this must be set to {@code yes}.
+
+             If the permission is defined with {@link android.R.attr#usageInfoRequired}
+             {@code true} this <em>must</em> be specified by apps that target Android Q or the
+             permission will not be granted, it will be as if the manifest didn't request it at all.
+             -->
+        <attr name="dataUsedForMonetization">
+          <!-- The application or its services may use data, or derived data, guarded by this
+               permission for monetization purposes. -->
+          <enum name="yes" value="1" />
+          <!-- The application or its services may use data, or derived data, guarded by this
+               permission for monetization purposes, however it will only do so when explicity
+               triggered by a user action. -->
+          <enum name="userTriggered" value="2" />
+          <!--  The application or its services does not use data, or derived data, guarded by
+                this permission for monetization purposes. -->
+          <enum name="no" value="3" />
+        </attr>
+
+        <!-- Specify how long the application or its related off-device services store
+             data, or derived data, guarded by this permission.
+
+             This can be one of "notRetained", "userSelected", "unlimited", or a number
+             representing the number of weeks the data is retained.
+
+             If the permission is defined with {@link android.R.attr#usageInfoRequired}
+             {@code true} this <em>must</em> be specified by apps that target Android Q or the
+             permission will not be granted, it will be as if the manifest didn't request it at all.
+             -->
+        <attr name="dataRetentionTime" format="string" />
+
     </declare-styleable>
 
     <!-- The <code>uses-configuration</code> tag specifies
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 63cac51..feefcad 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2909,6 +2909,7 @@
         <public name="opticalInsetRight" />
         <public name="opticalInsetBottom" />
         <public name="forceDarkAllowed" />
+        <!-- @hide @SystemApi -->
         <public name="supportsAmbientMode" />
         <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
         <public name="usesNonSdkApi" />
@@ -2922,6 +2923,11 @@
         <public name="importantForContentCapture" />
         <public name="supportsMultipleDisplays" />
         <public name="useAppZygote" />
+        <public name="usageInfoRequired" />
+        <public name="dataSentOffDevice" />
+        <public name="dataSharedWithThirdParty" />
+        <public name="dataUsedForMonetization" />
+        <public name="dataRetentionTime" />
     </public-group>
 
     <public-group type="drawable" first-id="0x010800b4">
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 3b0dc9d..135c137 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -64,7 +64,7 @@
     public boolean isRecordingFor(Object o) { return false; }
 
     // may be null
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 117521088)
     private Bitmap mBitmap;
 
     // optional field set by the caller
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 45d7a21..d6f08b9 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -1173,6 +1173,22 @@
         return nGetAllowForceDark(mNativeRenderNode);
     }
 
+    /**
+     * Returns the unique ID that identifies this RenderNode. This ID is unique for the
+     * lifetime of the process. IDs are reset on process death, and are unique only within
+     * the process.
+     *
+     * This ID is intended to be used with debugging tools to associate a particular
+     * RenderNode across different debug dumping & inspection tools. For example
+     * a View layout inspector should include the unique ID for any RenderNodes that it owns
+     * to associate the drawing content with the layout content.
+     *
+     * @return the unique ID for this RenderNode
+     */
+    public long getUniqueId() {
+        return nGetUniqueId(mNativeRenderNode);
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // Animations
     ///////////////////////////////////////////////////////////////////////////
@@ -1479,4 +1495,7 @@
 
     @CriticalNative
     private static native boolean nGetAllowForceDark(long renderNode);
+
+    @CriticalNative
+    private static native long nGetUniqueId(long renderNode);
 }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index caf610b..5bd59d4 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -713,11 +713,12 @@
     }
 
     /**
-     * Whether this drawable requests projection.
+     * Whether this drawable requests projection. Indicates that the
+     * {@link android.graphics.RenderNode} this Drawable will draw into should be drawn immediately
+     * after the closest ancestor RenderNode containing a projection receiver.
      *
-     * @hide magic!
+     * @see android.graphics.RenderNode#setProjectBackwards(boolean)
      */
-    @UnsupportedAppUsage
     public boolean isProjected() {
         return false;
     }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4a5b61a..5f27fae 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -179,6 +179,7 @@
         "renderthread/CanvasContext.cpp",
         "renderthread/DrawFrameTask.cpp",
         "renderthread/EglManager.cpp",
+        "renderthread/ReliableSurface.cpp",
         "renderthread/VulkanManager.cpp",
         "renderthread/RenderProxy.cpp",
         "renderthread/RenderTask.cpp",
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d2a8f02..4a63910 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -29,6 +29,7 @@
 
 #include <SkPathOps.h>
 #include <algorithm>
+#include <atomic>
 #include <sstream>
 #include <string>
 
@@ -47,8 +48,14 @@
     TreeInfo* mTreeInfo;
 };
 
+static int64_t generateId() {
+    static std::atomic<int64_t> sNextId{1};
+    return sNextId++;
+}
+
 RenderNode::RenderNode()
-        : mDirtyPropertyFields(0)
+        : mUniqueId(generateId())
+        , mDirtyPropertyFields(0)
         , mNeedsDisplayListSync(false)
         , mDisplayList(nullptr)
         , mStagingDisplayList(nullptr)
@@ -444,5 +451,38 @@
     return &mClippedOutlineCache.clippedOutline;
 }
 
+using StringBuffer = FatVector<char, 128>;
+
+template <typename... T>
+static void format(StringBuffer& buffer, const std::string_view& format, T... args) {
+    buffer.resize(buffer.capacity());
+    while (1) {
+        int needed = snprintf(buffer.data(), buffer.size(),
+                format.data(), std::forward<T>(args)...);
+        if (needed < 0) {
+            buffer[0] = '\0';
+            buffer.resize(1);
+            return;
+        }
+        if (needed < buffer.size()) {
+            buffer.resize(needed + 1);
+            return;
+        }
+        buffer.resize(buffer.size() * 2);
+    }
+}
+
+void RenderNode::markDrawStart(SkCanvas& canvas) {
+    StringBuffer buffer;
+    format(buffer, "RenderNode(id=%d, name='%s')", uniqueId(), getName());
+    canvas.drawAnnotation(SkRect::MakeWH(getWidth(), getHeight()), buffer.data(), nullptr);
+}
+
+void RenderNode::markDrawEnd(SkCanvas& canvas) {
+    StringBuffer buffer;
+    format(buffer, "/RenderNode(id=%d, name='%s')", uniqueId(), getName());
+    canvas.drawAnnotation(SkRect::MakeWH(getWidth(), getHeight()), buffer.data(), nullptr);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index be0b46b..6060123 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -213,6 +213,11 @@
 
     UsageHint usageHint() const { return mUsageHint; }
 
+    int64_t uniqueId() const { return mUniqueId; }
+
+    void markDrawStart(SkCanvas& canvas);
+    void markDrawEnd(SkCanvas& canvas);
+
 private:
     void computeOrderingImpl(RenderNodeOp* opState,
                              std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface,
@@ -233,6 +238,7 @@
     void incParentRefCount() { mParentCount++; }
     void decParentRefCount(TreeObserver& observer, TreeInfo* info = nullptr);
 
+    const int64_t mUniqueId;
     String8 mName;
     sp<VirtualLightRefBase> mUserContext;
 
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index ea14d11..d80cb6d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -115,12 +115,26 @@
     }
 }
 
+class MarkDraw {
+public:
+    explicit MarkDraw(SkCanvas& canvas, RenderNode& node) : mCanvas(canvas), mNode(node) {
+        if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+            mNode.markDrawStart(mCanvas);
+        }
+    }
+    ~MarkDraw() {
+        if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+            mNode.markDrawEnd(mCanvas);
+        }
+    }
+private:
+    SkCanvas& mCanvas;
+    RenderNode& mNode;
+};
+
 void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
     RenderNode* renderNode = mRenderNode.get();
-    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
-        SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
-        canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
-    }
+    MarkDraw _marker{*canvas, *renderNode};
 
     // We only respect the nothingToDraw check when we are composing a layer. This
     // ensures that we paint the layer even if it is not currently visible in the
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 142bca9..07979a2 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -155,7 +155,7 @@
     }
 }
 
-bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior,
+bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior,
                                     ColorMode colorMode) {
     if (mEglSurface != EGL_NO_SURFACE) {
         mEglManager.destroySurface(mEglSurface);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 4ab3541..47991069 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -42,7 +42,7 @@
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
-    bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior,
+    bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior,
                     renderthread::ColorMode colorMode) override;
     void onStop() override;
     bool isSurfaceReady() override;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index a494e49..e50ad1c 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -115,7 +115,7 @@
 
 void SkiaVulkanPipeline::onStop() {}
 
-bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior,
+bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior,
                                     ColorMode colorMode) {
     if (mVkSurface) {
         mVkManager.destroySurface(mVkSurface);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 14c0d69..02874c7 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -38,7 +38,7 @@
     bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
                      FrameInfo* currentFrameInfo, bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
-    bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior,
+    bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior,
                     renderthread::ColorMode colorMode) override;
     void onStop() override;
     bool isSurfaceReady() override;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f1a522e..182233f 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -142,7 +142,12 @@
 void CanvasContext::setSurface(sp<Surface>&& surface) {
     ATRACE_CALL();
 
-    mNativeSurface = std::move(surface);
+    if (surface) {
+        mNativeSurface = new ReliableSurface{std::move(surface)};
+        mNativeSurface->setDequeueTimeout(500_ms);
+    } else {
+        mNativeSurface = nullptr;
+    }
 
     ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB;
     bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode);
@@ -285,6 +290,7 @@
 
     info.damageAccumulator = &mDamageAccumulator;
     info.layerUpdateQueue = &mLayerUpdateQueue;
+    info.out.canDrawThisFrame = true;
 
     mAnimationContext->startFrame(info.mode);
     mRenderPipeline->onPrepareTree();
@@ -304,7 +310,7 @@
 
     mIsDirty = true;
 
-    if (CC_UNLIKELY(!mNativeSurface.get())) {
+    if (CC_UNLIKELY(!hasSurface())) {
         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
         info.out.canDrawThisFrame = false;
         return;
@@ -323,27 +329,6 @@
             // the deadline for RT animations
             info.out.canDrawThisFrame = false;
         }
-        /* This logic exists to try and recover from a display latch miss, which essentially
-         * results in the bufferqueue being double-buffered instead of triple-buffered.
-         * SurfaceFlinger itself now tries to handle & recover from this situation, so this
-         * logic should no longer be necessary. As it's occasionally triggering when
-         * undesired disable it.
-         * TODO: Remove this entirely if the results are solid.
-        else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos() * 3 ||
-                   (latestVsync - mLastDropVsync) < 500_ms) {
-            // It's been several frame intervals, assume the buffer queue is fine
-            // or the last drop was too recent
-            info.out.canDrawThisFrame = true;
-        } else {
-            info.out.canDrawThisFrame = !isSwapChainStuffed();
-            if (!info.out.canDrawThisFrame) {
-                // dropping frame
-                mLastDropVsync = mRenderThread.timeLord().latestVsync();
-            }
-        }
-        */
-    } else {
-        info.out.canDrawThisFrame = true;
     }
 
     // TODO: Do we need to abort out if the backdrop is added but not ready? Should that even
@@ -354,6 +339,19 @@
 
     if (!info.out.canDrawThisFrame) {
         mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+        return;
+    }
+
+    int err = mNativeSurface->reserveNext();
+    if (err != OK) {
+        mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+        info.out.canDrawThisFrame = false;
+        ALOGW("reserveNext failed, error = %d", err);
+        if (err != TIMED_OUT) {
+            // A timed out surface can still recover, but assume others are permanently dead.
+            setSurface(nullptr);
+        }
+        return;
     }
 
     bool postedFrameCallback = false;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 70be4a6..9e7abf4 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -25,6 +25,7 @@
 #include "IRenderPipeline.h"
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
+#include "ReliableSurface.h"
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 #include "thread/Task.h"
@@ -219,7 +220,7 @@
     EGLint mLastFrameHeight = 0;
 
     RenderThread& mRenderThread;
-    sp<Surface> mNativeSurface;
+    sp<ReliableSurface> mNativeSurface;
     // stopped indicates the CanvasContext will reject actual redraw operations,
     // and defer repaint until it is un-stopped
     bool mStopped = false;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 65ced6a..8230dfd 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -31,6 +31,8 @@
 
 #include <string>
 #include <vector>
+#include <system/window.h>
+#include <gui/Surface.h>
 
 #define GLES_VERSION 2
 
@@ -106,7 +108,7 @@
     LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
                         "Failed to initialize display %p! err=%s", mEglDisplay, eglErrorString());
 
-    ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
+    ALOGV("Initialized EGL, version %d.%d", (int)major, (int)minor);
 
     initExtensions();
 
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 4972554..42e17b273 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -28,9 +28,9 @@
 
 class GrContext;
 
-namespace android {
+struct ANativeWindow;
 
-class Surface;
+namespace android {
 
 namespace uirenderer {
 
@@ -67,7 +67,7 @@
     virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
                              FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
-    virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
+    virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
     virtual void onStop() = 0;
     virtual bool isSurfaceReady() = 0;
     virtual bool isContextReady() = 0;
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
new file mode 100644
index 0000000..0ab4cd2
--- /dev/null
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -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.
+ */
+
+#include "ReliableSurface.h"
+
+#include <private/android/AHardwareBufferHelpers.h>
+
+namespace android::uirenderer::renderthread {
+
+// TODO: Make surface less protected
+// This exists because perform is a varargs, and ANativeWindow has no va_list perform.
+// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do
+// that instead
+struct SurfaceExposer : Surface {
+    // Make warnings happy
+    SurfaceExposer() = delete;
+
+    using Surface::setBufferCount;
+    using Surface::setSwapInterval;
+    using Surface::dequeueBuffer;
+    using Surface::queueBuffer;
+    using Surface::cancelBuffer;
+    using Surface::lockBuffer_DEPRECATED;
+    using Surface::perform;
+};
+
+#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__)
+
+ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) {
+    LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr");
+
+    ANativeWindow::setSwapInterval = hook_setSwapInterval;
+    ANativeWindow::dequeueBuffer = hook_dequeueBuffer;
+    ANativeWindow::cancelBuffer = hook_cancelBuffer;
+    ANativeWindow::queueBuffer = hook_queueBuffer;
+    ANativeWindow::query = hook_query;
+    ANativeWindow::perform = hook_perform;
+
+    ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED;
+    ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED;
+    ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED;
+    ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED;
+}
+
+void ReliableSurface::perform(int operation, va_list args) {
+    std::lock_guard _lock{mMutex};
+
+    switch (operation) {
+        case NATIVE_WINDOW_SET_USAGE:
+            mUsage = va_arg(args, uint32_t);
+            break;
+        case NATIVE_WINDOW_SET_USAGE64:
+            mUsage = va_arg(args, uint64_t);
+            break;
+        case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
+            /* width */ va_arg(args, uint32_t);
+            /* height */ va_arg(args, uint32_t);
+            mFormat = va_arg(args, PixelFormat);
+            break;
+        case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
+            mFormat = va_arg(args, PixelFormat);
+            break;
+    }
+}
+
+int ReliableSurface::reserveNext() {
+    {
+        std::lock_guard _lock{mMutex};
+        if (mReservedBuffer) {
+            ALOGW("reserveNext called but there was already a buffer reserved?");
+            return OK;
+        }
+        if (mInErrorState) {
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    int fenceFd = -1;
+    ANativeWindowBuffer* buffer = nullptr;
+    int result = callProtected(mSurface, dequeueBuffer, &buffer, &fenceFd);
+
+    {
+        std::lock_guard _lock{mMutex};
+        LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext");
+        mReservedBuffer = buffer;
+        mReservedFenceFd.reset(fenceFd);
+        if (result != OK) {
+            ALOGW("reserveNext failed, error %d", result);
+        }
+    }
+
+    return result;
+}
+
+void ReliableSurface::clearReservedBuffer() {
+    std::lock_guard _lock{mMutex};
+    if (mReservedBuffer) {
+        ALOGW("Reserved buffer %p was never used", mReservedBuffer);
+    }
+    mReservedBuffer = nullptr;
+    mReservedFenceFd.reset();
+}
+
+int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) {
+    clearReservedBuffer();
+    if (isFallbackBuffer(buffer)) {
+        if (fenceFd > 0) {
+            close(fenceFd);
+        }
+        return OK;
+    }
+    int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd);
+    return result;
+}
+
+int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) {
+    {
+        std::lock_guard _lock{mMutex};
+        if (mReservedBuffer) {
+            *buffer = mReservedBuffer;
+            *fenceFd = mReservedFenceFd.release();
+            mReservedBuffer = nullptr;
+            return OK;
+        }
+    }
+
+    int result = callProtected(mSurface, dequeueBuffer, buffer, fenceFd);
+    if (result != OK) {
+        ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result);
+        *buffer = acquireFallbackBuffer();
+        *fenceFd = -1;
+        return *buffer ? OK : INVALID_OPERATION;
+    }
+    return OK;
+}
+
+int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) {
+    clearReservedBuffer();
+
+    if (isFallbackBuffer(buffer)) {
+        if (fenceFd > 0) {
+            close(fenceFd);
+        }
+        return OK;
+    }
+
+    int result = callProtected(mSurface, queueBuffer, buffer, fenceFd);
+    return result;
+}
+
+bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const {
+    if (!mScratchBuffer || !windowBuffer) {
+        return false;
+    }
+    ANativeWindowBuffer* scratchBuffer =
+            AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
+    return windowBuffer == scratchBuffer;
+}
+
+ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer() {
+    std::lock_guard _lock{mMutex};
+    mInErrorState = true;
+
+    if (mScratchBuffer) {
+        return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
+    }
+
+    AHardwareBuffer_Desc desc;
+    desc.usage = mUsage;
+    desc.format = mFormat;
+    desc.width = 1;
+    desc.height = 1;
+    desc.layers = 1;
+    desc.rfu0 = 0;
+    desc.rfu1 = 0;
+    AHardwareBuffer* newBuffer = nullptr;
+    int err = AHardwareBuffer_allocate(&desc, &newBuffer);
+    if (err) {
+        // Allocate failed, that sucks
+        ALOGW("Failed to allocate scratch buffer, error=%d", err);
+        return nullptr;
+    }
+    mScratchBuffer.reset(newBuffer);
+    return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer);
+}
+
+Surface* ReliableSurface::getWrapped(const ANativeWindow* window) {
+    return getSelf(window)->mSurface.get();
+}
+
+int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) {
+    return callProtected(getWrapped(window), setSwapInterval, interval);
+}
+
+int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer,
+                                        int* fenceFd) {
+    return getSelf(window)->dequeueBuffer(buffer, fenceFd);
+}
+
+int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
+                                       int fenceFd) {
+    return getSelf(window)->cancelBuffer(buffer, fenceFd);
+}
+
+int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
+                                      int fenceFd) {
+    return getSelf(window)->queueBuffer(buffer, fenceFd);
+}
+
+int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window,
+                                                   ANativeWindowBuffer** buffer) {
+    ANativeWindowBuffer* buf;
+    int fenceFd = -1;
+    int result = window->dequeueBuffer(window, &buf, &fenceFd);
+    if (result != OK) {
+        return result;
+    }
+    sp<Fence> fence(new Fence(fenceFd));
+    int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED");
+    if (waitResult != OK) {
+        ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult);
+        window->cancelBuffer(window, buf, -1);
+        return waitResult;
+    }
+    *buffer = buf;
+    return result;
+}
+
+int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window,
+                                                  ANativeWindowBuffer* buffer) {
+    return window->cancelBuffer(window, buffer, -1);
+}
+
+int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window,
+                                                ANativeWindowBuffer* buffer) {
+    // This method is a no-op in Surface as well
+    return OK;
+}
+
+int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window,
+                                                 ANativeWindowBuffer* buffer) {
+    return window->queueBuffer(window, buffer, -1);
+}
+
+int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) {
+    return getWrapped(window)->query(what, value);
+}
+
+int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) {
+    va_list args;
+    va_start(args, operation);
+    int result = callProtected(getWrapped(window), perform, operation, args);
+    va_end(args);
+
+    switch (operation) {
+        case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
+        case NATIVE_WINDOW_SET_USAGE:
+        case NATIVE_WINDOW_SET_USAGE64:
+            va_start(args, operation);
+            getSelf(window)->perform(operation, args);
+            va_end(args);
+            break;
+        default:
+            break;
+    }
+
+    return result;
+}
+
+};  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
new file mode 100644
index 0000000..9ae53a9
--- /dev/null
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gui/Surface.h>
+#include <utils/Macros.h>
+#include <utils/StrongPointer.h>
+
+#include <memory>
+
+namespace android::uirenderer::renderthread {
+
+class ReliableSurface : public ANativeObjectBase<ANativeWindow, ReliableSurface, RefBase> {
+    PREVENT_COPY_AND_ASSIGN(ReliableSurface);
+
+public:
+    ReliableSurface(sp<Surface>&& surface);
+
+    void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); }
+
+    int reserveNext();
+
+    void allocateBuffers() { mSurface->allocateBuffers(); }
+
+    int query(int what, int* value) const { return mSurface->query(what, value); }
+
+    nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); }
+
+    uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); }
+
+private:
+    const sp<Surface> mSurface;
+
+    mutable std::mutex mMutex;
+
+    uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER;
+    PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888;
+    std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{
+            nullptr, AHardwareBuffer_release};
+    bool mInErrorState = false;
+    ANativeWindowBuffer* mReservedBuffer = nullptr;
+    base::unique_fd mReservedFenceFd;
+
+    bool isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const;
+    ANativeWindowBuffer* acquireFallbackBuffer();
+    void clearReservedBuffer();
+
+    void perform(int operation, va_list args);
+    int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd);
+    int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
+    int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd);
+
+    static Surface* getWrapped(const ANativeWindow*);
+
+    // ANativeWindow hooks
+    static int hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd);
+    static int hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer,
+                                  int* fenceFd);
+    static int hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd);
+
+    static int hook_perform(ANativeWindow* window, int operation, ...);
+    static int hook_query(const ANativeWindow* window, int what, int* value);
+    static int hook_setSwapInterval(ANativeWindow* window, int interval);
+
+    static int hook_cancelBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer);
+    static int hook_dequeueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer** buffer);
+    static int hook_lockBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer);
+    static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer);
+};
+
+};  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 511f6cd..5411e66 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -59,6 +59,8 @@
                     // read next event
                     int count = mInputStream.read(buffer);
                     if (count < 0) {
+                        // This is the exit condition as read() returning <0 indicates
+                        // that the pipe has been closed.
                         break;
                         // FIXME - inform receivers here?
                     }
@@ -81,10 +83,15 @@
                             Log.e(TAG, "Unknown packet type " + packetType);
                             break;
                     }
-                }
+                } // while (true)
             } catch (IOException e) {
                 // FIXME report I/O failure?
-                Log.e(TAG, "read failed", e);
+                // TODO: The comment above about the exit condition is not currently working
+                // as intended. The read from the closed pipe is throwing an error rather than
+                // returning <0, so this becomes (probably) not an error, but the exit case.
+                // This warrants further investigation;
+                // Silence the (probably) spurious error message.
+                // Log.e(TAG, "read failed", e);
             } finally {
                 IoUtils.closeQuietly(mInputStream);
             }
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
index 0678263..789d185 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
@@ -72,7 +72,6 @@
     </LinearLayout>
 
     <LinearLayout
-        android:id="@+id/entity_header_links"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:layout_centerVertical="true"
@@ -85,6 +84,7 @@
             android:layout_width="wrap_content"
             android:layout_weight="1"
             android:layout_height="0dp"
+            android:visibility="gone"
             android:minWidth="24dp"
             android:src="@null"
             android:tint="?android:attr/colorAccent"/>
@@ -95,6 +95,7 @@
             android:layout_width="wrap_content"
             android:layout_weight="1"
             android:layout_height="0dp"
+            android:visibility="gone"
             android:minWidth="24dp"
             android:src="@null"
             android:tint="?android:attr/colorAccent"/>
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index d15a3ef..cfa067f 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -32,12 +32,13 @@
 
 include $(BUILD_PACKAGE)
 
-#############################################
-# SettingsLib Robolectric test target.      #
-#############################################
+############################################################
+# SettingsLib Robolectric test target.                     #
+############################################################
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := SettingsLibRoboTests
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -53,6 +54,9 @@
 
 LOCAL_MODULE_TAGS := optional
 
+# Generate test_config.properties
+include external/robolectric-shadows/gen_test_config.mk
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 #############################################################
diff --git a/packages/SettingsLib/tests/robotests/config/robolectric.properties b/packages/SettingsLib/tests/robotests/config/robolectric.properties
index 6b5b8e5..fab7251 100644
--- a/packages/SettingsLib/tests/robotests/config/robolectric.properties
+++ b/packages/SettingsLib/tests/robotests/config/robolectric.properties
@@ -1,5 +1 @@
-manifest=frameworks/base/packages/SettingsLib/tests/robotests/AndroidManifest.xml
 sdk=NEWEST_SDK
-
-shadows=\
-   com.android.settingslib.testutils.shadow.ShadowXmlUtils
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java
index 9ba9967..3a4e2e4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java
@@ -30,10 +30,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.util.ReflectionHelpers;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class CustomEditTextPreferenceComaptTest {
 
     @Mock
@@ -70,7 +71,7 @@
     }
 
     private static class TestPreference extends CustomEditTextPreferenceCompat {
-        public TestPreference(Context context) {
+        private TestPreference(Context context) {
             super(context);
         }
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java
index 9d7f59a..e94a06c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java
@@ -30,10 +30,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.util.ReflectionHelpers;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class CustomEditTextPreferenceTest {
 
     @Mock
@@ -70,7 +71,7 @@
     }
 
     private static class TestPreference extends CustomEditTextPreference {
-        public TestPreference(Context context) {
+        private TestPreference(Context context) {
             super(context);
         }
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/DeviceInfoUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/DeviceInfoUtilsTest.java
index 19a916c..4e8af73 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/DeviceInfoUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/DeviceInfoUtilsTest.java
@@ -24,9 +24,10 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class DeviceInfoUtilsTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java
index 36b70df..4d76331 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/HelpUtilsTest.java
@@ -18,12 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.R;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -36,20 +37,19 @@
 import android.provider.Settings;
 import android.view.MenuItem;
 
-import android.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
 /**
  * Tests for {@link HelpUtils}.
  */
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class HelpUtilsTest {
     private static final String TEST_HELP_URL = "intent:#Intent;action=com.android.test;end";
     private static final String PACKAGE_NAME_KEY = "package-name-key";
@@ -83,8 +83,6 @@
         when(mContext.getResources().getString(R.string.config_feedbackIntentNameKey))
                 .thenReturn(FEEDBACK_INTENT_NAME_KEY);
         when(mActivity.getPackageManager()).thenReturn(mPackageManager);
-
-
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index 88ac8ce..2b5a4e0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -25,8 +25,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
@@ -44,11 +44,12 @@
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.Arrays;
 import java.util.Collections;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class RestrictedLockUtilsTest {
 
     @Mock
@@ -178,8 +179,7 @@
     public void checkIfKeyguardFeaturesAreDisabled_doesMatchAllowedFeature_unifiedManagedProfile() {
         UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
         UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
-        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(new UserInfo[] {
-                userInfo, profileInfo}));
+        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
 
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
                 .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
@@ -207,8 +207,7 @@
     public void checkIfKeyguardFeaturesAreDisabled_notMatchOtherFeatures_unifiedManagedProfile() {
         UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
         UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
-        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(new UserInfo[] {
-                userInfo, profileInfo}));
+        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
 
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
                 .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
@@ -231,8 +230,7 @@
     public void checkIfKeyguardFeaturesAreDisabled_onlyMatchesProfile_separateManagedProfile() {
         UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
         UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
-        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(new UserInfo[] {
-                userInfo, profileInfo}));
+        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
 
         when(mDevicePolicyManager.getKeyguardDisabledFeatures(mAdmin1, mUserId))
                 .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
@@ -268,8 +266,7 @@
     public void checkIfKeyguardFeaturesAreDisabled_onlyMatchesParent_profileParentPolicy() {
         UserInfo userInfo = setUpUser(mUserId, new ComponentName[] {mAdmin1});
         UserInfo profileInfo = setUpManagedProfile(mProfileId, new ComponentName[] {mAdmin2});
-        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(new UserInfo[] {
-                userInfo, profileInfo}));
+        when(mUserManager.getProfiles(mUserId)).thenReturn(Arrays.asList(userInfo, profileInfo));
 
         when(mProxy.getParentProfileInstance(any(DevicePolicyManager.class), any())
                 .getKeyguardDisabledFeatures(mAdmin2, mProfileId))
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index 79d682d6..1b10c73 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib;
 
-
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -35,8 +34,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class RestrictedPreferenceHelperTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
deleted file mode 100644
index 8757eed..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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;
-
-import android.annotation.NonNull;
-
-import org.junit.runners.model.InitializationError;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.manifest.AndroidManifest;
-import org.robolectric.res.Fs;
-import org.robolectric.res.ResourcePath;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-
-public class SettingsLibRobolectricTestRunner extends RobolectricTestRunner {
-
-    public SettingsLibRobolectricTestRunner(Class<?> testClass) throws InitializationError {
-        super(testClass);
-    }
-
-    /**
-     * We are going to create our own custom manifest so we can add multiple resource paths to it.
-     */
-    @Override
-    protected AndroidManifest getAppManifest(Config config) {
-        try {
-            // Using the manifest file's relative path, we can figure out the application directory.
-            final URL appRoot =
-                new URL("file:frameworks/base/packages/SettingsLib/tests/robotests");
-            final URL manifestPath = new URL(appRoot, "AndroidManifest.xml");
-            final URL resDir = new URL(appRoot, "res");
-            final URL assetsDir = new URL(appRoot, "assets");
-
-            return new AndroidManifest(Fs.fromURL(manifestPath), Fs.fromURL(resDir),
-                Fs.fromURL(assetsDir), "com.android.settingslib") {
-                @Override
-                public List<ResourcePath> getIncludedResourcePaths() {
-                    final List<ResourcePath> paths = super.getIncludedResourcePaths();
-                    paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/AppPreference/res"));
-                    paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/HelpUtils/res"));
-                    paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/RestrictedLockUtils/res"));
-                    paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/"
-                            + "SettingsLayoutPreference/res"));
-                    paths.add(resourcePath("file:frameworks/base/packages/SettingsLib/res"));
-                    paths.add(resourcePath("file:frameworks/base/core/res/res"));
-                    paths.add(resourcePath("file:out/soong/.intermediates/prebuilts/sdk/current/androidx/androidx.appcompat_appcompat-nodeps/android_common/aar/res/"));
-                    return paths;
-                }
-            };
-        } catch (MalformedURLException e) {
-            throw new RuntimeException("SettingsLibRobolectricTestRunner failure", e);
-        }
-    }
-
-    private static ResourcePath resourcePath(@NonNull String spec) {
-        try {
-            return new ResourcePath(null, Fs.fromURL(new URL(spec)), null);
-        } catch (MalformedURLException e) {
-            throw new RuntimeException("SettingsLibRobolectricTestRunner failure", e);
-        }
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java
index e70baa1..0ca7791 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TetherUtilTest.java
@@ -32,12 +32,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class TetherUtilTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
index c0b69f2..3f0ba13 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
@@ -36,9 +36,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class TwoTargetPreferenceTest {
 
     private PreferenceViewHolder mViewHolder;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 08a75ab..594d767 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -49,6 +49,7 @@
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
@@ -58,10 +59,8 @@
 import java.util.HashMap;
 import java.util.Map;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(shadows = {
-            UtilsTest.ShadowSecure.class,
-            UtilsTest.ShadowLocationManager.class})
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {UtilsTest.ShadowSecure.class, UtilsTest.ShadowLocationManager.class})
 public class UtilsTest {
     private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
     private static final String PERCENTAGE_0 = "0%";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
index 152d024..44fdaec 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/accessibility/AccessibilityUtilsTest.java
@@ -23,14 +23,13 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class AccessibilityUtilsTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index b307b47..ccec175a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -41,7 +41,6 @@
 import android.os.UserHandle;
 import android.util.IconDrawableFactory;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.Callbacks;
 import com.android.settingslib.applications.ApplicationsState.Session;
@@ -55,6 +54,7 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
@@ -67,7 +67,7 @@
 import java.util.List;
 import java.util.UUID;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowUserManager.class,
         ApplicationsStateRoboTest.ShadowIconDrawableFactory.class,
         ApplicationsStateRoboTest.ShadowPackageManager.class})
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/DefaultAppInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/DefaultAppInfoTest.java
index a92a2dd..50fad70 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/DefaultAppInfoTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/DefaultAppInfoTest.java
@@ -18,8 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -32,16 +32,15 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class DefaultAppInfoTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
index d8c459c..f7fd25b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ServiceListingTest.java
@@ -26,14 +26,13 @@
 import android.content.ComponentName;
 import android.provider.Settings;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class ServiceListingTest {
 
     private static final String TEST_SETTING = "testSetting";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index 29831a8..c555cbe 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -17,8 +17,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -32,7 +32,6 @@
 import android.content.res.Resources;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -40,26 +39,27 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class A2dpProfileTest {
 
     @Mock
-    Context mContext;
+    private Context mContext;
     @Mock
-    CachedBluetoothDeviceManager mDeviceManager;
+    private CachedBluetoothDeviceManager mDeviceManager;
     @Mock
-    LocalBluetoothProfileManager mProfileManager;
+    private LocalBluetoothProfileManager mProfileManager;
     @Mock
-    BluetoothDevice mDevice;
+    private BluetoothDevice mDevice;
     @Mock
-    BluetoothA2dp mBluetoothA2dp;
-    BluetoothProfile.ServiceListener mServiceListener;
+    private BluetoothA2dp mBluetoothA2dp;
+    private BluetoothProfile.ServiceListener mServiceListener;
 
-    A2dpProfile mProfile;
+    private A2dpProfile mProfile;
     private ShadowBluetoothAdapter mShadowBluetoothAdapter;
 
     @Before
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index 274fff8..976445e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -18,18 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothProfile;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -37,11 +33,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class A2dpSinkProfileTest {
 
@@ -52,8 +49,6 @@
     @Mock
     private BluetoothA2dpSink mService;
     @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
     private BluetoothDevice mBluetoothDevice;
     private BluetoothProfile.ServiceListener mServiceListener;
     private A2dpSinkProfile mProfile;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index c147d5e..27b8dfc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -29,20 +29,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BluetoothEventManagerTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 07310bd..0eb6de9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -21,14 +21,14 @@
 import android.graphics.drawable.Drawable;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BluetoothUtilsTest {
 
     @Test
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
index 9c75491..47b1210 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -28,18 +28,17 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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 java.util.Collection;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class CachedBluetoothDeviceManagerTest {
     private final static String DEVICE_NAME_1 = "TestName_1";
     private final static String DEVICE_NAME_2 = "TestName_2";
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 5ceede1..4e5d38a 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
@@ -31,16 +31,15 @@
 import android.content.Context;
 import android.media.AudioManager;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class CachedBluetoothDeviceTest {
     private final static String DEVICE_NAME = "TestName";
     private final static String DEVICE_ALIAS = "TestAlias";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java
index c0a1f0c..9adef82 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java
@@ -11,7 +11,6 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -19,11 +18,12 @@
 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 org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class HeadsetProfileTest {
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index cb1b12d..2b5466c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -29,16 +29,15 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class HearingAidDeviceManagerTest {
     private final static long HISYNCID1 = 10;
     private final static long HISYNCID2 = 11;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 187be0b..69c020d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -18,18 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadsetClient;
 import android.bluetooth.BluetoothProfile;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -37,11 +33,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class HfpClientProfileTest {
 
@@ -52,8 +49,6 @@
     @Mock
     private BluetoothHeadsetClient mService;
     @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
     private BluetoothDevice mBluetoothDevice;
     private BluetoothProfile.ServiceListener mServiceListener;
     private HfpClientProfile mProfile;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
index c91ee22..f38af70 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
@@ -18,18 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHidDevice;
 import android.bluetooth.BluetoothProfile;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -37,11 +33,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class HidDeviceProfileTest {
 
@@ -52,8 +49,6 @@
     @Mock
     private BluetoothHidDevice mService;
     @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
     private BluetoothDevice mBluetoothDevice;
     private BluetoothProfile.ServiceListener mServiceListener;
     private HidDeviceProfile mProfile;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
index a3c3a54..5d5872e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
@@ -37,7 +37,6 @@
 import android.content.Intent;
 import android.os.ParcelUuid;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -45,6 +44,7 @@
 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 org.robolectric.shadow.api.Shadow;
@@ -52,7 +52,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class LocalBluetoothProfileManagerTest {
     private final static long HISYNCID = 10;
@@ -270,13 +270,13 @@
         verify(mCachedBluetoothDevice).refresh();
     }
 
-    private List<Integer> generateList(int[] profile) {
-        if (profile == null) {
+    private List<Integer> generateList(int[] profiles) {
+        if (profiles == null) {
             return null;
         }
-        final List<Integer> profileList = new ArrayList<>(profile.length);
-        for(int i = 0; i < profile.length; i++) {
-            profileList.add(profile[i]);
+        final List<Integer> profileList = new ArrayList<>(profiles.length);
+        for (int profile : profiles) {
+            profileList.add(profile);
         }
         return profileList;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index c4c48a8..6f66709 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -18,18 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothProfile;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -37,11 +33,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class MapClientProfileTest {
 
@@ -52,8 +49,6 @@
     @Mock
     private BluetoothMapClient mService;
     @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
     private BluetoothDevice mBluetoothDevice;
     private BluetoothProfile.ServiceListener mServiceListener;
     private MapClientProfile mProfile;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index e4a444c..b21ec9c3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -18,18 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -37,12 +33,13 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class})
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowBluetoothAdapter.class)
 public class PbapClientProfileTest {
 
     @Mock
@@ -52,8 +49,6 @@
     @Mock
     private BluetoothPbapClient mService;
     @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
     private BluetoothDevice mBluetoothDevice;
     private BluetoothProfile.ServiceListener mServiceListener;
     private PbapClientProfile mProfile;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index 9bb53ee..ec88034 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -18,18 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothSap;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSap;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -37,11 +33,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 import org.robolectric.shadow.api.Shadow;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class SapProfileTest {
 
@@ -52,8 +49,6 @@
     @Mock
     private BluetoothSap mService;
     @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
     private BluetoothDevice mBluetoothDevice;
     private BluetoothProfile.ServiceListener mServiceListener;
     private SapProfile mProfile;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
index 4d7553c..28de191 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
@@ -24,16 +24,15 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class AbstractPreferenceControllerTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
index 4ec6fb2..8a0ae91 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -35,13 +34,14 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class MetricsFeatureProviderTest {
     @Mock
     private LogWriter mLogWriter;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
index 6285fcd..8f51dec 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -17,8 +17,8 @@
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE;
 
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -26,16 +26,15 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class SharedPreferenceLoggerTest {
 
     private static final String TEST_TAG = "tag";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
index b251c09..097db17 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
@@ -17,10 +17,10 @@
 
 import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -35,7 +35,6 @@
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,10 +42,10 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.android.controller.ActivityController;
 
-
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class VisibilityLoggerMixinTest {
 
     @Mock
@@ -139,7 +138,7 @@
 
     private final class TestInstrumentable implements Instrumentable {
 
-        public static final int TEST_METRIC = 12345;
+        private static final int TEST_METRIC = 12345;
 
         @Override
         public int getMetricsCategory() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index 887c1d5..29e37e4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -28,7 +28,6 @@
 
 import androidx.lifecycle.LifecycleOwner;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.events.OnAttach;
 import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
@@ -43,10 +42,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.android.controller.ActivityController;
 import org.robolectric.shadows.androidx.fragment.FragmentController;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class LifecycleTest {
 
     private LifecycleOwner mLifecycleOwner;
@@ -56,7 +56,7 @@
 
         final TestObserver mFragObserver;
 
-        public TestDialogFragment() {
+        private TestDialogFragment() {
             mFragObserver = new TestObserver();
             mLifecycle.addObserver(mFragObserver);
         }
@@ -236,11 +236,11 @@
     }
 
     private static class OptionItemAccepter implements LifecycleObserver, OnOptionsItemSelected {
-        public boolean wasCalled = false;
+        private boolean mWasCalled = false;
 
         @Override
         public boolean onOptionsItemSelected(MenuItem menuItem) {
-            wasCalled = true;
+            mWasCalled = true;
             return false;
         }
     }
@@ -258,14 +258,14 @@
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
 
-        assertThat(accepter.wasCalled).isFalse();
+        assertThat(accepter.mWasCalled).isFalse();
     }
 
     private class OnStartObserver implements LifecycleObserver, OnStart {
 
         private final Lifecycle mLifecycle;
 
-        public OnStartObserver(Lifecycle lifecycle) {
+        private OnStartObserver(Lifecycle lifecycle) {
             mLifecycle = lifecycle;
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DeveloperOptionsPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DeveloperOptionsPreferenceControllerTest.java
index 9dd93b3a..6191a00 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DeveloperOptionsPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DeveloperOptionsPreferenceControllerTest.java
@@ -22,16 +22,15 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class DeveloperOptionsPreferenceControllerTest {
 
     private static final String TEST_KEY = "Test_pref_key";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
index a0fa6b5..3475ff7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java
@@ -18,23 +18,19 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.os.UserManager;
 import android.content.Context;
+import android.os.UserManager;
 import android.provider.Settings;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
-import org.robolectric.shadows.ShadowUserManager;
-import org.robolectric.shadow.api.Shadow;
-
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowUserManager;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class DevelopmentSettingsEnablerTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index d7b23b0..e84a25c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -32,17 +32,16 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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.shadows.ShadowApplication;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class EnableAdbPreferenceControllerTest {
     @Mock(answer = RETURNS_DEEP_STUBS)
     private PreferenceScreen mScreen;
@@ -150,7 +149,7 @@
     }
 
     class ConcreteEnableAdbPreferenceController extends AbstractEnableAdbPreferenceController {
-        public ConcreteEnableAdbPreferenceController(Context context) {
+        private ConcreteEnableAdbPreferenceController(Context context) {
             super(context);
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java
index 2f78899..146be23 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java
@@ -45,16 +45,16 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class LogdSizePreferenceControllerTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
index ed128e0..d5afb4b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java
@@ -29,7 +29,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -37,9 +36,10 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class LogpersistPreferenceControllerTest {
 
     private LifecycleOwner mLifecycleOwner;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropPokerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropPokerTest.java
index 40db478..d1212fc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropPokerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropPokerTest.java
@@ -27,16 +27,15 @@
 import android.os.IBinder;
 import android.os.Parcel;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class SystemPropPokerTest {
 
     @Spy
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java
index 234b4d5..16de5f8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java
@@ -26,7 +26,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -34,11 +33,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BluetoothAddressPreferenceControllerTest {
     @Mock
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
index aee956c..4444e63 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
@@ -30,7 +30,6 @@
 import android.content.IntentFilter;
 import android.os.Handler;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -39,8 +38,9 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class ConnectivityPreferenceControllerTest {
     @Mock
     private Context mContext;
@@ -91,8 +91,7 @@
     private static class ConcreteConnectivityPreferenceController
             extends AbstractConnectivityPreferenceController {
 
-
-        public ConcreteConnectivityPreferenceController(Context context,
+        private ConcreteConnectivityPreferenceController(Context context,
                 Lifecycle lifecycle) {
             super(context, lifecycle);
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java
index 2b490ee..bd223bd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java
@@ -25,12 +25,10 @@
 import android.content.Context;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -38,11 +36,10 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowSubscriptionManager;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class ImsStatusPreferenceControllerTest {
     @Mock
     private Context mContext;
@@ -61,8 +58,9 @@
     }
 
     @Test
-    @Config(shadows = ShadowSubscriptionManager.class)
     public void testIsAvailable() {
+        ShadowSubscriptionManager.setDefaultDataSubscriptionId(1234);
+
         CarrierConfigManager carrierConfigManager = mock(CarrierConfigManager.class);
         doReturn(carrierConfigManager).when(mContext).getSystemService(CarrierConfigManager.class);
 
@@ -92,18 +90,10 @@
                 .that(imsStatusPreferenceController.isAvailable()).isFalse();
     }
 
-    @Implements(SubscriptionManager.class)
-    public static class ShadowSubscriptionManager {
-        @Implementation
-        public static int getDefaultDataSubscriptionId() {
-            return 1234;
-        }
-    }
-
     private static class ConcreteImsStatusPreferenceController
             extends AbstractImsStatusPreferenceController {
 
-        public ConcreteImsStatusPreferenceController(Context context,
+        private ConcreteImsStatusPreferenceController(Context context,
                 Lifecycle lifecycle) {
             super(context, lifecycle);
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
index 1d957c3..76a26d9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
@@ -27,7 +27,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -35,11 +34,12 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class IpAddressPreferenceControllerTest {
     @Mock
     private Context mContext;
@@ -75,8 +75,7 @@
     private static class ConcreteIpAddressPreferenceController extends
             AbstractIpAddressPreferenceController {
 
-        public ConcreteIpAddressPreferenceController(Context context,
-                Lifecycle lifecycle) {
+        private ConcreteIpAddressPreferenceController(Context context, Lifecycle lifecycle) {
             super(context, lifecycle);
         }
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
index dc77400..5b71bdd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
@@ -25,16 +25,15 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class SerialNumberPreferenceControllerTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
index eb77cb6..5252c6c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
@@ -24,17 +24,16 @@
 import android.os.UserManager;
 import android.util.SparseBooleanArray;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class,
                 SimStatusImeiInfoPreferenceControllerTest.ShadowConnectivityManager.class})
 public class SimStatusImeiInfoPreferenceControllerTest {
@@ -106,7 +105,7 @@
 
         private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray();
 
-        public void setNetworkSupported(int networkType, boolean supported) {
+        private void setNetworkSupported(int networkType, boolean supported) {
             mSupportedNetworkTypes.put(networkType, supported);
         }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java
index 2e0348d..f09879b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java
@@ -28,7 +28,6 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -36,9 +35,10 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.shadows.ShadowLooper;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class UptimePreferenceControllerTest {
     @Mock
     private Context mContext;
@@ -92,7 +92,7 @@
 
     private static class ConcreteUptimePreferenceController
             extends AbstractUptimePreferenceController {
-        public ConcreteUptimePreferenceController(Context context,
+        private ConcreteUptimePreferenceController(Context context,
                 Lifecycle lifecycle) {
             super(context, lifecycle);
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
index 359ea77..74e5bf5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
@@ -33,7 +33,6 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -41,13 +40,14 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.Arrays;
 import java.util.List;
 
 @SuppressLint("HardwareIds")
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class WifiMacAddressPreferenceControllerTest {
     @Mock
     private Lifecycle mLifecycle;
@@ -197,7 +197,7 @@
     private static class ConcreteWifiMacAddressPreferenceController
             extends AbstractWifiMacAddressPreferenceController {
 
-        public ConcreteWifiMacAddressPreferenceController(Context context,
+        private ConcreteWifiMacAddressPreferenceController(Context context,
                 Lifecycle lifecycle) {
             super(context, lifecycle);
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java
index ca621ca..c0924d9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/display/BrightnessUtilsTest.java
@@ -20,12 +20,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BrightnessUtilsTest {
 
     private static final int MIN = 1;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 59a3dd6..605c861 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -20,14 +20,13 @@
 
 import android.util.ArraySet;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.Set;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class CategoryKeyTest {
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
index 40e7386..b77670b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
@@ -13,15 +13,14 @@
 import android.os.Bundle;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class TileTest {
 
     private ActivityInfo mActivityInfo;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 362ae4c..bbb4249 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -54,7 +54,6 @@
 import android.util.Pair;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,12 +61,13 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class TileUtilsTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index d0b6dab..2988905 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -33,28 +33,26 @@
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BatterySaverUtilsTest {
-    final int BATTERY_SAVER_THRESHOLD_1 = 15;
-    final int BATTERY_SAVER_THRESHOLD_2 = 20;
+    private static final int BATTERY_SAVER_THRESHOLD_1 = 15;
+    private static final int BATTERY_SAVER_THRESHOLD_2 = 20;
 
     @Mock
-    Context mMockContext;
+    private Context mMockContext;
 
     @Mock
-    ContentResolver mMockResolver;
+    private ContentResolver mMockResolver;
 
     @Mock
-    PowerManager mMockPowerManager;
+    private PowerManager mMockPowerManager;
 
     @Before
     public void setUp() throws Exception {
@@ -66,11 +64,11 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_firstCall_needWarning() throws Exception {
+    public void testSetPowerSaveMode_enable_firstCall_needWarning() {
         Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertEquals(false, BatterySaverUtils.setPowerSaveMode(mMockContext, true, true));
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
 
         verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(0)).setPowerSaveMode(anyBoolean());
@@ -83,11 +81,11 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_secondCall_needWarning() throws Exception {
+    public void testSetPowerSaveMode_enable_secondCall_needWarning() {
         Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); // Already acked.
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, true, true));
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(true));
@@ -97,11 +95,11 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_thridCall_needWarning() throws Exception {
+    public void testSetPowerSaveMode_enable_thridCall_needWarning() {
         Secure.putInt(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1); // Already acked.
         Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
 
-        assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, true, true));
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(true));
@@ -111,11 +109,11 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_enable_firstCall_noWarning() throws Exception {
+    public void testSetPowerSaveMode_enable_firstCall_noWarning() {
         Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
-        assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, true, false));
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(true));
@@ -125,12 +123,12 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_disable_firstCall_noWarning() throws Exception {
+    public void testSetPowerSaveMode_disable_firstCall_noWarning() {
         Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, false, false));
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(false));
@@ -141,12 +139,12 @@
     }
 
     @Test
-    public void testSetPowerSaveMode_disable_firstCall_needWarning() throws Exception {
+    public void testSetPowerSaveMode_disable_firstCall_needWarning() {
         Secure.putString(mMockResolver, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null");
         Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
 
         // When disabling, needFirstTimeWarning doesn't matter.
-        assertEquals(true, BatterySaverUtils.setPowerSaveMode(mMockContext, false, true));
+        assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
 
         verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
         verify(mMockPowerManager, times(1)).setPowerSaveMode(eq(false));
@@ -157,7 +155,7 @@
     }
 
     @Test
-    public void testEnsureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() throws Exception {
+    public void testEnsureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() {
         Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
 
         BatterySaverUtils.ensureAutoBatterySaver(mMockContext, BATTERY_SAVER_THRESHOLD_1);
@@ -172,7 +170,7 @@
     }
 
     @Test
-    public void testSetAutoBatterySaverTriggerLevel_setSuppressSuggestion() throws Exception {
+    public void testSetAutoBatterySaverTriggerLevel_setSuppressSuggestion() {
         Global.putString(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, "null");
         Secure.putString(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, "null");
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
index 9b1fe5f..bbf807d2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
@@ -31,7 +31,6 @@
 import android.content.pm.PackageManager;
 import android.os.IDeviceIdleController;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowDefaultDialerManager;
 import com.android.settingslib.testutils.shadow.ShadowSmsApplication;
 
@@ -40,12 +39,13 @@
 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 org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowPackageManager;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowDefaultDialerManager.class, ShadowSmsApplication.class})
 public class PowerWhitelistBackendTest {
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
index 49dde0e..35743c2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
@@ -16,8 +16,8 @@
 
 package com.android.settingslib.graph;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -25,17 +25,16 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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.util.ReflectionHelpers;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BatteryMeterDrawableBaseTest {
     private static final int CRITICAL_LEVEL = 5;
     private static final int PADDING = 5;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
index 5dbb5ca..1b350cc 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
@@ -22,14 +22,14 @@
 import android.graphics.drawable.VectorDrawable;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class BluetoothDeviceLayerDrawableTest {
     private static final int RES_ID = R.drawable.ic_bt_cellphone;
     private static final int BATTERY_LEVEL = 15;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
index fa64afe..b930aa6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
@@ -25,26 +25,21 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class InputMethodAndSubtypeUtilCompatTest {
 
     private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();
 
     private static HashSet<String> asHashSet(String... strings) {
-        HashSet<String> hashSet = new HashSet<>();
-        for (String s : strings) {
-            hashSet.add(s);
-        }
-        return hashSet;
+        return new HashSet<>(Arrays.asList(strings));
     }
 
     @Test
@@ -105,7 +100,6 @@
                 "ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
                 .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
                         "ime1", asHashSet("subtype1", "subtype2"));
-
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
index 03ab261..84606b4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
@@ -25,26 +25,21 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class InputMethodAndSubtypeUtilTest {
 
     private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();
 
     private static HashSet<String> asHashSet(String... strings) {
-        HashSet<String> hashSet = new HashSet<>();
-        for (String s : strings) {
-            hashSet.add(s);
-        }
-        return hashSet;
+        return new HashSet<>(Arrays.asList(strings));
     }
 
     @Test
@@ -103,7 +98,6 @@
                 "ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
                 .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
                         "ime1", asHashSet("subtype1", "subtype2"));
-
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
index b00476b2..4b5e909 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -18,10 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.ByteArrayInputStream;
@@ -32,7 +31,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class LicenseHtmlGeneratorFromXmlTest {
     private static final String VALILD_XML_STRING =
             "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
@@ -92,8 +91,8 @@
 
     @Test
     public void testParseValidXmlStream() throws XmlPullParserException, IOException {
-        Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
-        Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+        Map<String, String> fileNameToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
 
         LicenseHtmlGeneratorFromXml.parse(
                 new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())),
@@ -107,8 +106,8 @@
 
     @Test(expected = XmlPullParserException.class)
     public void testParseInvalidXmlStream() throws XmlPullParserException, IOException {
-        Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
-        Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+        Map<String, String> fileNameToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
 
         LicenseHtmlGeneratorFromXml.parse(
                 new InputStreamReader(new ByteArrayInputStream(INVALILD_XML_STRING.getBytes())),
@@ -117,8 +116,8 @@
 
     @Test
     public void testGenerateHtml() {
-        Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
-        Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+        Map<String, String> fileNameToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
 
         fileNameToContentIdMap.put("/file0", "0");
         fileNameToContentIdMap.put("/file1", "0");
@@ -132,8 +131,8 @@
 
     @Test
     public void testGenerateHtmlWithCustomHeading() {
-        Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
-        Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+        Map<String, String> fileNameToContentIdMap = new HashMap<>();
+        Map<String, String> contentIdToFileContentMap = new HashMap<>();
 
         fileNameToContentIdMap.put("/file0", "0");
         fileNameToContentIdMap.put("/file1", "0");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java
index c90de5f..e82bc06 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java
@@ -20,14 +20,13 @@
 
 import android.content.Context;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.After;
 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.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
@@ -37,7 +36,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = LicenseHtmlLoaderCompatTest.ShadowLicenseHtmlLoaderCompat.class)
 public class LicenseHtmlLoaderCompatTest {
 
@@ -58,7 +57,7 @@
 
     @Test
     public void testLoadInBackground() {
-        ArrayList<File> xmlFiles = new ArrayList();
+        ArrayList<File> xmlFiles = new ArrayList<>();
         xmlFiles.add(new File("test.xml"));
         File cachedHtmlFile = new File("test.html");
 
@@ -69,7 +68,7 @@
 
     @Test
     public void testLoadInBackgroundWithNoVaildXmlFiles() {
-        ArrayList<File> xmlFiles = new ArrayList();
+        ArrayList<File> xmlFiles = new ArrayList<>();
         File cachedHtmlFile = new File("test.html");
 
         setupFakeData(xmlFiles, cachedHtmlFile, true, true);
@@ -79,7 +78,7 @@
 
     @Test
     public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() {
-        ArrayList<File> xmlFiles = new ArrayList();
+        ArrayList<File> xmlFiles = new ArrayList<>();
         xmlFiles.add(new File("test.xml"));
         File cachedHtmlFile = new File("test.html");
 
@@ -90,7 +89,7 @@
 
     @Test
     public void testLoadInBackgroundWithGenerateHtmlFileFailed() {
-        ArrayList<File> xmlFiles = new ArrayList();
+        ArrayList<File> xmlFiles = new ArrayList<>();
         xmlFiles.add(new File("test.xml"));
         File cachedHtmlFile = new File("test.html");
 
@@ -112,10 +111,10 @@
     @Implements(LicenseHtmlLoaderCompat.class)
     public static class ShadowLicenseHtmlLoaderCompat {
 
-        public static List<File> sValidXmlFiles;
-        public static File sCachedHtmlFile;
-        public static boolean sIsCachedHtmlFileOutdated;
-        public static boolean sGenerateHtmlFileSucceeded;
+        private static List<File> sValidXmlFiles;
+        private static File sCachedHtmlFile;
+        private static boolean sIsCachedHtmlFileOutdated;
+        private static boolean sGenerateHtmlFileSucceeded;
 
         @Resetter
         public static void reset() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/InjectedSettingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/InjectedSettingTest.java
index c29481f..8c2e899 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/InjectedSettingTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/InjectedSettingTest.java
@@ -18,12 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public final class InjectedSettingTest {
 
     private static final String TEST_STRING = "test";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
index 9c168f7..08d5367 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
@@ -2,7 +2,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.isA;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
@@ -17,20 +17,19 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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 java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class RecentLocationAppsTest {
 
     private static final int TEST_UID = 1234;
@@ -56,8 +55,6 @@
     private int mTestUserId;
     private RecentLocationApps mRecentLocationApps;
 
-
-
     @Before
     public void setUp() throws NameNotFoundException {
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
index 50044f2..72ed5e1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageControllerTest.java
@@ -42,16 +42,15 @@
 import android.text.format.DateUtils;
 import android.util.FeatureFlagUtils;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class DataUsageControllerTest {
 
     private static final String SUB_ID = "Test Subscriber";
@@ -85,7 +84,6 @@
         doReturn(null).when(mController).getSession();
 
         assertThat(mController.getHistoricalUsageLevel(null /* template */)).isEqualTo(-1L);
-
     }
 
     @Test
@@ -95,7 +93,6 @@
 
         assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
                 .isEqualTo(0L);
-
     }
 
     @Test
@@ -113,7 +110,6 @@
 
         assertThat(mController.getHistoricalUsageLevel(NetworkTemplate.buildTemplateWifiWildcard()))
                 .isEqualTo(receivedBytes + transmittedBytes);
-
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java
index 0a03631..011f234 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleChartDataLoaderTest.java
@@ -27,15 +27,14 @@
 import android.os.RemoteException;
 import android.text.format.DateUtils;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class NetworkCycleChartDataLoaderTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
index 2314f27..d915963 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataForUidLoaderTest.java
@@ -19,7 +19,7 @@
 import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
 
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -33,15 +33,14 @@
 import android.net.NetworkPolicyManager;
 import android.text.format.DateUtils;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class NetworkCycleDataForUidLoaderTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index 9d60a97..2d8ea12 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -16,8 +16,8 @@
 
 package com.android.settingslib.net;
 
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.nullable;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -37,20 +37,19 @@
 import android.text.format.DateUtils;
 import android.util.Range;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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.util.ReflectionHelpers;
 
 import java.time.ZonedDateTime;
 import java.util.Iterator;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class NetworkCycleDataLoaderTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
index 89c319a..59d5674 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
@@ -16,11 +16,10 @@
 
 package com.android.settingslib.notification;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -38,16 +37,15 @@
 import android.service.notification.Condition;
 import android.view.LayoutInflater;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class EnableZenModeDialogTest {
     private EnableZenModeDialog mController;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java
index 8147656..437c0d4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/ZenDurationDialogTest.java
@@ -25,27 +25,23 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.provider.Settings;
-import android.service.notification.Condition;
 import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.appcompat.app.AlertDialog;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class ZenDurationDialogTest {
     private ZenDurationDialog mController;
 
     private Context mContext;
     private LayoutInflater mLayoutInflater;
-    private Condition mCountdownCondition;
-    private Condition mAlarmCondition;
     private ContentResolver mContentResolver;
     private AlertDialog.Builder mBuilder;
 
@@ -102,7 +98,6 @@
                 ZenDurationDialog.ALWAYS_ASK_CONDITION_INDEX).rb.isChecked());
     }
 
-
     @Test
     public void testChooseAlwaysPromptSetting() {
         Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_DURATION,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java
index 449451a..ffaa7443 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java
@@ -31,7 +31,6 @@
 import androidx.lifecycle.LifecycleOwner;
 import androidx.loader.app.LoaderManager;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.After;
@@ -40,10 +39,11 @@
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = ShadowSuggestionController.class)
 public class SuggestionControllerMixinCompatTest {
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java
index aac582f..4dc80f4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java
@@ -31,7 +31,6 @@
 
 import androidx.lifecycle.LifecycleOwner;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.After;
@@ -40,10 +39,11 @@
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = ShadowSuggestionController.class)
 public class SuggestionControllerMixinTest {
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
index f4afdb1..3e91641 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowDefaultDialerManager.java
@@ -26,19 +26,19 @@
 @Implements(DefaultDialerManager.class)
 public class ShadowDefaultDialerManager {
 
-    private static String sDefaultDailer;
+    private static String sDefaultDialer;
 
     @Resetter
     public void reset() {
-        sDefaultDailer = null;
+        sDefaultDialer = null;
     }
 
     @Implementation
     public static String getDefaultDialerApplication(Context context) {
-        return sDefaultDailer;
+        return sDefaultDialer;
     }
 
     public static void setDefaultDialerApplication(String dialer) {
-        sDefaultDailer = dialer;
+        sDefaultDialer = dialer;
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
index 4705cd2..9a169d2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/UserManagerHelperRoboTest.java
@@ -27,7 +27,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.testutils.shadow.ShadowActivityManager;
 
 import org.junit.After;
@@ -36,6 +35,7 @@
 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 org.robolectric.annotation.Implementation;
@@ -45,7 +45,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 @Config(shadows = { ShadowActivityManager.class, UserManagerHelperRoboTest.ShadowUserHandle.class})
 public class UserManagerHelperRoboTest {
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/IconCacheTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/IconCacheTest.java
index 645dfa1..026ad47 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/IconCacheTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/IconCacheTest.java
@@ -30,14 +30,13 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class IconCacheTest {
     private Icon mIcon;
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index 6a9579b..7ef31df 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -22,31 +22,30 @@
 
 import android.content.Context;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
 import java.time.Duration;
 import java.util.regex.Pattern;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class PowerUtilTest {
-    public static final String TEST_BATTERY_LEVEL_10 = "10%";
-    public static final long SEVENTEEN_MIN_MILLIS = Duration.ofMinutes(17).toMillis();
-    public static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis();
-    public static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis();
-    public static final long THREE_DAYS_MILLIS = Duration.ofDays(3).toMillis();
-    public static final long THIRTY_HOURS_MILLIS = Duration.ofHours(30).toMillis();
-    public static final String NORMAL_CASE_EXPECTED_PREFIX = "Should last until about";
-    public static final String ENHANCED_SUFFIX = " based on your usage";
+    private static final String TEST_BATTERY_LEVEL_10 = "10%";
+    private static final long SEVENTEEN_MIN_MILLIS = Duration.ofMinutes(17).toMillis();
+    private static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis();
+    private static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis();
+    private static final long THREE_DAYS_MILLIS = Duration.ofDays(3).toMillis();
+    private static final long THIRTY_HOURS_MILLIS = Duration.ofHours(30).toMillis();
+    private static final String NORMAL_CASE_EXPECTED_PREFIX = "Should last until about";
+    private static final String ENHANCED_SUFFIX = " based on your usage";
     // matches a time (ex: '1:15 PM', '2 AM', '23:00')
-    public static final String TIME_OF_DAY_REGEX = " (\\d)+:?(\\d)* ((AM)*)|((PM)*)";
+    private static final String TIME_OF_DAY_REGEX = " (\\d)+:?(\\d)* ((AM)*)|((PM)*)";
     // matches a percentage with parenthesis (ex: '(10%)')
-    public static final String PERCENTAGE_REGEX = " \\(\\d?\\d%\\)";
+    private static final String PERCENTAGE_REGEX = " \\(\\d?\\d%\\)";
 
     private Context mContext;
 
@@ -108,7 +107,6 @@
                         + "(" + PERCENTAGE_REGEX + "){0}")); // no percentage
     }
 
-
     @Test
     public void testGetBatteryRemainingStringFormatted_lessThanSevenMinutes_usesCorrectString() {
         String info = PowerUtil.getBatteryRemainingStringFormatted(mContext,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
index e4bbbcb..8fbbfbb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/StringUtilTest.java
@@ -25,14 +25,13 @@
 import android.text.format.DateUtils;
 import android.text.style.TtsSpan;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class StringUtilTest {
     private Context mContext;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
index 1e066b1..26db124 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/ThreadUtilsTest.java
@@ -15,28 +15,22 @@
  */
 package com.android.settingslib.utils;
 
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.shadows.ShadowLooper;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class ThreadUtilsTest {
 
     @Test
     public void testMainThread() throws InterruptedException {
         assertThat(ThreadUtils.isMainThread()).isTrue();
-        Thread background = new Thread(new Runnable() {
-            public void run() {
-                assertThat(ThreadUtils.isMainThread()).isFalse();
-            }
-        });
+        Thread background = new Thread(() -> assertThat(ThreadUtils.isMainThread()).isFalse());
         background.start();
         background.join();
     }
@@ -44,13 +38,11 @@
     @Test
     public void testEnsureMainThread() throws InterruptedException {
         ThreadUtils.ensureMainThread();
-        Thread background = new Thread(new Runnable() {
-            public void run() {
-                try {
-                    ThreadUtils.ensureMainThread();
-                    fail("Should not pass ensureMainThread in a background thread");
-                } catch (RuntimeException e) {
-                }
+        Thread background = new Thread(() -> {
+            try {
+                ThreadUtils.ensureMainThread();
+                fail("Should not pass ensureMainThread in a background thread");
+            } catch (RuntimeException expected) {
             }
         });
         background.start();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
index a00f12d..d41d511 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AnimatedImageViewTest.java
@@ -22,14 +22,13 @@
 import android.graphics.drawable.AnimatedRotateDrawable;
 import android.view.View;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class AnimatedImageViewTest {
     private AnimatedImageView mAnimatedImageView;
 
@@ -47,5 +46,4 @@
         AnimatedRotateDrawable drawable = (AnimatedRotateDrawable) mAnimatedImageView.getDrawable();
         assertThat(drawable.isRunning()).isTrue();
     }
-
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
index e030005..601da051 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
@@ -18,7 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -29,7 +29,6 @@
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -37,9 +36,10 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class FooterPreferenceMixinCompatTest {
 
     @Mock
@@ -97,5 +97,4 @@
         verify(mScreen).removePreference(any(FooterPreference.class));
         verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
     }
-
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
index 8817ff7..7ae5d2d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
@@ -18,7 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -29,7 +29,6 @@
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
 import org.junit.Before;
@@ -37,10 +36,10 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowApplication;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class FooterPreferenceMixinTest {
 
     @Mock
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index e0eceb4..0d2399e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -26,14 +26,14 @@
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class FooterPreferenceTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LayoutPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LayoutPreferenceTest.java
index 427a611..99261a3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LayoutPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LayoutPreferenceTest.java
@@ -27,14 +27,13 @@
 import androidx.preference.Preference.OnPreferenceClickListener;
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class LayoutPreferenceTest {
 
     private LayoutPreference mPreference;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java
index 10c9dfb..da97cc8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/apppreference/AppPreferenceTest.java
@@ -23,14 +23,13 @@
 
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class AppPreferenceTest {
 
     private Context mContext;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 86443bd..c5cbea7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -25,16 +25,15 @@
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 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;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class AccessPointPreferenceTest {
 
     private Context mContext = RuntimeEnvironment.application;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/TimestampedScoredNetworkTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/TimestampedScoredNetworkTest.java
index f0e8c66..b059df1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/TimestampedScoredNetworkTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/TimestampedScoredNetworkTest.java
@@ -22,15 +22,14 @@
 import android.net.WifiKey;
 import android.os.Parcel;
 
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
 
 import java.util.Date;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class TimestampedScoredNetworkTest {
   private TimestampedScoredNetwork impl;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 07c50fd..89960cb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -37,19 +37,19 @@
 import android.util.ArraySet;
 
 import com.android.settingslib.R;
-import com.android.settingslib.SettingsLibRobolectricTestRunner;
 
 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 java.util.ArrayList;
 import java.util.Set;
 
-@RunWith(SettingsLibRobolectricTestRunner.class)
+@RunWith(RobolectricTestRunner.class)
 public class WifiUtilsTest {
     private static final String TEST_SSID = "\"test_ssid\"";
     private static final String TEST_BSSID = "00:00:00:00:00:00";
@@ -79,7 +79,7 @@
         Bundle bundle = new Bundle();
         ArrayList<ScanResult> scanResults = buildScanResultCache();
         bundle.putParcelableArray(AccessPoint.KEY_SCANRESULTS,
-                                  scanResults.toArray(new Parcelable[scanResults.size()]));
+                                  scanResults.toArray(new Parcelable[0]));
         AccessPoint ap = new AccessPoint(mContext, bundle);
 
         when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 9a5a5b8..be504ef 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -23,6 +23,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
 
 import java.io.PrintWriter;
@@ -80,11 +81,12 @@
         if (isAmbientMode != mIsAmbientMode) {
             mIsAmbientMode = isAmbientMode;
             try {
+                long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
                 if (DEBUG) {
                     Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
-                            + ", animated: " + animated);
+                            + ", animationDuration: " + duration);
                 }
-                mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, animated);
+                mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
             } catch (RemoteException e) {
                 // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
                 Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 8b434a5..496aa0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -323,7 +323,9 @@
             post(new Runnable() {
                 @Override
                 public void run() {
-                    handleShowingDetail(detail, x, y, false /* toggleQs */);
+                    if (isAttachedToWindow()) {
+                        handleShowingDetail(detail, x, y, false /* toggleQs */);
+                    }
                 }
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 0638998..3a96595d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -198,7 +198,8 @@
         mIcon.setIcon(state, allowAnimations);
         setContentDescription(state.contentDescription);
 
-        mAccessibilityClass = state.expandedAccessibilityClassName;
+        mAccessibilityClass =
+                state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName;
         if (state instanceof QSTile.BooleanState) {
             boolean newState = ((BooleanState) state).value;
             if (mTileState != newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index 314a31d..0a2e04f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -250,23 +250,24 @@
                     otherChild = null;
                 }
             }
-            if (otherChild == null) {
+            if (otherChild == null && previousTranslation < 0) {
+                // Let's fade out as we approach the top of the screen. We can only do this if
+                // we're actually moving up
                 float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
                 transformationAmount = distanceToTop / child.getHeight();
                 transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
-                if (to) {
-                    transformationAmount = 1.0f - transformationAmount;
-                }
             }
             transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
                     useLinearTransformation);
-            if (transformationAmount == 0.0f
-                    && otherGroup.getIsolatedMessage() == otherChild) {
+            boolean otherIsIsolated = otherGroup.getIsolatedMessage() == otherChild;
+            if (transformationAmount == 0.0f && otherIsIsolated) {
                 ownGroup.setTransformingImages(true);
             }
             if (otherChild == null) {
                 child.setTranslationY(previousTranslation);
                 setClippingDeactivated(child, true);
+            } else if (ownGroup.getIsolatedMessage() == child || otherIsIsolated) {
+                // We don't want to add any translation for the image that is transforming
             } else if (to) {
                 float totalTranslation = child.getTop() + ownGroup.getTop()
                         - otherChild.getTop() - otherChild.getTop();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 7970f16..1616b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -176,9 +176,12 @@
             }
 
             // Check if the notification is displaying the menu, if so slide notification back
-            if (row.getProvider() != null && row.getProvider().isMenuVisible()) {
+            if (isMenuVisible(row)) {
                 row.animateTranslateNotification(0);
                 return;
+            } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
+                row.getNotificationParent().animateTranslateNotification(0);
+                return;
             }
 
             // Mark notification for one frame.
@@ -193,6 +196,10 @@
             mCallback.onNotificationClicked(sbn, row);
         }
 
+        private boolean isMenuVisible(ExpandableNotificationRow row) {
+            return row.getProvider() != null && row.getProvider().isMenuVisible();
+        }
+
         public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
             Notification notification = sbn.getNotification();
             if (notification.contentIntent != null || notification.fullScreenIntent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0cd431f..d4d45ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2067,6 +2067,8 @@
 
     private void setChildIsExpanding(boolean isExpanding) {
         mChildIsExpanding = isExpanding;
+        updateClipping();
+        invalidate();
     }
 
     @Override
@@ -2968,7 +2970,7 @@
                 return true;
             }
         } else if (child == mChildrenContainer) {
-            if (!mChildIsExpanding && (isClippingNeeded() || !hasNoRounding())) {
+            if (isClippingNeeded() || !hasNoRounding()) {
                 return true;
             }
         } else if (child instanceof NotificationGuts) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index a7aed5f..0efb130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -115,12 +115,14 @@
         if (!mCustomOutline) {
             int translation = mShouldTranslateContents && !ignoreTranslation
                     ? (int) getTranslation() : 0;
-            left = Math.max(translation, 0);
+            int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
+            left = Math.max(translation, 0) - halfExtraWidth;
             top = mClipTopAmount + mBackgroundTop;
-            right = getWidth() + Math.min(translation, 0);
+            right = getWidth() + halfExtraWidth + Math.min(translation, 0);
             // If the top is rounded we want the bottom to be at most at the top roundness, in order
             // to avoid the shadow changing when scrolling up.
-            bottom = Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness));
+            bottom = Math.max(mMinimumHeightForClipping,
+                    Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness)));
         } else {
             left = mOutlineRect.left;
             top = mOutlineRect.top;
@@ -219,10 +221,12 @@
 
     public void setExtraWidthForClipping(float extraWidthForClipping) {
         mExtraWidthForClipping = extraWidthForClipping;
+        invalidate();
     }
 
     public void setMinimumHeightForClipping(int minimumHeightForClipping) {
         mMinimumHeightForClipping = minimumHeightForClipping;
+        invalidate();
     }
 
     @Override
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 2fc3adc..408ab42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -721,7 +721,7 @@
         IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
         try {
-            wallpaperManager.setInAmbientMode(false /* ambientMode */, false /* animated */);
+            wallpaperManager.setInAmbientMode(false /* ambientMode */, 0L /* duration */);
         } catch (RemoteException e) {
             // Just pass, nothing critical.
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
index 6ac4462..ec2319d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
@@ -16,9 +16,8 @@
 
 package com.android.systemui.doze;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -27,8 +26,8 @@
 import android.os.RemoteException;
 import android.support.test.filters.SmallTest;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
 
 import org.junit.Before;
@@ -59,14 +58,14 @@
 
         mDozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
                 DozeMachine.State.DOZE_AOD);
-        verify(mIWallpaperManager).setInAmbientMode(eq(true), anyBoolean());
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), anyLong());
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
-        verify(mIWallpaperManager).setInAmbientMode(eq(false), anyBoolean());
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), anyLong());
 
         // Make sure we're sending false when AoD is off
         reset(mDozeParameters);
         mDozeWallpaperState.transitionTo(DozeMachine.State.FINISH, DozeMachine.State.DOZE_AOD);
-        verify(mIWallpaperManager).setInAmbientMode(eq(false), anyBoolean());
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), anyLong());
     }
 
     @Test
@@ -78,10 +77,12 @@
 
         mDozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
                 DozeMachine.State.DOZE_AOD);
-        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(true));
+        verify(mIWallpaperManager).setInAmbientMode(eq(true),
+                eq((long) StackStateAnimator.ANIMATION_DURATION_WAKEUP));
 
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
-        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(true));
+        verify(mIWallpaperManager).setInAmbientMode(eq(false),
+                eq((long) StackStateAnimator.ANIMATION_DURATION_WAKEUP));
     }
 
     @Test
@@ -93,24 +94,24 @@
 
         mDozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED,
                 DozeMachine.State.DOZE_AOD);
-        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(false));
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(0L));
 
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH);
-        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(false));
+        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(0L));
     }
 
     @Test
     public void testTransitionTo_requestPulseIsAmbientMode() throws RemoteException {
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE,
                 DozeMachine.State.DOZE_REQUEST_PULSE);
-        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(false));
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(0L));
     }
 
     @Test
     public void testTransitionTo_pulseIsAmbientMode() throws RemoteException {
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
                 DozeMachine.State.DOZE_PULSING);
-        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(false));
+        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(0L));
     }
 
     @Test
@@ -120,6 +121,7 @@
         reset(mIWallpaperManager);
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_PULSING,
                 DozeMachine.State.FINISH);
-        verify(mIWallpaperManager).setInAmbientMode(eq(false), eq(true));
+        verify(mIWallpaperManager).setInAmbientMode(eq(false),
+                eq((long) StackStateAnimator.ANIMATION_DURATION_WAKEUP));
     }
 }
diff --git a/services/backup/OWNERS b/services/backup/OWNERS
index 645723e..d1dbbff 100644
--- a/services/backup/OWNERS
+++ b/services/backup/OWNERS
@@ -1,7 +1,5 @@
 anniemeng@google.com
-artikz@google.com
 brufino@google.com
 bryanmawhinney@google.com
 ctate@google.com
 jorlow@google.com
-mkarpinski@google.com
diff --git a/services/core/java/com/android/server/locksettings/SP800Derive.java b/services/core/java/com/android/server/locksettings/SP800Derive.java
new file mode 100644
index 0000000..77561fc
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/SP800Derive.java
@@ -0,0 +1,82 @@
+/*
+ * 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.server.locksettings;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Implementation of NIST SP800-108
+ * "Recommendation for Key Derivation Using Pseudorandom Functions"
+ * Hardcoded:
+ * [PRF=HMAC_SHA256]
+ * [CTRLOCATION=BEFORE_FIXED]
+ * [RLEN=32_BITS]
+ * L = 256
+ * L suffix: 32 bits
+ */
+class SP800Derive {
+    private final byte[] mKeyBytes;
+
+    SP800Derive(byte[] keyBytes) {
+        mKeyBytes = keyBytes;
+    }
+
+    private Mac getMac() {
+        try {
+            final Mac m = Mac.getInstance("HmacSHA256");
+            m.init(new SecretKeySpec(mKeyBytes, m.getAlgorithm()));
+            return m;
+        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void update32(Mac m, int v) {
+        m.update(ByteBuffer.allocate(Integer.BYTES).putInt(v).array());
+    }
+
+    /**
+     *  Generate output from a single, fixed input.
+     */
+    public byte[] fixedInput(byte[] fixedInput) {
+        final Mac m = getMac();
+        update32(m, 1); // Hardwired counter value
+        m.update(fixedInput);
+        return m.doFinal();
+    }
+
+    /**
+     * Generate output from a label and context. We add a length field at the end of the context to
+     * disambiguate it from the length even in the presence of zero bytes.
+     */
+    public byte[] withContext(byte[] label, byte[] context) {
+        final Mac m = getMac();
+        // Hardwired counter value: 1
+        update32(m, 1); // Hardwired counter value
+        m.update(label);
+        m.update((byte) 0);
+        m.update(context);
+        update32(m, context.length * 8); // Disambiguate context
+        update32(m, 256); // Hardwired output length
+        return m.doFinal();
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 596daeb..d32c299 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -26,9 +26,9 @@
 import android.hardware.weaver.V1_0.WeaverReadResponse;
 import android.hardware.weaver.V1_0.WeaverReadStatus;
 import android.hardware.weaver.V1_0.WeaverStatus;
-import android.security.GateKeeper;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.security.GateKeeper;
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.util.ArrayMap;
@@ -102,7 +102,8 @@
     private static final int INVALID_WEAVER_SLOT = -1;
 
     private static final byte SYNTHETIC_PASSWORD_VERSION_V1 = 1;
-    private static final byte SYNTHETIC_PASSWORD_VERSION = 2;
+    private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2;
+    private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3;
     private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
     private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
 
@@ -128,6 +129,8 @@
     private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
     private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
     private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
+    private static final byte[] PERSONALISATION_CONTEXT =
+        "android-synthetic-password-personalization-context".getBytes();
 
     static class AuthenticationResult {
         public AuthenticationToken authToken;
@@ -136,6 +139,7 @@
     }
 
     static class AuthenticationToken {
+        private final byte mVersion;
         /*
          * Here is the relationship between all three fields:
          * P0 and P1 are two randomly-generated blocks. P1 is stored on disk but P0 is not.
@@ -146,29 +150,38 @@
         private @Nullable byte[] P1;
         private @NonNull String syntheticPassword;
 
+        AuthenticationToken(byte version) {
+            mVersion = version;
+        }
+
+        private byte[] derivePassword(byte[] personalization) {
+            if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
+                return (new SP800Derive(syntheticPassword.getBytes()))
+                    .withContext(personalization, PERSONALISATION_CONTEXT);
+            } else {
+                return SyntheticPasswordCrypto.personalisedHash(personalization,
+                        syntheticPassword.getBytes());
+            }
+        }
+
         public String deriveKeyStorePassword() {
-            return bytesToHex(SyntheticPasswordCrypto.personalisedHash(
-                    PERSONALIZATION_KEY_STORE_PASSWORD, syntheticPassword.getBytes()));
+            return bytesToHex(derivePassword(PERSONALIZATION_KEY_STORE_PASSWORD));
         }
 
         public byte[] deriveGkPassword() {
-            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_SP_GK_AUTH,
-                    syntheticPassword.getBytes());
+            return derivePassword(PERSONALIZATION_SP_GK_AUTH);
         }
 
         public byte[] deriveDiskEncryptionKey() {
-            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_FBE_KEY,
-                    syntheticPassword.getBytes());
+            return derivePassword(PERSONALIZATION_FBE_KEY);
         }
 
         public byte[] deriveVendorAuthSecret() {
-            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_AUTHSECRET_KEY,
-                    syntheticPassword.getBytes());
+            return derivePassword(PERSONALIZATION_AUTHSECRET_KEY);
         }
 
         public byte[] derivePasswordHashFactor() {
-            return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_PASSWORD_HASH,
-                    syntheticPassword.getBytes());
+            return derivePassword(PERSONALIZATION_PASSWORD_HASH);
         }
 
         private void initialize(byte[] P0, byte[] P1) {
@@ -185,7 +198,7 @@
         }
 
         protected static AuthenticationToken create() {
-            AuthenticationToken result = new AuthenticationToken();
+            AuthenticationToken result = new AuthenticationToken(SYNTHETIC_PASSWORD_VERSION_V3);
             result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH),
                     secureRandom(SYNTHETIC_PASSWORD_LENGTH));
             return result;
@@ -802,7 +815,16 @@
         }
         byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid);
         byte[] blob = new byte[content.length + 1 + 1];
-        blob[0] = SYNTHETIC_PASSWORD_VERSION;
+        /*
+         * We can upgrade from v1 to v2 because that's just a change in the way that
+         * the SP is stored. However, we can't upgrade to v3 because that is a change
+         * in the way that passwords are derived from the SP.
+         */
+        if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
+            blob[0] = SYNTHETIC_PASSWORD_VERSION_V3;
+        } else {
+            blob[0] = SYNTHETIC_PASSWORD_VERSION_V2;
+        }
         blob[1] = type;
         System.arraycopy(content, 0, blob, 2, content.length);
         saveState(SP_BLOB_NAME, blob, handle, userId);
@@ -940,7 +962,9 @@
             return null;
         }
         final byte version = blob[0];
-        if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
+        if (version != SYNTHETIC_PASSWORD_VERSION_V3
+                && version != SYNTHETIC_PASSWORD_VERSION_V2
+                && version != SYNTHETIC_PASSWORD_VERSION_V1) {
             throw new RuntimeException("Unknown blob version");
         }
         if (blob[1] != type) {
@@ -958,7 +982,7 @@
             Log.e(TAG, "Fail to decrypt SP for user " + userId);
             return null;
         }
-        AuthenticationToken result = new AuthenticationToken();
+        AuthenticationToken result = new AuthenticationToken(version);
         if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
             if (!loadEscrowData(result, userId)) {
                 Log.e(TAG, "User is not escrowable: " + userId);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 7750c37..0d6dadf 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -554,7 +554,7 @@
 
     final Handler mHandler;
     @VisibleForTesting
-    public final Handler mUidEventHandler;
+    final Handler mUidEventHandler;
 
     private final ServiceThread mUidEventThread;
 
@@ -1465,7 +1465,7 @@
     }
 
     @VisibleForTesting
-    public void updateNetworks() throws InterruptedException {
+    void updateNetworks() throws InterruptedException {
         updateNetworksInternal();
         final CountDownLatch latch = new CountDownLatch(1);
         mHandler.post(() -> {
@@ -1510,7 +1510,7 @@
      * @return cycleDay to use in the mobile NetworkPolicy.
      */
     @VisibleForTesting
-    public int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config,
+    int getCycleDayFromCarrierConfig(@Nullable PersistableBundle config,
             int fallbackCycleDay) {
         if (config == null) {
             return fallbackCycleDay;
@@ -1542,7 +1542,7 @@
      * @return warningBytes to use in the mobile NetworkPolicy.
      */
     @VisibleForTesting
-    public long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config,
+    long getWarningBytesFromCarrierConfig(@Nullable PersistableBundle config,
             long fallbackWarningBytes) {
         if (config == null) {
             return fallbackWarningBytes;
@@ -1575,7 +1575,7 @@
      * @return limitBytes to use in the mobile NetworkPolicy.
      */
     @VisibleForTesting
-    public long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config,
+    long getLimitBytesFromCarrierConfig(@Nullable PersistableBundle config,
             long fallbackLimitBytes) {
         if (config == null) {
             return fallbackLimitBytes;
@@ -2039,7 +2039,7 @@
     }
 
     @VisibleForTesting
-    public NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) {
+    NetworkPolicy buildDefaultMobilePolicy(int subId, String subscriberId) {
         final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
         final RecurrenceRule cycleRule = NetworkPolicy
                 .buildRule(ZonedDateTime.now().getDayOfMonth(), ZoneId.systemDefault());
@@ -3489,7 +3489,7 @@
     }
 
     @VisibleForTesting
-    public boolean isUidForeground(int uid) {
+    boolean isUidForeground(int uid) {
         synchronized (mUidRulesFirstLock) {
             return isUidStateForeground(
                     mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY));
@@ -3931,7 +3931,9 @@
      * power saving restrictions may still apply.
      */
     @VisibleForTesting
-    public void setAppIdleWhitelist(int uid, boolean shouldWhitelist) {
+    void setAppIdleWhitelist(int uid, boolean shouldWhitelist) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
         synchronized (mUidRulesFirstLock) {
             if (mAppIdleTempWhitelistAppIds.get(uid) == shouldWhitelist) {
                 // No change.
@@ -3956,7 +3958,7 @@
 
     /** Return the list of UIDs currently in the app idle whitelist. */
     @VisibleForTesting
-    public int[] getAppIdleWhitelist() {
+    int[] getAppIdleWhitelist() {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
         synchronized (mUidRulesFirstLock) {
@@ -3971,7 +3973,7 @@
 
     /** Returns if the UID is currently considered idle. */
     @VisibleForTesting
-    public boolean isUidIdle(int uid) {
+    boolean isUidIdle(int uid) {
         synchronized (mUidRulesFirstLock) {
             if (mAppIdleTempWhitelistAppIds.get(uid)) {
                 // UID is temporarily whitelisted.
@@ -4844,13 +4846,13 @@
     }
 
     @VisibleForTesting
-    public void addIdleHandler(IdleHandler handler) {
+    void addIdleHandler(IdleHandler handler) {
         mHandler.getLooper().getQueue().addIdleHandler(handler);
     }
 
     @GuardedBy("mUidRulesFirstLock")
     @VisibleForTesting
-    public void updateRestrictBackgroundByLowPowerModeUL(final PowerSaveState result) {
+    void updateRestrictBackgroundByLowPowerModeUL(final PowerSaveState result) {
         mRestrictBackgroundPowerState = result;
 
         boolean restrictBackground = result.batterySaverEnabled;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f279af0..94d276c 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -309,6 +309,7 @@
             newConfig = mConfig.copy();
             ZenRule rule = new ZenRule();
             populateZenRule(automaticZenRule, rule, true);
+            newConfig.automaticRules.put(rule.id, rule);
             if (setConfigLocked(newConfig, reason, rule.component, true)) {
                 return rule.id;
             } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1045289..c125e97 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -15246,7 +15246,8 @@
                         "inputs not balanced; missing argument for " + installPackageName);
             }
             final DeletePackageAction deletePackageAction;
-            if (prepareResult.replace) {
+            // we only want to try to delete for non system apps
+            if (prepareResult.replace && !prepareResult.system) {
                 deletePackageAction = mayDeletePackageLocked(res.removedInfo,
                         prepareResult.originalPs, prepareResult.disabledPs,
                         prepareResult.childPackageSettings);
@@ -17818,7 +17819,7 @@
             return null;
         }
         if (isSystemApp(ps)) {
-            if (ps.parentPackageName == null) {
+            if (ps.parentPackageName != null) {
                 Slog.w(TAG, "Attempt to delete child system package " + ps.pkg.packageName);
                 return null;
             }
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index e194d15..2d583ca3 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -105,6 +105,8 @@
      */
     private boolean perUser;
 
+    boolean usageInfoRequired;
+
     public BasePermission(String _name, String _sourcePackageName, @PermissionType int _type) {
         name = _name;
         sourcePackageName = _sourcePackageName;
@@ -351,6 +353,7 @@
         }
         if (bp.perm == p) {
             bp.protectionLevel = p.info.protectionLevel;
+            bp.usageInfoRequired = p.info.usageInfoRequired;
         }
         if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
             Log.d(TAG, "  Permissions: " + r);
@@ -430,6 +433,7 @@
         permissionInfo.packageName = sourcePackageName;
         permissionInfo.nonLocalizedLabel = name;
         permissionInfo.protectionLevel = protectionLevel;
+        permissionInfo.usageInfoRequired = usageInfoRequired;
         return permissionInfo;
     }
 
@@ -458,6 +462,7 @@
         bp.protectionLevel = readInt(parser, null, "protection",
                 PermissionInfo.PROTECTION_NORMAL);
         bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
+        bp.usageInfoRequired = readInt(parser, null, "usageInfoRequired", 0) != 0;
         if (dynamic) {
             final PermissionInfo pi = new PermissionInfo();
             pi.packageName = sourcePackage.intern();
@@ -465,6 +470,7 @@
             pi.icon = readInt(parser, null, "icon", 0);
             pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
             pi.protectionLevel = bp.protectionLevel;
+            pi.usageInfoRequired = bp.usageInfoRequired;
             bp.pendingPermissionInfo = pi;
         }
         out.put(bp.name, bp);
@@ -497,6 +503,7 @@
         if (protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
             serializer.attribute(null, "protection", Integer.toString(protectionLevel));
         }
+        serializer.attribute(null, "usageInfoRequired", usageInfoRequired ? "1" : "0");
         if (type == BasePermission.TYPE_DYNAMIC) {
             final PermissionInfo pi = perm != null ? perm.info : pendingPermissionInfo;
             if (pi != null) {
@@ -533,6 +540,7 @@
         if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
         // We'll take care of setting this one.
         if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+        if (pi1.usageInfoRequired != pi2.usageInfoRequired) return false;
         // These are not currently stored in settings.
         //if (!compareStrings(pi1.group, pi2.group)) return false;
         //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
@@ -580,6 +588,8 @@
             pw.print("    enforced=");
             pw.println(readEnforced);
         }
+        pw.print("    usageInfoRequired=");
+        pw.println(usageInfoRequired);
         return true;
     }
 }
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 8d27d1e..c8a68b4 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -402,7 +402,7 @@
             throws RemoteException {
         try {
             final int uid = context.getPackageManager()
-                    .getPackageUid(packageName, 0);
+                    .getPackageUidAsUser(packageName, UserHandle.getCallingUserId());
             Preconditions.checkArgument(Binder.getCallingUid() == uid);
         } catch (IllegalArgumentException | NullPointerException |
                 PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6ede423..cfec8ef 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1181,7 +1181,7 @@
                 // TODO(multi-display) TBD.
                 if (mInfo != null && mInfo.supportsAmbientMode() && displayId == DEFAULT_DISPLAY) {
                     try {
-                        connector.mEngine.setInAmbientMode(mInAmbientMode, false /* animated */);
+                        connector.mEngine.setInAmbientMode(mInAmbientMode, 0L /* duration */);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to set ambient mode state", e);
                     }
@@ -2023,11 +2023,17 @@
         }
     }
 
-    // TODO(b/115486823) Extends this method with specific display.
-    public void setInAmbientMode(boolean inAmbienMode, boolean animated) {
+    /**
+     * TODO(b/115486823) Extends this method with specific display.
+     * Propagate ambient state to wallpaper engine.
+     *
+     * @param inAmbientMode {@code true} when in ambient mode, {@code false} otherwise.
+     * @param animationDuration Duration of the animation, or 0 when immediate.
+     */
+    public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
         final IWallpaperEngine engine;
         synchronized (mLock) {
-            mInAmbientMode = inAmbienMode;
+            mInAmbientMode = inAmbientMode;
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null && data.connection != null && data.connection.mInfo != null
                     && data.connection.mInfo.supportsAmbientMode()) {
@@ -2040,7 +2046,7 @@
 
         if (engine != null) {
             try {
-                engine.setInAmbientMode(inAmbienMode, animated);
+                engine.setInAmbientMode(inAmbientMode, animationDuration);
             } catch (RemoteException e) {
                 // Cannot talk to wallpaper engine.
             }
@@ -2344,7 +2350,7 @@
                 return false;
             }
             if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
-                String msg = "Selected service does not require "
+                String msg = "Selected service does not have "
                         + android.Manifest.permission.BIND_WALLPAPER
                         + ": " + componentName;
                 if (fromUser) {
@@ -2396,6 +2402,22 @@
                 }
             }
 
+            if (wi != null && wi.supportsAmbientMode()) {
+                final int hasPrivilege = mIPackageManager.checkPermission(
+                        android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),
+                        serviceUserId);
+                if (hasPrivilege != PackageManager.PERMISSION_GRANTED) {
+                    String msg = "Selected service does not have "
+                            + android.Manifest.permission.AMBIENT_WALLPAPER
+                            + ": " + componentName;
+                    if (fromUser) {
+                        throw new SecurityException(msg);
+                    }
+                    Slog.w(TAG, msg);
+                    return false;
+                }
+            }
+
             // Bind the service!
             if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
             final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index e3133ef..eff0f75 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -42,7 +42,7 @@
  * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled}
  * or into {@code FINISHED} with {@link #onActivityLaunchFinished}. These are terminal states.
  *
- * Note that the {@link ActivityRecord} provided as a parameter to some state transitions isn't
+ * Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't
  * necessarily the same within a single launch sequence: it is only the top-most activity at the
  * time (if any). Trampoline activities coalesce several activity starts into a single launch
  * sequence.
@@ -94,6 +94,14 @@
     public static final int TEMPERATURE_HOT = 3;
 
     /**
+     * Typedef marker that a {@code byte[]} actually contains an
+     * <a href="proto/android/server/activitymanagerservice.proto">ActivityRecordProto</a>
+     * in the protobuf format.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ActivityRecordProto {}
+
+    /**
      * Notifies the observer that a new launch sequence has begun as a result of a new intent.
      *
      * Once a launch sequence begins, the resolved activity will either subsequently start with
@@ -135,7 +143,7 @@
      * Multiple calls to this method cannot occur without first terminating the current
      * launch sequence.
      */
-    public void onActivityLaunched(@NonNull ActivityRecord activity,
+    public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
                                    @Temperature int temperature);
 
     /**
@@ -157,7 +165,7 @@
      *          in the case of a trampoline, multiple activities could've been started
      *          and only the latest activity is reported here.
      */
-    public void onActivityLaunchCancelled(@Nullable ActivityRecord abortingActivity);
+    public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] abortingActivity);
 
     /**
      * Notifies the observer that the current launch sequence has been successfully finished.
@@ -178,5 +186,5 @@
      *          and only the latest activity that was top-most during first-frame drawn
      *          is reported here.
      */
-    public void onActivityLaunchFinished(@NonNull ActivityRecord finalActivity);
+    public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity);
 }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserverRegistry.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserverRegistry.java
new file mode 100644
index 0000000..fa90dc5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserverRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * 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.server.wm;
+
+import android.annotation.NonNull;
+
+/**
+ * Multi-cast delegate implementation for {@link ActivityMetricsLaunchObserver}.
+ *
+ * <br/><br/>
+ * This enables multiple launch observers to subscribe to {@link ActivityMetricsLogger}
+ * independently of each other.
+ *
+ * <br/><br/>
+ * Some callbacks in {@link ActivityMetricsLaunchObserver} have a {@code byte[]}
+ * parameter; this array is reused by all the registered observers, so it must not be written to
+ * (i.e. all observers must treat any array parameters as immutable).
+ *
+ * <br /><br />
+ * Multi-cast invocations occurs sequentially in-order of registered observers.
+ */
+public interface ActivityMetricsLaunchObserverRegistry {
+    /**
+     * Register an extra launch observer to receive the multi-cast.
+     *
+     * <br /><br />
+     * Multi-cast invocation happens in the same order the observers were registered. For example,
+     * <pre>
+     *     registerLaunchObserver(A)
+     *     registerLaunchObserver(B)
+     *
+     *     obs.onIntentFailed() ->
+     *       A.onIntentFailed()
+     *       B.onIntentFailed()
+     * </pre>
+     */
+    void registerLaunchObserver(@NonNull ActivityMetricsLaunchObserver launchObserver);
+
+    /**
+     * Unregister an existing launch observer. It will not receive the multi-cast in the future.
+     *
+     * <br /><br />
+     * This does nothing if this observer was not already registered.
+     */
+    void unregisterLaunchObserver(@NonNull ActivityMetricsLaunchObserver launchObserver);
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 416e133..16df52d 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -99,10 +99,12 @@
 import android.util.SparseIntArray;
 import android.util.StatsLog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
 /**
@@ -168,7 +170,8 @@
      * Due to the global single concurrent launch sequence, all calls to this observer must be made
      * in-order on the same thread to fulfill the "happens-before" guarantee in LaunchObserver.
      */
-    private final ActivityMetricsLaunchObserver mLaunchObserver = null;
+    private final LaunchObserverRegistryImpl mLaunchObserver;
+    @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512;
 
     private final class H extends Handler {
 
@@ -263,6 +266,7 @@
         mSupervisor = supervisor;
         mContext = context;
         mHandler = new H(looper);
+        mLaunchObserver = new LaunchObserverRegistryImpl(looper);
     }
 
     void logWindowState() {
@@ -1000,12 +1004,19 @@
         }
     }
 
+    public ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry() {
+        return mLaunchObserver;
+    }
+
     /** Notify the {@link ActivityMetricsLaunchObserver} that a new launch sequence has begun. */
     private void launchObserverNotifyIntentStarted(Intent intent) {
-        if (mLaunchObserver != null) {
-            // Beginning a launch is timing sensitive and so should be observed as soon as possible.
-            mLaunchObserver.onIntentStarted(intent);
-        }
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyIntentStarted");
+
+        // Beginning a launch is timing sensitive and so should be observed as soon as possible.
+        mLaunchObserver.onIntentStarted(intent);
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1014,9 +1025,12 @@
      * intent being delivered to the top running activity.
      */
     private void launchObserverNotifyIntentFailed() {
-        if (mLaunchObserver != null) {
-            mLaunchObserver.onIntentFailed();
-        }
+       Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyIntentFailed");
+
+        mLaunchObserver.onIntentFailed();
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1024,14 +1038,17 @@
      * has started.
      */
     private void launchObserverNotifyActivityLaunched(WindowingModeTransitionInfo info) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyActivityLaunched");
+
         @ActivityMetricsLaunchObserver.Temperature int temperature =
                 convertTransitionTypeToLaunchObserverTemperature(getTransitionType(info));
 
-        if (mLaunchObserver != null) {
-            // Beginning a launch is timing sensitive and so should be observed as soon as possible.
-            mLaunchObserver.onActivityLaunched(info.launchedActivity,
-                                               temperature);
-        }
+        // Beginning a launch is timing sensitive and so should be observed as soon as possible.
+        mLaunchObserver.onActivityLaunched(convertActivityRecordToProto(info.launchedActivity),
+                                           temperature);
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1039,11 +1056,15 @@
      * cancelled.
      */
     private void launchObserverNotifyActivityLaunchCancelled(WindowingModeTransitionInfo info) {
-        final ActivityRecord launchedActivity = info != null ? info.launchedActivity : null;
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyActivityLaunchCancelled");
 
-        if (mLaunchObserver != null) {
-            mLaunchObserver.onActivityLaunchCancelled(launchedActivity);
-        }
+        final @ActivityMetricsLaunchObserver.ActivityRecordProto byte[] activityRecordProto =
+                info != null ? convertActivityRecordToProto(info.launchedActivity) : null;
+
+        mLaunchObserver.onActivityLaunchCancelled(activityRecordProto);
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     /**
@@ -1051,11 +1072,34 @@
      * has fully finished (successfully).
      */
     private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info) {
-        final ActivityRecord launchedActivity = info.launchedActivity;
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:launchObserverNotifyActivityLaunchFinished");
 
-        if (mLaunchObserver != null) {
-            mLaunchObserver.onActivityLaunchFinished(launchedActivity);
-        }
+        mLaunchObserver.onActivityLaunchFinished(
+                convertActivityRecordToProto(info.launchedActivity));
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @VisibleForTesting
+    static @ActivityMetricsLaunchObserver.ActivityRecordProto byte[]
+            convertActivityRecordToProto(ActivityRecord record) {
+        // May take non-negligible amount of time to convert ActivityRecord into a proto,
+        // so track the time.
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                "MetricsLogger:convertActivityRecordToProto");
+
+        // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
+        // so create a new one every time.
+        final ProtoOutputStream protoOutputStream =
+                new ProtoOutputStream(LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+        // Write this data out as the top-most ActivityRecordProto (i.e. it is not a sub-object).
+        record.writeToProto(protoOutputStream);
+        final byte[] bytes = protoOutputStream.getBytes();
+
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        return bytes;
     }
 
     private static @ActivityMetricsLaunchObserver.Temperature int
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b4aec35..6f2461b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3215,8 +3215,11 @@
         proto.end(token);
     }
 
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
+    /**
+     * Write all fields to an {@code ActivityRecordProto}. This assumes the
+     * {@code ActivityRecordProto} is the outer-most proto data.
+     */
+    void writeToProto(ProtoOutputStream proto) {
         super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(STATE, mState.toString());
@@ -3226,6 +3229,11 @@
             proto.write(PROC_ID, app.getPid());
         }
         proto.write(TRANSLUCENT, !fullscreen);
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        writeToProto(proto);
         proto.end(token);
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 3162ee3..987c706 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -434,7 +434,9 @@
 
         mInitialized = true;
         mRunningTasks = createRunningTasks();
-        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext, mHandler.getLooper());
+
+        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext,
+                mHandler.getLooper());
         mKeyguardController = new KeyguardController(mService, this);
 
         mPersisterQueue = new PersisterQueue();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index d665592..0cdbedb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -473,4 +473,6 @@
     public abstract void setProfileApp(String profileApp);
     public abstract void setProfileProc(WindowProcessController wpc);
     public abstract void setProfilerInfo(ProfilerInfo profilerInfo);
+
+    public abstract ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry();
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8f99dae..e1a1e61 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4511,6 +4511,21 @@
         return mKeyguardController.isKeyguardLocked();
     }
 
+    /**
+     * Clears launch params for the given package.
+     * @param packageNames the names of the packages of which the launch params are to be cleared
+     */
+    @Override
+    public void clearLaunchParamsForPackages(List<String> packageNames) {
+        mAmInternal.enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "clearLaunchParamsForPackages");
+        synchronized (mGlobalLock) {
+            for (int i = 0; i < packageNames.size(); ++i) {
+                mStackSupervisor.mLaunchParamsPersister.removeRecordForPackage(packageNames.get(i));
+            }
+        }
+    }
+
     void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
@@ -6879,5 +6894,12 @@
                 mProfilerInfo = profilerInfo;
             }
         }
+
+        @Override
+        public ActivityMetricsLaunchObserverRegistry getLaunchObserverRegistry() {
+            synchronized (mGlobalLock) {
+                return mStackSupervisor.getActivityMetricsLogger().getLaunchObserverRegistry();
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f1d1e49..7aabc15 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.power.V1_0.PowerHint;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -57,6 +59,7 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mDisplayPolicy;
+    private final DisplayWindowSettings mDisplayWindowSettings;
     private final Context mContext;
     private final Object mLock;
 
@@ -71,10 +74,6 @@
     private StatusBarManagerInternal mStatusBarManagerInternal;
     private SettingsObserver mSettingsObserver;
 
-    // Default display does not rotate, apps that require non-default orientation will have to
-    // have the orientation emulated.
-    private boolean mForceDefaultOrientation;
-
     private int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
     @VisibleForTesting
@@ -93,6 +92,13 @@
     private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
     private int mUserRotation = Surface.ROTATION_0;
 
+    /**
+     * A flag to indicate if the display rotation should be fixed to user specified rotation
+     * regardless of all other states (including app requrested orientation). {@code true} the
+     * display rotation should be fixed to user specified rotation, {@code false} otherwise.
+     */
+    private boolean mFixedToUserRotation;
+
     private int mDemoHdmiRotation;
     private int mDemoRotation;
     private boolean mDemoHdmiRotationLock;
@@ -100,15 +106,17 @@
 
     DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
         this(service, displayContent, displayContent.getDisplayPolicy(),
-                service.mContext, service.getWindowManagerLock());
+                service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
     }
 
     @VisibleForTesting
     DisplayRotation(WindowManagerService service, DisplayContent displayContent,
-            DisplayPolicy displayPolicy, Context context, Object lock) {
+            DisplayPolicy displayPolicy, DisplayWindowSettings displayWindowSettings,
+            Context context, Object lock) {
         mService = service;
         mDisplayContent = displayContent;
         mDisplayPolicy = displayPolicy;
+        mDisplayWindowSettings = displayWindowSettings;
         mContext = context;
         mLock = lock;
         isDefaultDisplay = displayContent.isDefaultDisplay;
@@ -204,12 +212,19 @@
         // so if the orientation is forced, we need to respect that no matter what.
         final boolean isTv = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
-        mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv) &&
-                res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
-                // For debug purposes the next line turns this feature off with:
-                // $ adb shell setprop config.override_forced_orient true
-                // $ adb shell wm size reset
-                !"true".equals(SystemProperties.get("config.override_forced_orient"));
+        final boolean forceDefaultOrientationInRes =
+                res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation);
+        final boolean forceDefaultOrienation =
+                ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv)
+                        && forceDefaultOrientationInRes
+                        // For debug purposes the next line turns this feature off with:
+                        // $ adb shell setprop config.override_forced_orient true
+                        // $ adb shell wm size reset
+                        && !"true".equals(SystemProperties.get("config.override_forced_orient"));
+        // Configuration says we force to use the default orientation. We can fall back to fix
+        // rotation to only user rotation. As long as OEM doesn't change user rotation then the
+        // rotation of this display is effectively stuck at 0 deg.
+        setFixedToUserRotation(forceDefaultOrienation);
     }
 
     void setRotation(int rotation) {
@@ -227,7 +242,14 @@
         }
     }
 
-    void restoreUserRotation(int userRotationMode, int userRotation) {
+    void restoreSettings(int userRotationMode, int userRotation,
+            boolean fixedToUserRotation) {
+        mFixedToUserRotation = fixedToUserRotation;
+
+        // We will retrieve user rotation and user rotation mode from settings for default display.
+        if (isDefaultDisplay) {
+            return;
+        }
         if (userRotationMode != WindowManagerPolicy.USER_ROTATION_FREE
                 && userRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) {
             Slog.w(TAG, "Trying to restore an invalid user rotation mode " + userRotationMode
@@ -243,6 +265,18 @@
         mUserRotation = userRotation;
     }
 
+    void setFixedToUserRotation(boolean fixedToUserRotation) {
+        if (mFixedToUserRotation == fixedToUserRotation) {
+            return;
+        }
+
+        mFixedToUserRotation = fixedToUserRotation;
+        mDisplayWindowSettings.setFixedToUserRotation(mDisplayContent,
+                fixedToUserRotation);
+        mService.updateRotation(true /* alwaysSendConfiguration */,
+                false /* forceRelayout */);
+    }
+
     private void setUserRotation(int userRotationMode, int userRotation) {
         if (isDefaultDisplay) {
             // We'll be notified via settings listener, so we don't need to update internal values.
@@ -265,7 +299,7 @@
             mUserRotation = userRotation;
             changed = true;
         }
-        mService.mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
+        mDisplayWindowSettings.setUserRotation(mDisplayContent, userRotationMode,
                 userRotation);
         if (changed) {
             mService.updateRotation(true /* alwaysSendConfiguration */,
@@ -291,9 +325,8 @@
                 Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) == 0;
     }
 
-    /** @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true. */
-    boolean isDefaultOrientationForced() {
-        return mForceDefaultOrientation;
+    boolean isFixedToUserRotation() {
+        return mFixedToUserRotation;
     }
 
     public int getLandscapeRotation() {
@@ -399,6 +432,12 @@
      * screen is switched off.
      */
     private boolean needSensorRunning() {
+        if (mFixedToUserRotation) {
+            // We are sure we only respect user rotation settings, so we are sure we will not
+            // support sensor rotation.
+            return false;
+        }
+
         if (mSupportAutoRotation) {
             if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
                     || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
@@ -459,8 +498,8 @@
                         );
         }
 
-        if (mForceDefaultOrientation) {
-            return Surface.ROTATION_0;
+        if (mFixedToUserRotation) {
+            return mUserRotation;
         }
 
         int sensorRotation = mOrientationListener != null
@@ -701,8 +740,8 @@
         // demo, hdmi, vr, etc mode.
 
         // Determine if the rotation is currently forced.
-        if (mForceDefaultOrientation) {
-            return false; // Rotation is forced to default orientation.
+        if (mFixedToUserRotation) {
+            return false; // Rotation is forced to user settings.
         }
 
         final int lidState = mDisplayPolicy.getLidState();
@@ -861,6 +900,7 @@
         pw.print(" mDemoHdmiRotationLock=" + mDemoHdmiRotationLock);
         pw.println(" mUndockedHdmiRotation=" + Surface.rotationToString(mUndockedHdmiRotation));
         pw.println(prefix + "  mLidOpenRotation=" + Surface.rotationToString(mLidOpenRotation));
+        pw.println(prefix + "  mFixedToUserRotation=" + mFixedToUserRotation);
     }
 
     private class OrientationListener extends WindowOrientationListener {
@@ -945,4 +985,10 @@
             }
         }
     }
+
+    @VisibleForTesting
+    interface ContentObserverRegister {
+        void registerContentObserver(Uri uri, boolean notifyForDescendants,
+                ContentObserver observer, @UserIdInt int userHandle);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index f7dfd3f..45d77de 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -80,6 +80,7 @@
         private boolean mShouldShowWithInsecureKeyguard = false;
         private boolean mShouldShowSystemDecors = false;
         private boolean mShouldShowIme = false;
+        private boolean mFixedToUserRotation;
 
         private Entry(String name) {
             mName = name;
@@ -97,7 +98,8 @@
                     && mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED
                     && !mShouldShowWithInsecureKeyguard
                     && !mShouldShowSystemDecors
-                    && !mShouldShowIme;
+                    && !mShouldShowIme
+                    && !mFixedToUserRotation;
         }
     }
 
@@ -186,6 +188,13 @@
         writeSettingsIfNeeded(entry, displayInfo);
     }
 
+    void setFixedToUserRotation(DisplayContent displayContent, boolean fixedToUserRotation) {
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final Entry entry = getOrCreateEntry(displayInfo);
+        entry.mFixedToUserRotation = fixedToUserRotation;
+        writeSettingsIfNeeded(entry, displayInfo);
+    }
+
     private int getWindowingModeLocked(Entry entry, int displayId) {
         int windowingMode = entry != null ? entry.mWindowingMode
                 : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -331,7 +340,8 @@
         displayInfo.overscanRight = entry.mOverscanRight;
         displayInfo.overscanBottom = entry.mOverscanBottom;
 
-        dc.getDisplayRotation().restoreUserRotation(entry.mUserRotationMode, entry.mUserRotation);
+        dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
+                entry.mUserRotation, entry.mFixedToUserRotation);
 
         if (entry.mForcedDensity != 0) {
             dc.mBaseDisplayDensity = entry.mForcedDensity;
@@ -458,6 +468,8 @@
                     "shouldShowWithInsecureKeyguard");
             entry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors");
             entry.mShouldShowIme = getBooleanAttribute(parser, "shouldShowIme");
+            entry.mFixedToUserRotation = getBooleanAttribute(parser,
+                    "fixedToUserRotation");
             mEntries.put(name, entry);
         }
         XmlUtils.skipCurrentTag(parser);
@@ -541,6 +553,10 @@
                 if (entry.mShouldShowIme) {
                     out.attribute(null, "shouldShowIme", Boolean.toString(entry.mShouldShowIme));
                 }
+                if (entry.mFixedToUserRotation) {
+                    out.attribute(null, "fixedToUserRotation",
+                            Boolean.toString(entry.mFixedToUserRotation));
+                }
                 out.endTag(null, "display");
             }
 
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
new file mode 100644
index 0000000..93e2d8d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -0,0 +1,162 @@
+/*
+ * 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.server.wm;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.ArrayList;
+
+/**
+ * Multi-cast implementation of {@link ActivityMetricsLaunchObserver}.
+ *
+ * <br /><br />
+ * If this class is called through the {@link ActivityMetricsLaunchObserver} interface,
+ * then the call is forwarded to all registered observers at the time.
+ *
+ * <br /><br />
+ * All calls are invoked asynchronously in-order on a background thread. This fulfills the
+ * sequential ordering guarantee in {@link ActivityMetricsLaunchObserverRegistry}.
+ *
+ * @see ActivityTaskManagerInternal#getLaunchObserverRegistry()
+ */
+class LaunchObserverRegistryImpl implements
+        ActivityMetricsLaunchObserverRegistry, ActivityMetricsLaunchObserver {
+    private final ArrayList<ActivityMetricsLaunchObserver> mList = new ArrayList<>();
+
+    /**
+     * All calls are posted to a handler because:
+     *
+     * 1. We don't know how long the observer will take to handle this call and we don't want
+     *    to block the WM critical section on it.
+     * 2. We don't know the lock ordering of the observer so we don't want to expose a chance
+     *    of deadlock.
+     */
+    private final Handler mHandler;
+
+    public LaunchObserverRegistryImpl(Looper looper) {
+        mHandler = new Handler(looper);
+    }
+
+    @Override
+    public void registerLaunchObserver(ActivityMetricsLaunchObserver launchObserver) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleRegisterLaunchObserver, this, launchObserver));
+    }
+
+    @Override
+    public void unregisterLaunchObserver(ActivityMetricsLaunchObserver launchObserver) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleUnregisterLaunchObserver, this, launchObserver));
+    }
+
+    @Override
+    public void onIntentStarted(Intent intent) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnIntentStarted, this, intent));
+    }
+
+    @Override
+    public void onIntentFailed() {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnIntentFailed, this));
+    }
+
+    @Override
+    public void onActivityLaunched(
+            @ActivityRecordProto byte[] activity,
+            int temperature) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnActivityLaunched,
+                this, activity, temperature));
+    }
+
+    @Override
+    public void onActivityLaunchCancelled(
+        @ActivityRecordProto byte[] activity) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnActivityLaunchCancelled, this, activity));
+    }
+
+    @Override
+    public void onActivityLaunchFinished(
+        @ActivityRecordProto byte[] activity) {
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, this, activity));
+    }
+
+    // Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be
+    // unbound (i.e. not capture any variables explicitly or implicitly) to fulfill the
+    // singleton-lambda requirement.
+
+    private void handleRegisterLaunchObserver(ActivityMetricsLaunchObserver observer) {
+        mList.add(observer);
+    }
+
+    private void handleUnregisterLaunchObserver(ActivityMetricsLaunchObserver observer) {
+        mList.remove(observer);
+    }
+
+    private void handleOnIntentStarted(Intent intent) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onIntentStarted(intent);
+        }
+    }
+
+    private void handleOnIntentFailed() {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onIntentFailed();
+        }
+    }
+
+    private void handleOnActivityLaunched(
+            @ActivityRecordProto byte[] activity,
+            @Temperature int temperature) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onActivityLaunched(activity, temperature);
+        }
+    }
+
+    private void handleOnActivityLaunchCancelled(
+            @ActivityRecordProto byte[] activity) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+             ActivityMetricsLaunchObserver o = mList.get(i);
+             o.onActivityLaunchCancelled(activity);
+        }
+    }
+
+    private void handleOnActivityLaunchFinished(
+            @ActivityRecordProto byte[] activity) {
+        // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+        for (int i = 0; i < mList.size(); i++) {
+            ActivityMetricsLaunchObserver o = mList.get(i);
+            o.onActivityLaunchFinished(activity);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index da9a507..bc6a690 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -269,7 +269,7 @@
         outParams.mBounds.set(persistableParams.mBounds);
     }
 
-    private void onPackageRemoved(String packageName) {
+    void removeRecordForPackage(String packageName) {
         final List<File> fileToDelete = new ArrayList<>();
         for (int i = 0; i < mMap.size(); ++i) {
             int userId = mMap.keyAt(i);
@@ -310,7 +310,7 @@
 
         @Override
         public void onPackageRemoved(String packageName) {
-            LaunchParamsPersister.this.onPackageRemoved(packageName);
+            removeRecordForPackage(packageName);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index ee0e89b..3947bd4 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -222,7 +222,7 @@
     }
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
-            boolean forceDefaultOrientation, boolean isSecure, WindowManagerService service) {
+            boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
         mService = service;
         mContext = context;
         mDisplayContent = displayContent;
@@ -234,7 +234,7 @@
         final int originalWidth;
         final int originalHeight;
         DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        if (forceDefaultOrientation) {
+        if (fixedToUserRotation) {
             // Emulated orientation.
             mForceDefaultOrientation = true;
             originalWidth = displayContent.mBaseDisplayWidth;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b91afcd..4085f3d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3591,6 +3591,17 @@
         }
     }
 
+    void setRotateForApp(int displayId, boolean enabled) {
+        synchronized (mGlobalLock) {
+            final DisplayContent display = mRoot.getDisplayContent(displayId);
+            if (display == null) {
+                Slog.w(TAG, "Trying to set rotate for app for a missing display.");
+                return;
+            }
+            display.getDisplayRotation().setFixedToUserRotation(enabled);
+        }
+    }
+
     @Override
     public void freezeRotation(int rotation) {
         freezeDisplayRotation(Display.DEFAULT_DISPLAY, rotation);
@@ -5397,7 +5408,7 @@
 
             displayContent.updateDisplayInfo();
             screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
-                    displayContent.getDisplayRotation().isDefaultOrientationForced(), isSecure,
+                    displayContent.getDisplayRotation().isFixedToUserRotation(), isSecure,
                     this);
             mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
                     screenRotationAnimation);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bf77ba8..6865ce3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -76,6 +76,8 @@
                             getNextArgRequired());
                 case "set-user-rotation":
                     return runSetDisplayUserRotation(pw);
+                case "set-fix-to-user-rotation":
+                    return runSetFixToUserRotation(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -297,6 +299,32 @@
         }
     }
 
+    private int runSetFixToUserRotation(PrintWriter pw) {
+        int displayId = Display.DEFAULT_DISPLAY;
+        String arg = getNextArgRequired();
+        if ("-d".equals(arg)) {
+            displayId = Integer.parseInt(getNextArgRequired());
+            arg = getNextArgRequired();
+        }
+
+        final boolean enabled;
+        switch (arg) {
+            case "enabled":
+                enabled = true;
+                break;
+            case "disabled":
+                enabled = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expecting enabled or disabled, but we get "
+                        + arg);
+                return -1;
+        }
+
+        mInternal.setRotateForApp(displayId, enabled);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -316,6 +344,8 @@
         pw.println("    Dismiss the keyguard, prompting user for auth if necessary.");
         pw.println("  set-user-rotation [free|lock] [-d DISPLAY_ID] [rotation]");
         pw.println("    Set user rotation mode and user rotation.");
+        pw.println("  set-fix-to-user-rotation [-d DISPLAY_ID] [enabled|disabled]");
+        pw.println("    Enable or disable rotating display for app requested orientation.");
         if (!IS_USER) {
             pw.println("  tracing (start | stop)");
             pw.println("    Start or stop window tracing.");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java
new file mode 100644
index 0000000..fc2dcb9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SP800DeriveTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.test.AndroidTestCase;
+
+import com.android.internal.util.HexDump;
+
+public class SP800DeriveTests extends AndroidTestCase {
+    public void testFixedInput() throws Exception {
+        // CAVP: https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/key-derivation
+        byte[] keyBytes = HexDump.hexStringToByteArray(
+            "e204d6d466aad507ffaf6d6dab0a5b26"
+            + "152c9e21e764370464e360c8fbc765c6");
+        SP800Derive sk = new SP800Derive(keyBytes);
+        byte[] fixedInput = HexDump.hexStringToByteArray(
+            "7b03b98d9f94b899e591f3ef264b71b1"
+            + "93fba7043c7e953cde23bc5384bc1a62"
+            + "93580115fae3495fd845dadbd02bd645"
+            + "5cf48d0f62b33e62364a3a80");
+        byte[] res = sk.fixedInput(fixedInput);
+        assertEquals((
+                "770dfab6a6a4a4bee0257ff335213f78"
+                + "d8287b4fd537d5c1fffa956910e7c779").toUpperCase(), HexDump.toHexString(res));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
rename to services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 92efc3c..99b827c 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package com.android.server.net;
 
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_WIFI;
@@ -72,7 +72,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
@@ -142,9 +141,8 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
-import com.android.server.net.NetworkPolicyManagerInternal;
-import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.net.NetworkStatsManagerInternal;
+import com.android.server.DeviceIdleController;
+import com.android.server.LocalServices;
 
 import com.google.common.util.concurrent.AbstractFuture;
 
@@ -195,15 +193,6 @@
 
 /**
  * Tests for {@link NetworkPolicyManagerService}.
- *
- * <p>Typical usage:
- *
- * <pre><code>
-    m -j32 FrameworksServicesTests && adb install -r -g \
-    ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk && \
-    adb shell am instrument -e class "com.android.server.NetworkPolicyManagerServiceTest" -w \
-    "com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner"
- * </code></pre>
  */
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -376,7 +365,7 @@
                 return null;
             }
         }).when(mActivityManager).registerUidObserver(any(), anyInt(),
-                eq(NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE), isNull(String.class));
+                eq(NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE), any(String.class));
 
         mFutureIntent = newRestrictBackgroundChangedFuture();
         mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
@@ -425,7 +414,7 @@
 
         // catch INetworkManagementEventObserver during systemReady()
         final ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
-              ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
+                ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
         verify(mNetworkManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
 
@@ -1782,7 +1771,7 @@
     }
 
     private static NetworkPolicy buildFakeMobilePolicy(int cycleDay, long warningBytes,
-            long limitBytes, boolean inferred){
+            long limitBytes, boolean inferred) {
         final NetworkTemplate template = buildTemplateMobileAll(FAKE_SUBSCRIBER_ID);
         return new NetworkPolicy(template, cycleDay, new Time().timezone, warningBytes,
                 limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred);
@@ -1880,7 +1869,7 @@
     }
 
     private static void assertNotificationType(int expected, String actualTag) {
-        assertEquals("notification type mismatch for '" + actualTag +"'",
+        assertEquals("notification type mismatch for '" + actualTag + "'",
                 Integer.toString(expected), actualTag.substring(actualTag.lastIndexOf(':') + 1));
     }
 
@@ -1914,7 +1903,8 @@
         final String action = ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
         final Intent intent = future.get(5, TimeUnit.SECONDS);
         assertNotNull("Didn't get a " + action + "intent in 5 seconds");
-        assertEquals("Wrong package on " + action + " intent", expectedPackage, intent.getPackage());
+        assertEquals("Wrong package on " + action + " intent",
+                expectedPackage, intent.getPackage());
     }
 
     // TODO: replace by Truth, Hamcrest, or a similar tool.
@@ -1935,7 +1925,7 @@
         }
         if (errors.length() > 0) {
             fail("assertContainsInAnyOrder(expected=" + Arrays.toString(expected)
-                    + ", actual=" + Arrays.toString(actual) +") failed: \n" + errors);
+                    + ", actual=" + Arrays.toString(actual) + ") failed: \n" + errors);
         }
     }
 
@@ -1998,7 +1988,7 @@
 
         @Override
         public Void answer(InvocationOnMock invocation) throws Throwable {
-            Log.d(TAG,"counting down on answer: " + invocation);
+            Log.d(TAG, "counting down on answer: " + invocation);
             latch.countDown();
             return null;
         }
@@ -2036,8 +2026,8 @@
             final String assetPath = NETPOLICY_DIR + "/" + mNetpolicyXml;
             final File netConfigFile = new File(mPolicyDir, "netpolicy.xml");
             Log.d(TAG, "Creating " + netConfigFile + " from asset " + assetPath);
-            try (final InputStream in = context.getResources().getAssets().open(assetPath);
-                    final OutputStream out = new FileOutputStream(netConfigFile)) {
+            try (InputStream in = context.getResources().getAssets().open(assetPath);
+                    OutputStream out = new FileOutputStream(netConfigFile)) {
                 Streams.copy(in, out);
             }
         }
@@ -2049,9 +2039,7 @@
     @Retention(RetentionPolicy.RUNTIME)
     @Target(ElementType.METHOD)
     public @interface NetPolicyXml {
-
-        public String value() default "";
-
+        String value() default "";
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index d798865..ce59e6e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -31,6 +31,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
+import android.content.pm.UsesPermissionInfo;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
@@ -464,6 +465,7 @@
         pkg.services.add(new PackageParser.Service(dummy, new ServiceInfo()));
         pkg.instrumentation.add(new PackageParser.Instrumentation(dummy, new InstrumentationInfo()));
         pkg.requestedPermissions.add("foo7");
+        pkg.usesPermissionInfos.add(new UsesPermissionInfo("foo7"));
         pkg.implicitPermissions.add("foo25");
 
         pkg.protectedBroadcasts = new ArrayList<>();
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index b823e70..7f390a5 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -27,7 +27,12 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.WindowConfiguration;
 import android.platform.test.annotations.Presubmit;
@@ -378,6 +383,33 @@
                 mSecondaryDisplay.getDisplayRotation().getUserRotation());
     }
 
+    @Test
+    public void testNotFixedToUserRotationByDefault() {
+        mTarget.setUserRotation(mPrimaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
+                Surface.ROTATION_0);
+
+        final DisplayRotation displayRotation = mock(DisplayRotation.class);
+        mPrimaryDisplay = spy(mPrimaryDisplay);
+        when(mPrimaryDisplay.getDisplayRotation()).thenReturn(displayRotation);
+
+        mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
+
+        verify(displayRotation).restoreSettings(anyInt(), anyInt(), eq(false));
+    }
+
+    @Test
+    public void testSetFixedToUserRotation() {
+        mTarget.setFixedToUserRotation(mPrimaryDisplay, true);
+
+        final DisplayRotation displayRotation = mock(DisplayRotation.class);
+        mPrimaryDisplay = spy(mPrimaryDisplay);
+        when(mPrimaryDisplay.getDisplayRotation()).thenReturn(displayRotation);
+
+        applySettingsToDisplayByNewInstance(mPrimaryDisplay);
+
+        verify(displayRotation).restoreSettings(anyInt(), anyInt(), eq(true));
+    }
+
     private static void assertOverscan(DisplayContent display, int left, int top, int right,
             int bottom) {
         final DisplayInfo info = display.getDisplayInfo();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 38d8e39..6c7ede3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -41,6 +41,7 @@
 
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.AutomaticZenRule;
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
@@ -1097,6 +1098,25 @@
         assertFalse(Objects.equals(defaultRuleName, ruleAfterUpdating.name)); // update name
     }
 
+    @Test
+    public void testAddAutomaticZenRule() {
+        AutomaticZenRule zenRule = new AutomaticZenRule("name",
+                new ComponentName("android", "ScheduleConditionProvider"),
+                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        String id = mZenModeHelperSpy.addAutomaticZenRule(zenRule, "test");
+
+        assertTrue(id != null);
+        ZenModeConfig.ZenRule ruleInConfig = mZenModeHelperSpy.mConfig.automaticRules.get(id);
+        assertTrue(ruleInConfig != null);
+        assertEquals(zenRule.isEnabled(), ruleInConfig.enabled);
+        assertEquals(zenRule.isModified(), ruleInConfig.modified);
+        assertEquals(zenRule.getConditionId(), ruleInConfig.conditionId);
+        assertEquals(NotificationManager.zenModeFromInterruptionFilter(
+                zenRule.getInterruptionFilter(), -1), ruleInConfig.zenMode);
+        assertEquals(zenRule.getName(), ruleInConfig.name);
+    }
+
     private void setupZenConfig() {
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
         mZenModeHelperSpy.mConfig.allowAlarms = false;
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f128b4e2..67acec6 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -37,6 +37,8 @@
 
     <application android:debuggable="true"
                  android:testOnly="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityA" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityB" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityRequestedOrientationChange" />
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 0e30037..cac9cf6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -24,20 +24,30 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.timeout;
 
 import android.content.Intent;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+
+import java.util.Arrays;
 
 /**
  * Tests for the {@link ActivityMetricsLaunchObserver} class.
@@ -51,6 +61,7 @@
 public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase {
     private ActivityMetricsLogger mActivityMetricsLogger;
     private ActivityMetricsLaunchObserver mLaunchObserver;
+    private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry;
 
     private TestActivityStack mStack;
     private TaskRecord mTask;
@@ -61,16 +72,13 @@
     public void setUpAMLO() throws Exception {
         setupActivityTaskManagerService();
 
-        mActivityMetricsLogger =
-                new ActivityMetricsLogger(mSupervisor, mService.mContext, mService.mH.getLooper());
-
         mLaunchObserver = mock(ActivityMetricsLaunchObserver.class);
 
-        // TODO: Use ActivityMetricsLaunchObserverRegistry .
-        java.lang.reflect.Field f =
-                mActivityMetricsLogger.getClass().getDeclaredField("mLaunchObserver");
-        f.setAccessible(true);
-        f.set(mActivityMetricsLogger, mLaunchObserver);
+        // ActivityStackSupervisor always creates its own instance of ActivityMetricsLogger.
+        mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger();
+
+        mLaunchObserverRegistry = mActivityMetricsLogger.getLaunchObserverRegistry();
+        mLaunchObserverRegistry.registerLaunchObserver(mLaunchObserver);
 
         // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
         // This seems to be the easiest way to create an ActivityRecord.
@@ -81,13 +89,45 @@
         mActivityRecordTrampoline = new ActivityBuilder(mService).setTask(mTask).build();
     }
 
+    @After
+    public void tearDownAMLO() throws Exception {
+        if (mLaunchObserverRegistry != null) {  // Don't NPE if setUp failed.
+            mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver);
+        }
+    }
+
+    static class ActivityRecordMatcher implements ArgumentMatcher</*@ActivityRecordProto*/ byte[]> {
+        private final @ActivityRecordProto byte[] mExpected;
+
+        public ActivityRecordMatcher(ActivityRecord activityRecord) {
+            mExpected = activityRecordToProto(activityRecord);
+        }
+
+        public boolean matches(@ActivityRecordProto byte[] actual) {
+            return Arrays.equals(mExpected, actual);
+        }
+    }
+
+    static @ActivityRecordProto byte[] activityRecordToProto(ActivityRecord record) {
+        return ActivityMetricsLogger.convertActivityRecordToProto(record);
+    }
+
+    static @ActivityRecordProto byte[] eqProto(ActivityRecord record) {
+        return argThat(new ActivityRecordMatcher(record));
+    }
+
+    static <T> T verifyAsync(T mock) {
+        // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
+        return verify(mock, timeout(100));
+    }
+
     @Test
     public void testOnIntentStarted() throws Exception {
         Intent intent = new Intent("action 1");
 
         mActivityMetricsLogger.notifyActivityLaunching(intent);
 
-        verify(mLaunchObserver).onIntentStarted(eq(intent));
+        verifyAsync(mLaunchObserver).onIntentStarted(eq(intent));
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -102,7 +142,7 @@
         mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
                 activityRecord);
 
-        verify(mLaunchObserver).onIntentFailed();
+        verifyAsync(mLaunchObserver).onIntentFailed();
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -113,7 +153,7 @@
         mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
                 mActivityRecord);
 
-        verify(mLaunchObserver).onActivityLaunched(eq(mActivityRecord), anyInt());
+        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt());
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -127,7 +167,7 @@
        mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(),
                SystemClock.uptimeMillis());
 
-       verify(mLaunchObserver).onActivityLaunchFinished(eq(mActivityRecord));
+       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord));
        verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -135,12 +175,12 @@
     public void testOnActivityLaunchCancelled() throws Exception {
        testOnActivityLaunched();
 
-       mActivityRecord.nowVisible = true;
+       mActivityRecord.mDrawn = true;
 
        // Cannot time already-visible activities.
        mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mActivityRecord);
 
-       verify(mLaunchObserver).onActivityLaunchCancelled(eq(mActivityRecord));
+       verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecord));
        verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -151,7 +191,7 @@
         mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
                 mActivityRecord);
 
-        verify(mLaunchObserver).onActivityLaunched(eq(mActivityRecord), anyInt());
+        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt());
 
         // A second, distinct, activity launch is coalesced into the the current app launch sequence
         mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
@@ -170,7 +210,7 @@
        mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(),
                SystemClock.uptimeMillis());
 
-       verify(mLaunchObserver).onActivityLaunchFinished(eq(mActivityRecordTrampoline));
+       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline));
        verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -178,13 +218,26 @@
     public void testOnActivityLaunchCancelledTrampoline() throws Exception {
        testOnActivityLaunchedTrampoline();
 
-       mActivityRecordTrampoline.nowVisible = true;
+       mActivityRecordTrampoline.mDrawn = true;
 
        // Cannot time already-visible activities.
        mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
                mActivityRecordTrampoline);
 
-       verify(mLaunchObserver).onActivityLaunchCancelled(eq(mActivityRecordTrampoline));
+       verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecordTrampoline));
        verifyNoMoreInteractions(mLaunchObserver);
     }
+
+    @Test
+    public void testActivityRecordProtoIsNotTooBig() throws Exception {
+        // The ActivityRecordProto must not be too big, otherwise converting it at runtime
+        // will become prohibitively expensive.
+        assertWithMessage("mActivityRecord: %s", mActivityRecord).
+                that(activityRecordToProto(mActivityRecord).length).
+                isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+
+        assertWithMessage("mActivityRecordTrampoline: %s", mActivityRecordTrampoline).
+                that(activityRecordToProto(mActivityRecordTrampoline).length).
+                isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
new file mode 100644
index 0000000..e988994
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -0,0 +1,823 @@
+/*
+ * 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.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.atMost;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.view.Surface;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class for {@link DisplayRotation}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:DisplayRotationTests
+ */
+@SmallTest
+@Presubmit
+@FlakyTest(detail = "Confirm stable in post-submit before removing")
+public class DisplayRotationTests {
+    private static final long UI_HANDLER_WAIT_TIMEOUT_MS = 50;
+
+    private StatusBarManagerInternal mPreviousStatusBarManagerInternal;
+
+    private WindowManagerService mMockWm;
+    private DisplayContent mMockDisplayContent;
+    private DisplayPolicy mMockDisplayPolicy;
+    private Context mMockContext;
+    private Resources mMockRes;
+    private SensorManager mMockSensorManager;
+    private Sensor mFakeSensor;
+    private DisplayWindowSettings mMockDisplayWindowSettings;
+    private ContentResolver mMockResolver;
+    private FakeSettingsProvider mFakeSettingsProvider;
+    private StatusBarManagerInternal mMockStatusBarManagerInternal;
+
+    // Fields below are callbacks captured from test target.
+    private ContentObserver mShowRotationSuggestionsObserver;
+    private ContentObserver mAccelerometerRotationObserver;
+    private ContentObserver mUserRotationObserver;
+    private SensorEventListener mOrientationSensorListener;
+
+    private DisplayRotationBuilder mBuilder;
+
+    private DisplayRotation mTarget;
+
+    @Before
+    public void setUp() {
+        FakeSettingsProvider.clearSettingsProvider();
+
+        mMockWm = mock(WindowManagerService.class);
+        mMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
+
+        mPreviousStatusBarManagerInternal = LocalServices.getService(
+                StatusBarManagerInternal.class);
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
+
+        mBuilder = new DisplayRotationBuilder();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        if (mPreviousStatusBarManagerInternal != null) {
+            LocalServices.addService(StatusBarManagerInternal.class,
+                    mPreviousStatusBarManagerInternal);
+            mPreviousStatusBarManagerInternal = null;
+        }
+    }
+
+    // ================================
+    // Display Settings Related Tests
+    // ================================
+    @Test
+    public void testLocksUserRotation_LockRotation_DefaultDisplay() throws Exception {
+        mBuilder.build();
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, mTarget.getUserRotationMode());
+        assertEquals(Surface.ROTATION_180, mTarget.getUserRotation());
+
+        assertEquals(0, Settings.System.getInt(mMockResolver,
+                Settings.System.ACCELEROMETER_ROTATION));
+        assertEquals(Surface.ROTATION_180, Settings.System.getInt(mMockResolver,
+                Settings.System.USER_ROTATION));
+    }
+
+    @Test
+    public void testPersistsUserRotation_LockRotation_NonDefaultDisplay() throws Exception {
+        mBuilder.mIsDefaultDisplay = false;
+
+        mBuilder.build();
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, mTarget.getUserRotationMode());
+        assertEquals(Surface.ROTATION_180, mTarget.getUserRotation());
+
+        verify(mMockDisplayWindowSettings).setUserRotation(mMockDisplayContent,
+                WindowManagerPolicy.USER_ROTATION_LOCKED, Surface.ROTATION_180);
+    }
+
+    @Test
+    public void testPersistUserRotation_UnlockRotation_DefaultDisplay() throws Exception {
+        mBuilder.build();
+
+        thawRotation();
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_FREE, mTarget.getUserRotationMode());
+
+        assertEquals(1, Settings.System.getInt(mMockResolver,
+                Settings.System.ACCELEROMETER_ROTATION));
+    }
+
+    @Test
+    public void testPersistsUserRotation_UnlockRotation_NonDefaultDisplay() throws Exception {
+        mBuilder.mIsDefaultDisplay = false;
+
+        mBuilder.build();
+
+        thawRotation();
+
+        assertEquals(WindowManagerPolicy.USER_ROTATION_FREE, mTarget.getUserRotationMode());
+
+        verify(mMockDisplayWindowSettings).setUserRotation(same(mMockDisplayContent),
+                eq(WindowManagerPolicy.USER_ROTATION_FREE), anyInt());
+    }
+
+    @Test
+    public void testPersistsFixedToUserRotation() throws Exception {
+        mBuilder.build();
+
+        mTarget.setFixedToUserRotation(true);
+
+        verify(mMockDisplayWindowSettings).setFixedToUserRotation(mMockDisplayContent, true);
+
+        reset(mMockDisplayWindowSettings);
+        mTarget.setFixedToUserRotation(false);
+
+        verify(mMockDisplayWindowSettings).setFixedToUserRotation(mMockDisplayContent, false);
+    }
+
+    // ========================================
+    // Tests for User Rotation based Rotation
+    // ========================================
+    @Test
+    public void testReturnsUserRotation_UserRotationLocked_NoAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsUserRotation_UserRotationLocked_CompatibleAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsSidesays_UserRotationLocked_IncompatibleAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        final int rotation = mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, Surface.ROTATION_90);
+        assertTrue("Rotation should be sideways, but it's "
+                        + Surface.rotationToString(rotation),
+                rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+    }
+
+    // =================================
+    // Tests for Sensor based Rotation
+    // =================================
+    private void verifyOrientationListenerRegistration(int numOfInvocation) {
+        final ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(
+                SensorEventListener.class);
+        verify(mMockSensorManager, times(numOfInvocation)).registerListener(
+                listenerCaptor.capture(),
+                same(mFakeSensor),
+                anyInt(),
+                any());
+        if (numOfInvocation > 0) {
+            mOrientationSensorListener = listenerCaptor.getValue();
+        }
+    }
+
+    @Test
+    public void testNotEnablesSensor_AutoRotationNotSupported() throws Exception {
+        mBuilder.setSupportAutoRotation(false).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ScreenNotOn() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(false);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_NotAwake() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(false);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_KeyguardNotDrawnCompletely() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(false);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_WindowManagerNotDrawnCompletely() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(false);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_FixedUserRotation() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.setFixedToUserRotation(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ForceDefaultRotation() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ForceDefaultRotation_Car() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, true, false);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    @Test
+    public void testNotEnablesSensor_ForceDefaultRotation_Tv() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, true);
+
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(0);
+    }
+
+    private void enableOrientationSensor() {
+        when(mMockDisplayPolicy.isScreenOnEarly()).thenReturn(true);
+        when(mMockDisplayPolicy.isAwake()).thenReturn(true);
+        when(mMockDisplayPolicy.isKeyguardDrawComplete()).thenReturn(true);
+        when(mMockDisplayPolicy.isWindowManagerDrawComplete()).thenReturn(true);
+        mTarget.updateOrientationListener();
+        verifyOrientationListenerRegistration(1);
+    }
+
+    private SensorEvent createSensorEvent(int rotation) throws Exception {
+        final Constructor<SensorEvent> constructor =
+                SensorEvent.class.getDeclaredConstructor(int.class);
+        constructor.setAccessible(true);
+        final SensorEvent event = constructor.newInstance(1);
+        event.sensor = mFakeSensor;
+        event.values[0] = rotation;
+        event.timestamp = SystemClock.elapsedRealtimeNanos();
+        return event;
+    }
+
+    @Test
+    public void testReturnsSensorRotation_RotationThawed() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    private boolean waitForUiHandler() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        UiThread.getHandler().post(latch::countDown);
+        return latch.await(UI_HANDLER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+
+    @Test
+    public void testUpdatesRotationWhenSensorUpdates_RotationThawed() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertTrue(waitForUiHandler());
+
+        verify(mMockWm).updateRotation(false, false);
+    }
+
+    @Test
+    public void testNotifiesChoiceWhenSensorUpdates_RotationLocked() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        freezeRotation(Surface.ROTATION_270);
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+        assertTrue(waitForUiHandler());
+
+        verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true);
+    }
+
+    @Test
+    public void testReturnsCompatibleRotation_SensorEnabled_RotationThawed() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        thawRotation();
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        final int rotation = mTarget.rotationForOrientation(SCREEN_ORIENTATION_LANDSCAPE,
+                Surface.ROTATION_0);
+        assertTrue("Rotation should be sideways but it's "
+                + Surface.rotationToString(rotation),
+                rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270);
+    }
+
+    @Test
+    public void testReturnsUserRotation_SensorEnabled_RotationLocked() throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        freezeRotation(Surface.ROTATION_270);
+
+        enableOrientationSensor();
+
+        mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_180));
+
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    // =================================
+    // Tests for Policy based Rotation
+    // =================================
+    @Test
+    public void testReturnsUserRotation_ForceDefaultRotation() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(SCREEN_ORIENTATION_PORTRAIT,
+                Surface.ROTATION_180));
+    }
+
+    @Test
+    public void testReturnsUserRotation_ForceDefaultRotation_Car() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, true, false);
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(SCREEN_ORIENTATION_PORTRAIT,
+                Surface.ROTATION_180));
+    }
+
+    @Test
+    public void testReturnsUserRotation_ForceDefaultRotation_Tv() throws Exception {
+        mBuilder.build();
+        when(mMockRes.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation))
+                .thenReturn(true);
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, true);
+
+        assertEquals(Surface.ROTATION_0, mTarget.rotationForOrientation(SCREEN_ORIENTATION_PORTRAIT,
+                Surface.ROTATION_180));
+    }
+
+    @Test
+    public void testReturnsLidOpenRotation_LidOpen() throws Exception {
+        mBuilder.setLidOpenRotation(Surface.ROTATION_90).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.getLidState()).thenReturn(
+                WindowManagerPolicy.WindowManagerFuncs.LID_OPEN);
+
+        freezeRotation(Surface.ROTATION_270);
+
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    @Test
+    public void testReturnsCarDockRotation_CarDockedMode() throws Exception {
+        mBuilder.setCarDockRotation(Surface.ROTATION_270).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_CAR);
+
+        freezeRotation(Surface.ROTATION_90);
+
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsDeskDockRotation_DeskDockedMode() throws Exception {
+        mBuilder.setDeskDockRotation(Surface.ROTATION_270).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        when(mMockDisplayPolicy.getDockMode()).thenReturn(Intent.EXTRA_DOCK_STATE_DESK);
+
+        freezeRotation(Surface.ROTATION_90);
+
+        assertEquals(Surface.ROTATION_270, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_90));
+    }
+
+    @Test
+    public void testReturnsUserRotation_FixedToUserRotation_IgnoreIncompatibleAppRequest()
+            throws Exception {
+        mBuilder.build();
+        configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+        mTarget.setFixedToUserRotation(true);
+
+        freezeRotation(Surface.ROTATION_180);
+
+        final int rotation = mTarget.rotationForOrientation(
+                ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, Surface.ROTATION_90);
+        assertEquals(Surface.ROTATION_180, rotation);
+    }
+
+    @Test
+    public void testReturnsUserRotation_NonDefaultDisplay() throws Exception {
+        mBuilder.setIsDefaultDisplay(false).build();
+        configureDisplayRotation(SCREEN_ORIENTATION_LANDSCAPE, false, false);
+
+        freezeRotation(Surface.ROTATION_90);
+
+        assertEquals(Surface.ROTATION_90, mTarget.rotationForOrientation(
+                SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0));
+    }
+
+    /**
+     * Call {@link DisplayRotation#configure(int, int, int, int)} to configure {@link #mTarget}
+     * according to given parameters.
+     */
+    private void configureDisplayRotation(int displayOrientation, boolean isCar, boolean isTv) {
+        final int width;
+        final int height;
+        switch (displayOrientation) {
+            case SCREEN_ORIENTATION_LANDSCAPE:
+                width = 1920;
+                height = 1080;
+                break;
+            case SCREEN_ORIENTATION_PORTRAIT:
+                width = 1080;
+                height = 1920;
+                break;
+            default:
+                throw new IllegalArgumentException("displayOrientation needs to be either landscape"
+                        + " or portrait, but we got "
+                        + ActivityInfo.screenOrientationToString(displayOrientation));
+        }
+
+        final PackageManager mockPackageManager = mock(PackageManager.class);
+        when(mMockContext.getPackageManager()).thenReturn(mockPackageManager);
+        when(mockPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+                .thenReturn(isCar);
+        when(mockPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+                .thenReturn(isTv);
+
+        final int shortSizeDp = (isCar || isTv) ? 540 : 720;
+        final int longSizeDp = 960;
+        mTarget.configure(width, height, shortSizeDp, longSizeDp);
+    }
+
+    private void freezeRotation(int rotation) {
+        mTarget.freezeRotation(rotation);
+
+        if (mTarget.isDefaultDisplay) {
+            mAccelerometerRotationObserver.onChange(false);
+            mUserRotationObserver.onChange(false);
+        }
+    }
+
+    private void thawRotation() {
+        mTarget.thawRotation();
+
+        if (mTarget.isDefaultDisplay) {
+            mAccelerometerRotationObserver.onChange(false);
+            mUserRotationObserver.onChange(false);
+        }
+    }
+
+    private class DisplayRotationBuilder {
+        private boolean mIsDefaultDisplay = true;
+        private boolean mSupportAutoRotation = true;
+
+        private int mLidOpenRotation = WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+        private int mCarDockRotation;
+        private int mDeskDockRotation;
+        private int mUndockedHdmiRotation;
+
+        private DisplayRotationBuilder setIsDefaultDisplay(boolean isDefaultDisplay) {
+            mIsDefaultDisplay = isDefaultDisplay;
+            return this;
+        }
+
+        private DisplayRotationBuilder setSupportAutoRotation(boolean supportAutoRotation) {
+            mSupportAutoRotation = supportAutoRotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setLidOpenRotation(int rotation) {
+            mLidOpenRotation = rotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setCarDockRotation(int rotation) {
+            mCarDockRotation = rotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setDeskDockRotation(int rotation) {
+            mDeskDockRotation = rotation;
+            return this;
+        }
+
+        private DisplayRotationBuilder setUndockedHdmiRotation(int rotation) {
+            mUndockedHdmiRotation = rotation;
+            return this;
+        }
+
+        private void captureObservers() {
+            ArgumentCaptor<ContentObserver> captor = ArgumentCaptor.forClass(
+                    ContentObserver.class);
+            verify(mMockResolver, atMost(1)).registerContentObserver(
+                    eq(Settings.Secure.getUriFor(Settings.Secure.SHOW_ROTATION_SUGGESTIONS)),
+                    anyBoolean(),
+                    captor.capture(),
+                    anyInt());
+            if (!captor.getAllValues().isEmpty()) {
+                mShowRotationSuggestionsObserver = captor.getValue();
+            }
+
+            captor = ArgumentCaptor.forClass(ContentObserver.class);
+            verify(mMockResolver, atMost(1)).registerContentObserver(
+                    eq(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION)),
+                    anyBoolean(),
+                    captor.capture(),
+                    anyInt());
+            if (!captor.getAllValues().isEmpty()) {
+                mAccelerometerRotationObserver = captor.getValue();
+            }
+
+            captor = ArgumentCaptor.forClass(ContentObserver.class);
+            verify(mMockResolver, atMost(1)).registerContentObserver(
+                    eq(Settings.System.getUriFor(Settings.System.USER_ROTATION)),
+                    anyBoolean(),
+                    captor.capture(),
+                    anyInt());
+            if (!captor.getAllValues().isEmpty()) {
+                mUserRotationObserver = captor.getValue();
+            }
+        }
+
+        private Sensor createSensor(int type) throws Exception {
+            Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+            constr.setAccessible(true);
+            Sensor sensor = constr.newInstance();
+
+            setSensorType(sensor, type);
+            setSensorField(sensor, "mName", "Mock " + sensor.getStringType() + "/" + type);
+            setSensorField(sensor, "mVendor", "Mock Vendor");
+            setSensorField(sensor, "mVersion", 1);
+            setSensorField(sensor, "mHandle", -1);
+            setSensorField(sensor, "mMaxRange", 10);
+            setSensorField(sensor, "mResolution", 1);
+            setSensorField(sensor, "mPower", 1);
+            setSensorField(sensor, "mMinDelay", 1000);
+            setSensorField(sensor, "mMaxDelay", 1000000000);
+            setSensorField(sensor, "mFlags", 0);
+            setSensorField(sensor, "mId", -1);
+
+            return sensor;
+        }
+
+        private void setSensorType(Sensor sensor, int type) throws Exception {
+            Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE);
+            setter.setAccessible(true);
+            setter.invoke(sensor, type);
+        }
+
+        private void setSensorField(Sensor sensor, String fieldName, Object value)
+                throws Exception {
+            Field field = Sensor.class.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            field.set(sensor, value);
+        }
+
+        private int convertRotationToDegrees(@Surface.Rotation int rotation) {
+            switch (rotation) {
+                case Surface.ROTATION_0:
+                    return 0;
+                case Surface.ROTATION_90:
+                    return 90;
+                case Surface.ROTATION_180:
+                    return 180;
+                case Surface.ROTATION_270:
+                    return 270;
+                default:
+                    return -1;
+            }
+        }
+
+        private void build() throws Exception {
+            mMockContext = mock(Context.class);
+
+            mMockDisplayContent = mock(WindowTestUtils.TestDisplayContent.class);
+            mMockDisplayContent.isDefaultDisplay = mIsDefaultDisplay;
+
+            mMockDisplayPolicy = mock(DisplayPolicy.class);
+
+            mMockRes = mock(Resources.class);
+            when(mMockContext.getResources()).thenReturn((mMockRes));
+            when(mMockRes.getBoolean(com.android.internal.R.bool.config_supportAutoRotation))
+                    .thenReturn(mSupportAutoRotation);
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_lidOpenRotation))
+                    .thenReturn(convertRotationToDegrees(mLidOpenRotation));
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_carDockRotation))
+                    .thenReturn(convertRotationToDegrees(mCarDockRotation));
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_deskDockRotation))
+                    .thenReturn(convertRotationToDegrees(mDeskDockRotation));
+            when(mMockRes.getInteger(com.android.internal.R.integer.config_undockedHdmiRotation))
+                    .thenReturn(convertRotationToDegrees(mUndockedHdmiRotation));
+
+            mMockSensorManager = mock(SensorManager.class);
+            when(mMockContext.getSystemService(Context.SENSOR_SERVICE))
+                    .thenReturn(mMockSensorManager);
+            mFakeSensor = createSensor(Sensor.TYPE_DEVICE_ORIENTATION);
+            when(mMockSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION)).thenReturn(
+                    Collections.singletonList(mFakeSensor));
+
+            mMockResolver = mock(ContentResolver.class);
+            when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
+            mFakeSettingsProvider = new FakeSettingsProvider();
+            when(mMockResolver.acquireProvider(Settings.AUTHORITY))
+                    .thenReturn(mFakeSettingsProvider.getIContentProvider());
+
+            mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
+            mTarget = new DisplayRotation(mMockWm, mMockDisplayContent, mMockDisplayPolicy,
+                    mMockDisplayWindowSettings, mMockContext, new Object());
+
+            captureObservers();
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 2c3c66b..f3a125b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -272,6 +272,51 @@
     }
 
     @Test
+    public void testClearsRecordInMemory() {
+        mTarget.saveTask(mTestTask);
+
+        mTarget.removeRecordForPackage(TEST_COMPONENT.getPackageName());
+
+        mTarget.getLaunchParams(mTestTask, null, mResult);
+
+        assertTrue("Result should be empty.", mResult.isEmpty());
+    }
+
+    @Test
+    public void testClearsWriteQueueItem() {
+        mTarget.saveTask(mTestTask);
+
+        mTarget.removeRecordForPackage(TEST_COMPONENT.getPackageName());
+
+        final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor,
+                mUserFolderGetter);
+        target.onSystemReady();
+        target.onUnlockUser(TEST_USER_ID);
+
+        target.getLaunchParams(mTestTask, null, mResult);
+
+        assertTrue("Result should be empty.", mResult.isEmpty());
+    }
+
+    @Test
+    public void testClearsFile() {
+        mTarget.saveTask(mTestTask);
+        mPersisterQueue.flush();
+
+        mTarget.removeRecordForPackage(TEST_COMPONENT.getPackageName());
+
+        final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor,
+                mUserFolderGetter);
+        target.onSystemReady();
+        target.onUnlockUser(TEST_USER_ID);
+
+        target.getLaunchParams(mTestTask, null, mResult);
+
+        assertTrue("Result should be empty.", mResult.isEmpty());
+    }
+
+
+    @Test
     public void testClearsRecordInMemoryOnPackageUninstalled() {
         mTarget.saveTask(mTestTask);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index e56edab..3c87721 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -65,7 +65,7 @@
 
             final DisplayRotation displayRotation = new DisplayRotation(
                     mock(WindowManagerService.class), displayContent, displayPolicy,
-                    context, new Object());
+                    mock(DisplayWindowSettings.class), context, new Object());
             displayRotation.mPortraitRotation = Surface.ROTATION_0;
             displayRotation.mLandscapeRotation = Surface.ROTATION_90;
             displayRotation.mUpsideDownRotation = Surface.ROTATION_180;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 34a8c96..45cfe1e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -569,14 +569,6 @@
     public static final String IS_OPPORTUNISTIC = "is_opportunistic";
 
     /**
-     * TelephonyProvider column name for subId of parent subscription of an opportunistic
-     * subscription.
-     * if the parent sub id is valid, then is_opportunistic should always to true.
-     * @hide
-     */
-    public static final String PARENT_SUB_ID = "parent_sub_id";
-
-    /**
      * TelephonyProvider column name for group ID. Subscriptions with same group ID
      * are considered bundled together, and should behave as a single subscription at
      * certain scenarios.
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
index 8d8fc84..b9e282e 100644
--- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -38,7 +38,7 @@
             public Engine onCreateEngine() {
                 return new Engine() {
                     @Override
-                    public void onAmbientModeChanged(boolean inAmbientMode, boolean animated) {
+                    public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
                         ambientModeChangedCount[0]++;
                     }
                 };
@@ -47,12 +47,12 @@
         WallpaperService.Engine engine = service.onCreateEngine();
         engine.setCreated(true);
 
-        engine.doAmbientModeChanged(false, false);
+        engine.doAmbientModeChanged(false, 0);
         assertFalse("ambient mode should be false", engine.isInAmbientMode());
         assertEquals("onAmbientModeChanged should have been called",
                 ambientModeChangedCount[0], 1);
 
-        engine.doAmbientModeChanged(true, false);
+        engine.doAmbientModeChanged(true, 0);
         assertTrue("ambient mode should be false", engine.isInAmbientMode());
         assertEquals("onAmbientModeChanged should have been called",
                 ambientModeChangedCount[0], 2);
diff --git a/tools/powermodel/Android.bp b/tools/powermodel/Android.bp
new file mode 100644
index 0000000..f597aab
--- /dev/null
+++ b/tools/powermodel/Android.bp
@@ -0,0 +1,26 @@
+
+java_library_host {
+    name: "powermodel",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "guava",
+    ],
+}
+
+java_test_host {
+    name: "powermodel-test",
+
+    test_suites: ["general-tests"],
+
+    srcs: ["test/**/*.java"],
+    java_resource_dirs: ["test-resource"],
+
+    static_libs: [
+        "powermodel",
+        "junit",
+        "mockito",
+    ],
+}
+
diff --git a/tools/powermodel/TEST_MAPPING b/tools/powermodel/TEST_MAPPING
new file mode 100644
index 0000000..c8db339
--- /dev/null
+++ b/tools/powermodel/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit": [
+    {
+      "name": "powermodel-test"
+    }
+  ]
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/ActivityReport.java b/tools/powermodel/src/com/android/powermodel/ActivityReport.java
new file mode 100644
index 0000000..4a8f633
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ActivityReport.java
@@ -0,0 +1,92 @@
+/*
+ * 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.powermodel;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * ActivityReport contains the summary of the activity that consumes power
+ * as reported by batterystats or statsd.
+ */
+public class ActivityReport {
+    private AppList<AppActivity> mApps;
+
+    public ImmutableList<AppActivity> getAllApps() {
+        return mApps.getAllApps();
+    }
+
+    public ImmutableList<AppActivity> getRegularApps() {
+        return mApps.getRegularApps();
+    }
+
+    public List<AppActivity> findApp(String pkg) {
+        return mApps.findApp(pkg);
+    }
+
+    public AppActivity findApp(SpecialApp specialApp) {
+        return mApps.findApp(specialApp);
+    }
+
+    /**
+     * Find a component in the GLOBAL app.
+     * <p>
+     * Returns null if either the global app doesn't exist (bad data?) or the component
+     * doesn't exist in the global app.
+     */
+    public ComponentActivity findGlobalComponent(Component component) {
+         final AppActivity global = mApps.findApp(SpecialApp.GLOBAL);
+         if (global == null) {
+             return null;
+         }
+         return global.getComponentActivity(component);
+    }
+
+    public static class Builder {
+        private AppList.Builder<AppActivity,AppActivity.Builder> mApps = new AppList.Builder();
+
+        public Builder() {
+        }
+
+        public ActivityReport build() {
+            final ActivityReport result = new ActivityReport();
+            result.mApps = mApps.build();
+            return result;
+        }
+
+        public void addActivity(Component component, Collection<ComponentActivity> activities) {
+            for (final ComponentActivity activity: activities) {
+                addActivity(component, activity);
+            }
+        }
+
+        public void addActivity(Component component, ComponentActivity activity) {
+            AppActivity.Builder app = mApps.get(activity.attribution);
+            if (app == null) {
+                app = new AppActivity.Builder();
+                app.setAttribution(activity.attribution);
+                mApps.put(activity.attribution, app);
+            }
+            app.addComponentActivity(component, activity);
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppActivity.java b/tools/powermodel/src/com/android/powermodel/AppActivity.java
new file mode 100644
index 0000000..b87426c
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppActivity.java
@@ -0,0 +1,80 @@
+/*
+ * 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.powermodel;
+
+import java.util.HashMap;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class AppActivity extends AppInfo {
+
+    private ImmutableMap<Component, ComponentActivity> mComponents;
+    // TODO: power rails
+    // private ImmutableMap<Component, PowerRailActivity> mRails;
+
+    private AppActivity() {
+    }
+
+    /**
+     * Returns the {@link ComponentActivity} for the {@link Component} provided,
+     * or null if this AppActivity does not have that component.
+     * @more
+     * If there is no ComponentActivity for a particular Component, then
+     * there was no usage associated with that app for the app in question.
+     */
+    public ComponentActivity getComponentActivity(Component component) {
+        return mComponents.get(component);
+    }
+
+    public ImmutableSet<Component> getComponents() {
+        return mComponents.keySet();
+    }
+
+    public ImmutableMap<Component,ComponentActivity> getComponentActivities() {
+        return mComponents;
+    }
+
+    // TODO: power rails
+    // public ComponentActivity getPowerRail(Component component) {
+    //     return mComponents.get(component);
+    // }
+    //
+    // public Set<Component> getPowerRails() {
+    //     return mComponents.keySet();
+    // }
+
+    public static class Builder extends AppInfo.Builder<AppActivity> {
+        private HashMap<Component, ComponentActivity> mComponents = new HashMap();
+        // TODO power rails.
+        
+        public Builder() {
+        }
+
+        public AppActivity build() {
+            final AppActivity result = new AppActivity();
+            init(result);
+            result.mComponents = ImmutableMap.copyOf(mComponents);
+            return result;
+        }
+
+        public void addComponentActivity(Component component, ComponentActivity activity) {
+            mComponents.put(component, activity);
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppInfo.java b/tools/powermodel/src/com/android/powermodel/AppInfo.java
new file mode 100644
index 0000000..208339e
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppInfo.java
@@ -0,0 +1,56 @@
+/*
+ * 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.powermodel;
+
+class AppInfo {
+    private AttributionKey mAttribution;
+
+    protected AppInfo() {
+    }
+
+    public boolean hasPackage(String pkg) {
+        return mAttribution.hasPackage(pkg);
+    }
+
+    public AttributionKey getAttribution() {
+        return mAttribution;
+    }
+
+    abstract static class Builder<APP extends AppInfo> {
+        private AttributionKey mAttribution;
+
+        public Builder() {
+        }
+
+        public abstract APP build();
+
+        protected void init(AppInfo app) {
+            if (mAttribution == null) {
+                throw new RuntimeException("setAttribution(AttributionKey attribution) not called");
+            }
+            app.mAttribution = mAttribution;
+        }
+
+        public void setAttribution(AttributionKey attribution) {
+            mAttribution = attribution;
+        }
+
+        public AttributionKey getAttribution() {
+            return mAttribution;
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppList.java b/tools/powermodel/src/com/android/powermodel/AppList.java
new file mode 100644
index 0000000..19572fc
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppList.java
@@ -0,0 +1,91 @@
+/*
+ * 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.powermodel;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+class AppList<APP extends AppInfo> {
+    private ImmutableList<APP> mAllApps;
+    private ImmutableList<APP> mRegularApps;
+    private ImmutableMap<SpecialApp,APP> mSpecialApps;
+
+    private AppList() {
+    }
+
+    public ImmutableList<APP> getAllApps() {
+        return mAllApps;
+    }
+
+    public ImmutableList<APP> getRegularApps() {
+        return mRegularApps;
+    }
+
+    public List<APP> findApp(String pkg) {
+        List<APP> result = new ArrayList();
+        for (APP app: mRegularApps) {
+            if (app.hasPackage(pkg)) {
+                result.add(app);
+            }
+        }
+        return result;
+    }
+
+    public APP findApp(SpecialApp specialApp) {
+        return mSpecialApps.get(specialApp);
+    }
+
+    public static class Builder<APP extends AppInfo, BUILDER extends AppInfo.Builder<APP>> {
+        private final HashMap<AttributionKey,BUILDER> mApps = new HashMap();
+
+        public Builder() {
+        }
+
+        public AppList<APP> build() {
+            final AppList<APP> result = new AppList();
+            final ArrayList<APP> allApps = new ArrayList();
+            final ArrayList<APP> regularApps = new ArrayList();
+            final HashMap<SpecialApp,APP> specialApps = new HashMap();
+            for (AppInfo.Builder<APP> app: mApps.values()) {
+                final AttributionKey attribution = app.getAttribution();
+                final APP appActivity = app.build();
+                allApps.add(appActivity);
+                if (attribution.isSpecialApp()) {
+                    specialApps.put(attribution.getSpecialApp(), appActivity);
+                } else {
+                    regularApps.add(appActivity);
+                }
+            }
+            result.mAllApps = ImmutableList.copyOf(allApps);
+            result.mRegularApps = ImmutableList.copyOf(regularApps);
+            result.mSpecialApps = ImmutableMap.copyOf(specialApps);
+            return result;
+        }
+
+        public BUILDER get(AttributionKey attribution) {
+            return mApps.get(attribution);
+        }
+
+        public BUILDER put(AttributionKey attribution, BUILDER app) {
+            return mApps.put(attribution, app);
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AppPower.java b/tools/powermodel/src/com/android/powermodel/AppPower.java
new file mode 100644
index 0000000..283982b
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AppPower.java
@@ -0,0 +1,86 @@
+/*
+ * 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.powermodel;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableMap;
+
+public class AppPower extends AppInfo {
+    private ImmutableMap<Component, ComponentPower> mComponents;
+
+    private double mAppPowerMah;
+
+
+    private AppPower() {
+    }
+
+    /**
+     * Returns the {@link ComponentPower} for the {@link Component} provided,
+     * or null if this AppPower does not have that component.
+     * @more
+     * If the component was in the power profile for this device, there
+     * will be a component for it, even if there was no power used
+     * by that component. In that case, the
+     * {@link ComponentPower.getUsage() ComponentPower.getUsage()}
+     * method will return 0.
+     */
+    public ComponentPower getComponentPower(Component component) {
+        return mComponents.get(component);
+    }
+
+    public Set<Component> getComponents() {
+        return mComponents.keySet();
+    }
+
+    /**
+     * Return the total power used by this app.
+     */
+    public double getAppPowerMah() {
+        return mAppPowerMah;
+    }
+
+    /**
+     * Builder class for {@link AppPower}
+     */
+    public static class Builder extends AppInfo.Builder<AppPower> {
+        private HashMap<Component, ComponentPower> mComponents = new HashMap();
+
+        public Builder() {
+        }
+
+        public AppPower build() {
+            final AppPower result = new AppPower();
+            init(result);
+            result.mComponents = ImmutableMap.copyOf(mComponents);
+
+            // Add up the components
+            double appPowerMah = 0;
+            for (final ComponentPower componentPower: mComponents.values()) {
+                appPowerMah += componentPower.powerMah;
+            }
+            result.mAppPowerMah = appPowerMah;
+
+            return result;
+        }
+
+        public void addComponentPower(Component component, ComponentPower componentPower) {
+            mComponents.put(component, componentPower);
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/AttributionKey.java b/tools/powermodel/src/com/android/powermodel/AttributionKey.java
new file mode 100644
index 0000000..f19e0b7
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/AttributionKey.java
@@ -0,0 +1,113 @@
+/*
+ * 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.powermodel;
+
+import java.util.Set;
+import java.util.HashSet;
+
+import com.google.common.collect.ImmutableSet;
+
+public class AttributionKey {
+    private final int mUid;
+    private final ImmutableSet<String> mPackages;
+    private final SpecialApp mSpecialApp;
+
+    public AttributionKey(SpecialApp specialApp) {
+        mUid = -1;
+        mPackages = ImmutableSet.of();
+        mSpecialApp = specialApp;
+    }
+
+    public AttributionKey(int uid, Set<String> packages) {
+        mUid = uid;
+        mPackages = ImmutableSet.copyOf(packages);
+        mSpecialApp = null;
+    }
+
+    public ImmutableSet<String> getPackages() {
+        return mPackages;
+    }
+
+    public boolean hasPackage(String pkg) {
+        return mPackages.contains(pkg);
+    }
+
+    public SpecialApp getSpecialApp() {
+        return mSpecialApp;
+    }
+
+    public boolean isSpecialApp() {
+        return mSpecialApp != null;
+    }
+
+    /**
+     * Returns the uid for this attribution, or -1 if there isn't one
+     * (e.g. if it is a special app).
+     */
+    public int getUid() {
+        return mUid;
+    }
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = (31 * hash) + (mUid);
+        hash = (31 * hash) + (mPackages == null ? 0 : mPackages.hashCode());
+        hash = (31 * hash) + (mSpecialApp == null ? 0 : mSpecialApp.hashCode());
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null) {
+            return false;
+        }
+        if (this.getClass() != o.getClass()) {
+            return false;
+        }
+        final AttributionKey that = (AttributionKey)o;
+        return (this.mUid == that.mUid)
+                && this.mPackages != null && this.mPackages.equals(that.mPackages)
+                && this.mSpecialApp != null && this.mSpecialApp.equals(that.mSpecialApp);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder str = new StringBuilder("AttributionKey(");
+        if (mUid >= 0) {
+            str.append(" uid=");
+            str.append(mUid);
+        }
+        if (mPackages.size() > 0) {
+            str.append(" packages=[");
+            for (String pkg: mPackages) {
+                str.append(' ');
+                str.append(pkg);
+            }
+            str.append(" ]");
+        }
+        if (mSpecialApp != null) {
+            str.append(" specialApp=");
+            str.append(mSpecialApp.name());
+        }
+        str.append(" )");
+        return str.toString();
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/BatteryStatsReader.java b/tools/powermodel/src/com/android/powermodel/BatteryStatsReader.java
new file mode 100644
index 0000000..595c661
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/BatteryStatsReader.java
@@ -0,0 +1,74 @@
+/*
+ * 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.powermodel;
+
+import java.io.InputStream;
+import java.io.IOException;
+import com.android.powermodel.component.ModemBatteryStatsReader;
+
+public class BatteryStatsReader {
+    /**
+     * Construct a reader.
+     */
+    public BatteryStatsReader() {
+    }
+
+    /**
+     * Parse a powermodel.xml file and return a PowerProfile object.
+     *
+     * @param stream An InputStream containing the batterystats output.
+     *
+     * @throws ParseException Thrown when the xml file can not be parsed.
+     * @throws IOException When there is a problem reading the stream.
+     */
+    public static ActivityReport parse(InputStream stream) throws ParseException, IOException {
+        final Parser parser = new Parser(stream);
+        return parser.parse();
+    }
+
+    /**
+     * Implements the reading and power model logic.
+     */
+    private static class Parser {
+        final InputStream mStream;
+        final ActivityReport mResult;
+        RawBatteryStats mBs;
+
+        /**
+         * Constructor to capture the parameters to read.
+         */
+        Parser(InputStream stream) {
+            mStream = stream;
+            mResult = new ActivityReport();
+        }
+
+        /**
+         * Read the stream, parse it, and apply the power model.
+         * Do not call this more than once.
+         */
+        ActivityReport parse() throws ParseException, IOException {
+            mBs = RawBatteryStats.parse(mStream);
+
+            final ActivityReport.Builder report = new ActivityReport.Builder();
+
+            report.addActivity(Component.MODEM, ModemBatteryStatsReader.createActivities(mBs));
+
+            return report.build();
+        }
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/Component.java b/tools/powermodel/src/com/android/powermodel/Component.java
new file mode 100644
index 0000000..baae6d7
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/Component.java
@@ -0,0 +1,34 @@
+/*
+ * 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.powermodel;
+
+/**
+ * The hardware components that use power on a device.
+ */
+public enum Component {
+    CPU,
+    SCREEN,
+    MODEM,
+    WIFI,
+    BLUETOOTH,
+    VIDEO,
+    AUDIO,
+    FLASHLIGHT,
+    CAMERA,
+    GPS,
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/ComponentActivity.java b/tools/powermodel/src/com/android/powermodel/ComponentActivity.java
new file mode 100644
index 0000000..c1e2662
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ComponentActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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.powermodel;
+
+
+/**
+ * Encapsulates the work done by an app (including synthetic apps) that costs power.
+ */
+public class ComponentActivity {
+    public AttributionKey attribution;
+
+    protected ComponentActivity(AttributionKey attribution) {
+        this.attribution = attribution;
+    }
+
+    // TODO: Can we refactor what goes into the activities so this function
+    // doesn't need the global state?
+    /**
+     * Apply the power profile for this component.  Subclasses should implement this
+     * to do the per-component calculatinos.  The default implementation returns null.
+     * If this method returns null, then there will be no power associated for this
+     * component, which, for example is true with some of the GLOBAL activities.
+     */
+    public ComponentPower applyProfile(ActivityReport activityReport, PowerProfile profile) {
+        return null;
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/ComponentPower.java b/tools/powermodel/src/com/android/powermodel/ComponentPower.java
new file mode 100644
index 0000000..b22ff87
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ComponentPower.java
@@ -0,0 +1,41 @@
+/*
+ * 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.powermodel;
+
+/**
+ * The hardware component that uses power on a device.
+ * <p>
+ * This base class contains the total power used by each Component in an app.
+ * Subclasses may add more detail, which is a drill-down, but is not to be
+ * <i>added</i> to {@link #powerMah}.
+ */
+public abstract class ComponentPower<ACTIVITY extends ComponentActivity> {
+    /**
+     * The app associated with this ComponentPower.
+     */
+    public AttributionKey attribution;
+
+    /**
+     * The app activity that resulted in the power usage for this component.
+     */
+    public ACTIVITY activity;
+
+    /**
+     * The total power used by this component in this app.
+     */
+    public double powerMah;
+}
diff --git a/tools/powermodel/src/com/android/powermodel/ComponentProfile.java b/tools/powermodel/src/com/android/powermodel/ComponentProfile.java
new file mode 100644
index 0000000..e76e5fb
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ComponentProfile.java
@@ -0,0 +1,20 @@
+/*
+ * 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.powermodel;
+
+public class ComponentProfile {
+}
diff --git a/tools/powermodel/src/com/android/powermodel/CsvParser.java b/tools/powermodel/src/com/android/powermodel/CsvParser.java
new file mode 100644
index 0000000..78cd261
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/CsvParser.java
@@ -0,0 +1,173 @@
+/*
+ * 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.powermodel;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Parses CSV.
+ * <p>
+ * Call parse() with an InputStream.
+ * <p>
+ * CsvLineProcessor.onLine() will be called for each line in the source document.
+ * <p>
+ * To simplify parsing and to protect against using too much memory for bad
+ * data, the maximum field length is {@link #MAX_FIELD_SIZE}.
+ */
+class CsvParser {
+    /**
+     * The maximum size of a single field in bytes.
+     */
+    public static final int MAX_FIELD_SIZE = (8*1024)-1;
+
+    /**
+     * Callback interface for each line of CSV as it is parsed.
+     */
+    interface LineProcessor {
+        /**
+         * A line of CSV was parsed.
+         * 
+         * @param lineNumber the line number in the file, starting at 1
+         * @param fields the comma separated fields for the line
+         */
+        void onLine(int lineNumber, ArrayList<String> fields) throws ParseException;
+    }
+
+    /**
+     * Parse the CSV text in input, calling onto processor for each row.
+     */
+    public static void parse(InputStream input, LineProcessor processor)
+            throws IOException, ParseException {
+        final Charset utf8 = StandardCharsets.UTF_8;
+        final byte[] buf = new byte[MAX_FIELD_SIZE+1];
+        int lineNumber = 1;
+        int readPos = 0;
+        int prev = 0;
+        ArrayList<String> fields = new ArrayList<String>();
+        boolean finalBuffer = false;
+        boolean escaping = false;
+        boolean sawQuote = false;
+
+        while (!finalBuffer) {
+            int amt = input.read(buf, readPos, buf.length-readPos);
+            if (amt < 0) {
+                // No more data. Process whatever's left from before.
+                amt = readPos;
+                finalBuffer = true;
+            } else {
+                // Process whatever's left from before, plus the new data.
+                amt += readPos;
+                finalBuffer = false;
+            }
+
+            // Process as much of this buffer as we can.
+            int fieldStart = 0;
+            int index = readPos;
+            int escapeIndex = escaping ? readPos : -1;
+            while (index < amt) {
+                byte c = buf[index];
+                if (c == '\r' || c == '\n') {
+                    if (escaping) {
+                        // TODO: Quotes do not escape newlines in our CSV dialect,
+                        // but we actually see some data where it should.
+                        fields.add(new String(buf, fieldStart, escapeIndex-fieldStart));
+                        escapeIndex = -1;
+                        escaping = false;
+                        sawQuote = false;
+                    } else {
+                        fields.add(new String(buf, fieldStart, index-fieldStart));
+                    }
+                    // Don't report blank lines
+                    if (fields.size() > 1 || (fields.size() == 1 && fields.get(0).length() > 0)) {
+                        processor.onLine(lineNumber, fields);
+                    }
+                    fields = new ArrayList<String>();
+                    if (!(c == '\n' && prev == '\r')) {
+                        // Don't double increment for dos line endings.
+                        lineNumber++;
+                    }
+                    fieldStart = index = index + 1;
+                } else {
+                    if (escaping) {
+                        // Field started with a " so quotes are escaped with " and commas
+                        // don't matter except when following a single quote.
+                        if (c == '"') {
+                            if (sawQuote) {
+                                buf[escapeIndex] = buf[index];
+                                escapeIndex++;
+                                sawQuote = false;
+                            } else {
+                                sawQuote = true;
+                            }
+                            index++;
+                        } else if (sawQuote && c == ',') {
+                            fields.add(new String(buf, fieldStart, escapeIndex-fieldStart));
+                            fieldStart = index = index + 1;
+                            escapeIndex = -1;
+                            escaping = false;
+                            sawQuote = false;
+                        } else {
+                            buf[escapeIndex] = buf[index];
+                            escapeIndex++;
+                            index++;
+                            sawQuote = false;
+                        }
+                    } else {
+                        if (c == ',') {
+                            fields.add(new String(buf, fieldStart, index-fieldStart));
+                            fieldStart = index + 1;
+                        } else if (c == '"' && fieldStart == index) {
+                            // First character is a "
+                            escaping = true;
+                            fieldStart = escapeIndex = index + 1;
+                        }
+                        index++;
+                    }
+                }
+                prev = c;
+            }
+
+            // A single field is greater than buf.length, so fail.
+            if (fieldStart == 0 && index == buf.length) {
+                throw new ParseException(lineNumber, "Line is too long: "
+                        + new String(buf, 0, 20, utf8) + "...");
+            }
+
+            // Move whatever we didn't process to the beginning of the buffer
+            // and try again.
+            if (fieldStart != amt) {
+                readPos = (escaping ? escapeIndex : index) - fieldStart;
+                System.arraycopy(buf, fieldStart, buf, 0, readPos);
+            } else {
+                readPos = 0;
+            }
+        
+            // Process whatever's left over
+            if (finalBuffer) {
+                fields.add(new String(buf, 0, readPos));
+                // If there is any content, return the last line.
+                if (fields.size() > 1 || (fields.size() == 1 && fields.get(0).length() > 0)) {
+                    processor.onLine(lineNumber, fields);
+                }
+            }
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/ParseException.java b/tools/powermodel/src/com/android/powermodel/ParseException.java
new file mode 100644
index 0000000..e1f232b
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/ParseException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.powermodel;
+
+public class ParseException extends Exception {
+    public final int line;
+
+    public ParseException(int line, String message, Throwable th) {
+        super(message, th);
+        this.line = line;
+    }
+
+    public ParseException(int line, String message) {
+        this(line, message, null);
+    }
+
+    public ParseException(String message) {
+        this(0, message, null);
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/PowerProfile.java b/tools/powermodel/src/com/android/powermodel/PowerProfile.java
new file mode 100644
index 0000000..373a9c9
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/PowerProfile.java
@@ -0,0 +1,527 @@
+/*
+ * 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.powermodel;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import com.android.powermodel.component.AudioProfile;
+import com.android.powermodel.component.BluetoothProfile;
+import com.android.powermodel.component.CameraProfile;
+import com.android.powermodel.component.CpuProfile;
+import com.android.powermodel.component.FlashlightProfile;
+import com.android.powermodel.component.GpsProfile;
+import com.android.powermodel.component.ModemProfile;
+import com.android.powermodel.component.ScreenProfile;
+import com.android.powermodel.component.VideoProfile;
+import com.android.powermodel.component.WifiProfile;
+import com.android.powermodel.util.Conversion;
+
+public class PowerProfile {
+
+    // Remaining fields from the android code for which the actual usage is unclear.
+    //   battery.capacity
+    //   bluetooth.controller.voltage
+    //   modem.controller.voltage
+    //   gps.voltage
+    //   wifi.controller.voltage
+    //   radio.on
+    //   radio.scanning
+    //   radio.active
+    //   memory.bandwidths
+    //   wifi.batchedscan
+    //   wifi.scan
+    //   wifi.on
+    //   wifi.active
+    //   wifi.controller.tx_levels
+
+    private static Pattern RE_CLUSTER_POWER = Pattern.compile("cpu.cluster_power.cluster([0-9]*)");
+    private static Pattern RE_CORE_SPEEDS = Pattern.compile("cpu.core_speeds.cluster([0-9]*)");
+    private static Pattern RE_CORE_POWER = Pattern.compile("cpu.core_power.cluster([0-9]*)");
+
+    private HashMap<Component, ComponentProfile> mComponents = new HashMap();
+
+    /**
+     * Which element we are currently parsing.
+     */
+    enum ElementState {
+        BEGIN,
+        TOP,
+        ITEM,
+        ARRAY,
+        VALUE
+    }
+
+    /**
+     * Implements the reading and power model logic.
+     */
+    private static class Parser {
+        private final InputStream mStream;
+        private final PowerProfile mResult;
+
+        // Builders for the ComponentProfiles.
+        private final AudioProfile mAudio = new AudioProfile();
+        private final BluetoothProfile mBluetooth = new BluetoothProfile();
+        private final CameraProfile mCamera = new CameraProfile();
+        private final CpuProfile.Builder mCpuBuilder = new CpuProfile.Builder();
+        private final FlashlightProfile mFlashlight = new FlashlightProfile();
+        private final GpsProfile.Builder mGpsBuilder = new GpsProfile.Builder();
+        private final ModemProfile.Builder mModemBuilder = new ModemProfile.Builder();
+        private final ScreenProfile mScreen = new ScreenProfile();
+        private final VideoProfile mVideo = new VideoProfile();
+        private final WifiProfile mWifi = new WifiProfile();
+
+        /**
+         * Constructor to capture the parameters to read.
+         */
+        Parser(InputStream stream) {
+            mStream = stream;
+            mResult = new PowerProfile();
+        }
+
+        /**
+         * Read the stream, parse it, and apply the power model.
+         * Do not call this more than once.
+         */
+        PowerProfile parse() throws ParseException {
+            final SAXParserFactory factory = SAXParserFactory.newInstance();
+            AndroidResourceHandler handler = null;
+            try {
+                final SAXParser saxParser = factory.newSAXParser();
+
+                handler = new AndroidResourceHandler() {
+                    @Override
+                    public void onItem(Locator locator, String name, float value)
+                            throws SAXParseException {
+                        Parser.this.onItem(locator, name, value);
+                    }
+
+                    @Override
+                    public void onArray(Locator locator, String name, float[] value)
+                            throws SAXParseException {
+                        Parser.this.onArray(locator, name, value);
+                    }
+                };
+
+                saxParser.parse(mStream, handler);
+            } catch (ParserConfigurationException ex) {
+                // Coding error, not runtime error.
+                throw new RuntimeException(ex);
+            } catch (SAXParseException ex) {
+                throw new ParseException(ex.getLineNumber(), ex.getMessage(), ex);
+            } catch (SAXException | IOException ex) {
+                // Make a guess about the line number.
+                throw new ParseException(handler.getLineNumber(), ex.getMessage(), ex);
+            }
+
+            // TODO: This doesn't cover the multiple algorithms. Some refactoring will
+            // be necessary.
+            mResult.mComponents.put(Component.AUDIO, mAudio);
+            mResult.mComponents.put(Component.BLUETOOTH, mBluetooth);
+            mResult.mComponents.put(Component.CAMERA, mCamera);
+            mResult.mComponents.put(Component.CPU, mCpuBuilder.build());
+            mResult.mComponents.put(Component.FLASHLIGHT, mFlashlight);
+            mResult.mComponents.put(Component.GPS, mGpsBuilder.build());
+            mResult.mComponents.put(Component.MODEM, mModemBuilder.build());
+            mResult.mComponents.put(Component.SCREEN, mScreen);
+            mResult.mComponents.put(Component.VIDEO, mVideo);
+            mResult.mComponents.put(Component.WIFI, mWifi);
+
+            return mResult;
+        }
+
+        /**
+         * Handles an item tag in the power_profile.xml.
+         */
+        public void onItem(Locator locator, String name, float value) throws SAXParseException {
+            Integer index;
+            try {
+                if ("ambient.on".equals(name)) {
+                    mScreen.ambientMa = value;
+                } else if ("audio".equals(name)) {
+                    mAudio.onMa = value;
+                } else if ("bluetooth.controller.idle".equals(name)) {
+                    mBluetooth.idleMa = value;
+                } else if ("bluetooth.controller.rx".equals(name)) {
+                    mBluetooth.rxMa = value;
+                } else if ("bluetooth.controller.tx".equals(name)) {
+                    mBluetooth.txMa = value;
+                } else if ("camera.avg".equals(name)) {
+                    mCamera.onMa = value;
+                } else if ("camera.flashlight".equals(name)) {
+                    mFlashlight.onMa = value;
+                } else if ("cpu.suspend".equals(name)) {
+                    mCpuBuilder.setSuspendMa(value);
+                } else if ("cpu.idle".equals(name)) {
+                    mCpuBuilder.setIdleMa(value);
+                } else if ("cpu.active".equals(name)) {
+                    mCpuBuilder.setActiveMa(value);
+                } else if ((index = matchIndexedRegex(locator, RE_CLUSTER_POWER, name)) != null) {
+                    mCpuBuilder.setClusterPower(index, value);
+                } else if ("gps.on".equals(name)) {
+                    mGpsBuilder.setOnMa(value);
+                } else if ("modem.controller.sleep".equals(name)) {
+                    mModemBuilder.setSleepMa(value);
+                } else if ("modem.controller.idle".equals(name)) {
+                    mModemBuilder.setIdleMa(value);
+                } else if ("modem.controller.rx".equals(name)) {
+                    mModemBuilder.setRxMa(value);
+                } else if ("radio.scanning".equals(name)) {
+                    mModemBuilder.setScanningMa(value);
+                } else if ("screen.on".equals(name)) {
+                    mScreen.onMa = value;
+                } else if ("screen.full".equals(name)) {
+                    mScreen.fullMa = value;
+                } else if ("video".equals(name)) {
+                    mVideo.onMa = value;
+                } else if ("wifi.controller.idle".equals(name)) {
+                    mWifi.idleMa = value;
+                } else if ("wifi.controller.rx".equals(name)) {
+                    mWifi.rxMa = value;
+                } else if ("wifi.controller.tx".equals(name)) {
+                    mWifi.txMa = value;
+                } else {
+                    // TODO: Uncomment this when we have all of the items parsed.
+                    // throw new SAXParseException("Unhandled <item name=\"" + name + "\"> element",
+                    //        locator, ex);
+
+                }
+            } catch (ParseException ex) {
+                throw new SAXParseException(ex.getMessage(), locator, ex);
+            }
+        }
+
+        /**
+         * Handles an array tag in the power_profile.xml.
+         */
+        public void onArray(Locator locator, String name, float[] value) throws SAXParseException {
+            Integer index;
+            try {
+                if ("cpu.clusters.cores".equals(name)) {
+                    mCpuBuilder.setCoreCount(Conversion.toIntArray(value));
+                } else if ((index = matchIndexedRegex(locator, RE_CORE_SPEEDS, name)) != null) {
+                    mCpuBuilder.setCoreSpeeds(index, Conversion.toIntArray(value));
+                } else if ((index = matchIndexedRegex(locator, RE_CORE_POWER, name)) != null) {
+                    mCpuBuilder.setCorePower(index, value);
+                } else if ("gps.signalqualitybased".equals(name)) {
+                    mGpsBuilder.setSignalMa(value);
+                } else if ("modem.controller.tx".equals(name)) {
+                    mModemBuilder.setTxMa(value);
+                } else {
+                    // TODO: Uncomment this when we have all of the items parsed.
+                    // throw new SAXParseException("Unhandled <item name=\"" + name + "\"> element",
+                    //        locator, ex);
+                }
+            } catch (ParseException ex) {
+                throw new SAXParseException(ex.getMessage(), locator, ex);
+            }
+        }
+    }
+
+    /**
+     * SAX XML handler that can parse the android resource files.
+     * In our case, all elements are floats.
+     */
+    abstract static class AndroidResourceHandler extends DefaultHandler {
+        /**
+         * The set of names already processed. Map of name to line number.
+         */
+        private HashMap<String,Integer> mAlreadySeen = new HashMap<String,Integer>();
+
+        /**
+         * Where in the document we are parsing.
+         */
+        private Locator mLocator;
+
+        /**
+         * Which element we are currently parsing.
+         */
+        private ElementState mState = ElementState.BEGIN;
+
+        /**
+         * Saved name from item and array elements.
+         */
+        private String mName;
+
+        /**
+         * The text that is currently being captured, or null if {@link #startCapturingText()}
+         * has not been called.
+         */
+        private StringBuilder mText;
+
+        /**
+         * The array values that have been parsed so for for this array. Null if we are
+         * not inside an array tag.
+         */
+        private ArrayList<Float> mArray;
+
+        /**
+         * Called when an item tag is encountered.
+         */
+        public abstract void onItem(Locator locator, String name, float value)
+                throws SAXParseException;
+
+        /**
+         * Called when an array is encountered.
+         */
+        public abstract void onArray(Locator locator, String name, float[] value)
+                throws SAXParseException;
+
+        /**
+         * If we have a Locator set, return the line number, otherwise return 0.
+         */
+        public int getLineNumber() {
+            return mLocator != null ? mLocator.getLineNumber() : 0;
+        }
+
+        /**
+         * Handle setting the parse location object.
+         */
+        public void setDocumentLocator(Locator locator) {
+            mLocator = locator;
+        }
+
+        /**
+         * Handle beginning of an element.
+         *
+         * @param ns Namespace uri
+         * @param ln Local name (inside namespace)
+         * @param element Tag name
+         */
+        @Override
+        public void startElement(String ns, String ln, String element,
+                Attributes attr) throws SAXException {
+            switch (mState) {
+                case BEGIN:
+                    // Outer element, we don't care the tag name.
+                    mState = ElementState.TOP;
+                    return;
+                case TOP:
+                    if ("item".equals(element)) {
+                        mState = ElementState.ITEM;
+                        saveNameAttribute(attr);
+                        startCapturingText();
+                        return;
+                    } else if ("array".equals(element)) {
+                        mState = ElementState.ARRAY;
+                        mArray = new ArrayList<Float>();
+                        saveNameAttribute(attr);
+                        return;
+                    }
+                    break;
+                case ARRAY:
+                    if ("value".equals(element)) {
+                        mState = ElementState.VALUE;
+                        startCapturingText();
+                        return;
+                    }
+                    break;
+            }
+            throw new SAXParseException("unexpected element: '" + element + "'", mLocator);
+        }
+
+        /**
+         * Handle end of an element.
+         *
+         * @param ns Namespace uri
+         * @param ln Local name (inside namespace)
+         * @param element Tag name
+         */
+        @Override
+        public void endElement(String ns, String ln, String element) throws SAXException {
+            switch (mState) {
+                case ITEM: {
+                    float value = parseFloat(finishCapturingText());
+                    mState = ElementState.TOP;
+                    onItem(mLocator, mName, value);
+                    break;
+                }
+                case ARRAY: {
+                    final int N = mArray.size();
+                    float[] values = new float[N];
+                    for (int i=0; i<N; i++) {
+                        values[i] = mArray.get(i);
+                    }
+                    mArray = null;
+                    mState = ElementState.TOP;
+                    onArray(mLocator, mName, values);
+                    break;
+                }
+                case VALUE: {
+                    mArray.add(parseFloat(finishCapturingText()));
+                    mState = ElementState.ARRAY;
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Interstitial text received.
+         *
+         * @throws SAXException if there shouldn't be non-whitespace text here
+         */
+        @Override
+        public void characters(char text[], int start, int length) throws SAXException {
+            if (mText == null && length > 0 && !isWhitespace(text, start, length)) {
+                throw new SAXParseException("unexpected text: '"
+                        + firstLine(text, start, length).trim() + "'", mLocator);
+            }
+            if (mText != null) {
+                mText.append(text, start, length);
+            }
+        }
+
+        /**
+         * Begin collecting text from inside an element.
+         */
+        private void startCapturingText() {
+            if (mText != null) {
+                throw new RuntimeException("ASSERTION FAILED: Shouldn't be already capturing"
+                        + " text. mState=" + mState.name()
+                        + " line=" + mLocator.getLineNumber()
+                        + " column=" + mLocator.getColumnNumber());
+            }
+            mText = new StringBuilder();
+        }
+
+        /**
+         * Stop capturing text from inside an element.
+         *
+         * @return the captured text
+         */
+        private String finishCapturingText() {
+            if (mText == null) {
+                throw new RuntimeException("ASSERTION FAILED: Should already be capturing"
+                        + " text. mState=" + mState.name()
+                        + " line=" + mLocator.getLineNumber()
+                        + " column=" + mLocator.getColumnNumber());
+            }
+            final String result = mText.toString().trim();
+            mText = null;
+            return result;
+        }
+
+        /**
+         * Get the "name" attribute.
+         *
+         * @throws SAXParseException if the name attribute is not present or if
+         * the name has already been seen in the file.
+         */
+        private void saveNameAttribute(Attributes attr) throws SAXParseException {
+            final String name = attr.getValue("name");
+            if (name == null) {
+                throw new SAXParseException("expected 'name' attribute", mLocator);
+            }
+            Integer prev = mAlreadySeen.put(name, mLocator.getLineNumber());
+            if (prev != null) {
+                throw new SAXParseException("name '" + name + "' already seen on line: " + prev,
+                        mLocator);
+            }
+            mName = name;
+        }
+
+        /**
+         * Gets the float value of the string.
+         *
+         * @throws SAXParseException if 'text' can't be parsed as a float.
+         */
+        private float parseFloat(String text) throws SAXParseException {
+            try {
+                return Float.parseFloat(text);
+            } catch (NumberFormatException ex) {
+                throw new SAXParseException("not a valid float value: '" + text + "'",
+                        mLocator, ex);
+            }
+        }
+    }
+
+    /**
+     * Return whether the given substring is all whitespace.
+     */
+    private static boolean isWhitespace(char[] text, int start, int length) {
+        for (int i = start; i < (start + length); i++) {
+            if (!Character.isSpace(text[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Return the contents of text up to the first newline.
+     */
+    private static String firstLine(char[] text, int start, int length) {
+        // TODO: The line number will be wrong if we skip preceeding blank lines.
+        while (length > 0) {
+            if (Character.isSpace(text[start])) {
+                start++;
+                length--;
+            }
+        }
+        int newlen = 0;
+        for (; newlen < length; newlen++) {
+            final char c = text[newlen];
+            if (c == '\n' || c == '\r') {
+                break;
+            }
+        }
+        return new String(text, start, newlen);
+    }
+
+    /**
+     * If the pattern matches, return the first group of that as an Integer.
+     * If not return null.
+     */
+    private static Integer matchIndexedRegex(Locator locator, Pattern pattern, String text)
+            throws SAXParseException {
+        final Matcher m = pattern.matcher(text);
+        if (m.matches()) {
+            try {
+                return Integer.parseInt(m.group(1));
+            } catch (NumberFormatException ex) {
+                throw new SAXParseException("Invalid field name: '" + text + "'", locator, ex);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public static PowerProfile parse(InputStream stream) throws ParseException {
+        return (new Parser(stream)).parse();
+    }
+
+    private PowerProfile() {
+    }
+
+    public ComponentProfile getComponent(Component component) {
+        return mComponents.get(component);
+    }
+
+}
diff --git a/tools/powermodel/src/com/android/powermodel/PowerReport.java b/tools/powermodel/src/com/android/powermodel/PowerReport.java
new file mode 100644
index 0000000..76ba672
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/PowerReport.java
@@ -0,0 +1,101 @@
+/*
+ * 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.powermodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * PowerReport contains the summary of all power used on a device
+ * as reported by batterystats or statsd, based on the power profile.
+ */
+public class PowerReport {
+    private AppList<AppPower> mApps;
+    private double mTotalPowerMah;
+
+    private PowerReport() {
+    }
+
+    /**
+     * The total power used by this device for this PowerReport.
+     */
+    public double getTotalPowerMah() {
+        return mTotalPowerMah;
+    }
+
+    public List<AppPower> getAllApps() {
+        return mApps.getAllApps();
+    }
+
+    public List<AppPower> getRegularApps() {
+        return mApps.getRegularApps();
+    }
+
+    public List<AppPower> findApp(String pkg) {
+        return mApps.findApp(pkg);
+    }
+
+    public AppPower findApp(SpecialApp specialApp) {
+        return mApps.findApp(specialApp);
+    }
+
+    public static PowerReport createReport(PowerProfile profile, ActivityReport activityReport) {
+        final PowerReport.Builder powerReport = new PowerReport.Builder();
+        for (final AppActivity appActivity: activityReport.getAllApps()) {
+            final AppPower.Builder appPower = new AppPower.Builder();
+            appPower.setAttribution(appActivity.getAttribution());
+
+            for (final ImmutableMap.Entry<Component,ComponentActivity> entry:
+                    appActivity.getComponentActivities().entrySet()) {
+                final ComponentPower componentPower = entry.getValue()
+                        .applyProfile(activityReport, profile);
+                if (componentPower != null) {
+                    appPower.addComponentPower(entry.getKey(), componentPower);
+                }
+            }
+
+            powerReport.add(appPower);
+        }
+        return powerReport.build();
+    }
+
+    private static class Builder {
+        private AppList.Builder mApps = new AppList.Builder();
+
+        public Builder() {
+        }
+
+        public PowerReport build() {
+            final PowerReport report = new PowerReport();
+
+            report.mApps = mApps.build();
+
+            for (AppPower app: report.mApps.getAllApps()) {
+                report.mTotalPowerMah += app.getAppPowerMah();
+            }
+
+            return report;
+        }
+
+        public void add(AppPower.Builder app) {
+            mApps.put(app.getAttribution(), app);
+        }
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/RawBatteryStats.java b/tools/powermodel/src/com/android/powermodel/RawBatteryStats.java
new file mode 100644
index 0000000..76c0482
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/RawBatteryStats.java
@@ -0,0 +1,1175 @@
+/*
+ * 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.powermodel;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class RawBatteryStats {
+    /**
+     * The factory objects for the records. Initialized in the static block.
+     */
+    private static HashMap<String,RecordFactory> sFactories
+            = new HashMap<String,RecordFactory>();
+
+    /**
+     * The Record objects that have been parsed.
+     */
+    private ArrayList<Record> mRecords = new ArrayList<Record>();
+
+    /**
+     * The Record objects that have been parsed, indexed by type.
+     *
+     * Don't use this before {@link #indexRecords()} has been called.
+     */
+    private ImmutableMap<String,ImmutableList<Record>> mRecordsByType;
+
+    /**
+     * The attribution keys for which we have data (corresponding to UIDs we've seen).
+     * <p>
+     * Does not include the synthetic apps.
+     * <p>
+     * Don't use this before {@link #indexRecords()} has been called.
+     */
+    private ImmutableSet<AttributionKey> mApps;
+
+    /**
+     * The warnings that have been issued during parsing.
+     */
+    private ArrayList<Warning> mWarnings = new ArrayList<Warning>();
+
+    /**
+     * The version of the BatteryStats dumpsys that we are using.  This value
+     * is set to -1 initially, and then when parsing the (hopefully) first
+     * line, 'vers', it is set to the correct version.
+     */
+    private int mDumpsysVersion = -1;
+
+    /**
+     * Enum used in the Line annotation to mark whether a field is expected to be
+     * system-wide or scoped to an app.
+     */
+    public enum Scope {
+        SYSTEM,
+        UID
+    }
+
+    /**
+     * Enum used to indicated the expected number of results.
+     */
+    public enum Count {
+        SINGLE,
+        MULTIPLE
+    }
+
+    /**
+     * Annotates classes that represent a line of CSV in the batterystats CSV
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.TYPE)
+    @interface Line {
+        String tag();
+        Scope scope();
+        Count count();
+    }
+
+    /**
+     * Annotates fields that should be parsed automatically from CSV
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.FIELD)
+    @interface Field {
+        /**
+         * The "column" of this field in the most recent version of the CSV.
+         * When parsing old versions, fields that were added will be automatically
+         * removed and the indices will be fixed up.
+         *
+         * The header fields (version, uid, category, type) will be automatically
+         * handled for the base Line type.  The index 0 should start after those.
+         */
+        int index();
+
+        /**
+         * First version that this field appears in.
+         */
+        int added() default 0;
+    }
+
+    /**
+     * Each line in the BatteryStats CSV is tagged with a category, that says
+     * which of the time collection modes was used for the data.
+     */
+    public enum Category {
+        INFO("i"),
+        LAST("l"),
+        UNPLUGGED("u"),
+        CURRENT("c");
+
+        public final String tag;
+
+        Category(String tag) {
+            this.tag = tag;
+        }
+    }
+
+    /**
+     * Base class for all lines in a batterystats CSV file.
+     */
+    public static class Record {
+        /**
+         * Whether all of the fields for the indicated version of this record
+         * have been filled in.
+         */
+        public boolean complete;
+
+
+        @Field(index=-4)
+        public int lineVersion;
+
+        @Field(index=-3)
+        public int uid;
+
+        @Field(index=-2)
+        public Category category;
+
+        @Field(index=-1)
+        public String lineType;
+    }
+
+    @Line(tag="bt", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class Battery extends Record {
+        // If which != STATS_SINCE_CHARGED, the csv will be "N/A" and we will get
+        // a parsing warning.  Nobody uses anything other than STATS_SINCE_CHARGED.
+        @Field(index=0)
+        public int startCount;
+
+        @Field(index=1)
+        public long whichBatteryRealtimeMs;
+
+        @Field(index=2)
+        public long whichBatteryUptimeMs;
+
+        @Field(index=3)
+        public long totalRealtimeMs;
+
+        @Field(index=4)
+        public long totalUptimeMs;
+
+        @Field(index=5)
+        public long getStartClockTimeMs;
+
+        @Field(index=6)
+        public long whichBatteryScreenOffRealtimeMs;
+
+        @Field(index=7)
+        public long whichBatteryScreenOffUptimeMs;
+
+        @Field(index=8)
+        public long estimatedBatteryCapacityMah;
+
+        @Field(index=9)
+        public long minLearnedBatteryCapacityMah;
+
+        @Field(index=10)
+        public long maxLearnedBatteryCapacityMah;
+
+        @Field(index=11)
+        public long screenDozeTimeMs;
+    }
+
+    @Line(tag="gn", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class GlobalNetwork extends Record {
+        @Field(index=0)
+        public long mobileRxTotalBytes;
+
+        @Field(index=1)
+        public long mobileTxTotalBytes;
+
+        @Field(index=2)
+        public long wifiRxTotalBytes;
+
+        @Field(index=3)
+        public long wifiTxTotalBytes;
+
+        @Field(index=4)
+        public long mobileRxTotalPackets;
+
+        @Field(index=5)
+        public long mobileTxTotalPackets;
+
+        @Field(index=6)
+        public long wifiRxTotalPackets;
+
+        @Field(index=7)
+        public long wifiTxTotalPackets;
+
+        @Field(index=8)
+        public long btRxTotalBytes;
+
+        @Field(index=9)
+        public long btTxTotalBytes;
+    }
+
+    @Line(tag="gmcd", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class GlobalModemController extends Record {
+        @Field(index=0)
+        public long idleMs;
+
+        @Field(index=1)
+        public long rxTimeMs;
+
+        @Field(index=2)
+        public long powerMaMs;
+
+        @Field(index=3)
+        public long[] txTimeMs;
+    }
+
+    @Line(tag="m", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class Misc extends Record {
+        @Field(index=0)
+        public long screenOnTimeMs;
+
+        @Field(index=1)
+        public long phoneOnTimeMs;
+
+        @Field(index=2)
+        public long fullWakeLockTimeTotalMs;
+
+        @Field(index=3)
+        public long partialWakeLockTimeTotalMs;
+
+        @Field(index=4)
+        public long mobileRadioActiveTimeMs;
+
+        @Field(index=5)
+        public long mobileRadioActiveAdjustedTimeMs;
+
+        @Field(index=6)
+        public long interactiveTimeMs;
+
+        @Field(index=7)
+        public long powerSaveModeEnabledTimeMs;
+
+        @Field(index=8)
+        public int connectivityChangeCount;
+
+        @Field(index=9)
+        public long deepDeviceIdleModeTimeMs;
+
+        @Field(index=10)
+        public long deepDeviceIdleModeCount;
+
+        @Field(index=11)
+        public long deepDeviceIdlingTimeMs;
+
+        @Field(index=12)
+        public long deepDeviceIdlingCount;
+
+        @Field(index=13)
+        public long mobileRadioActiveCount;
+
+        @Field(index=14)
+        public long mobileRadioActiveUnknownTimeMs;
+
+        @Field(index=15)
+        public long lightDeviceIdleModeTimeMs;
+
+        @Field(index=16)
+        public long lightDeviceIdleModeCount;
+
+        @Field(index=17)
+        public long lightDeviceIdlingTimeMs;
+
+        @Field(index=18)
+        public long lightDeviceIdlingCount;
+
+        @Field(index=19)
+        public long lightLongestDeviceIdleModeTimeMs;
+
+        @Field(index=20)
+        public long deepLongestDeviceIdleModeTimeMs;
+    }
+
+    @Line(tag="nt", scope=Scope.UID, count=Count.SINGLE)
+    public static class Network extends Record {
+        @Field(index=0)
+        public long mobileRxBytes;
+
+        @Field(index=1)
+        public long mobileTxBytes;
+
+        @Field(index=2)
+        public long wifiRxBytes;
+
+        @Field(index=3)
+        public long wifiTxBytes;
+
+        @Field(index=4)
+        public long mobileRxPackets;
+
+        @Field(index=5)
+        public long mobileTxPackets;
+
+        @Field(index=6)
+        public long wifiRxPackets;
+
+        @Field(index=7)
+        public long wifiTxPackets;
+
+        // This is microseconds, because... batterystats.
+        @Field(index=8)
+        public long mobileRadioActiveTimeUs;
+
+        @Field(index=9)
+        public long mobileRadioActiveCount;
+
+        @Field(index=10)
+        public long btRxBytes;
+
+        @Field(index=11)
+        public long btTxBytes;
+
+        @Field(index=12)
+        public long mobileWakeupCount;
+
+        @Field(index=13)
+        public long wifiWakeupCount;
+
+        @Field(index=14)
+        public long mobileBgRxBytes;
+
+        @Field(index=15)
+        public long mobileBgTxBytes;
+
+        @Field(index=16)
+        public long wifiBgRxBytes;
+
+        @Field(index=17)
+        public long wifiBgTxBytes;
+
+        @Field(index=18)
+        public long mobileBgRxPackets;
+
+        @Field(index=19)
+        public long mobileBgTxPackets;
+
+        @Field(index=20)
+        public long wifiBgRxPackets;
+
+        @Field(index=21)
+        public long wifiBgTxPackets;
+    }
+
+    @Line(tag="sgt", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class SignalStrengthTime extends Record {
+        @Field(index=0)
+        public long[] phoneSignalStrengthTimeMs;
+    }
+
+    @Line(tag="sst", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class SignalScanningTime extends Record {
+        @Field(index=0)
+        public long phoneSignalScanningTimeMs;
+    }
+
+    @Line(tag="uid", scope=Scope.UID, count=Count.MULTIPLE)
+    public static class Uid extends Record {
+        @Field(index=0)
+        public int uidKey;
+
+        @Field(index=1)
+        public String pkg;
+    }
+
+    @Line(tag="vers", scope=Scope.SYSTEM, count=Count.SINGLE)
+    public static class Version extends Record {
+        @Field(index=0)
+        public int dumpsysVersion;
+
+        @Field(index=1)
+        public int parcelVersion;
+
+        @Field(index=2)
+        public String startPlatformVersion;
+
+        @Field(index=3)
+        public String endPlatformVersion;
+    }
+
+    /**
+     * Codes for the warnings to classify warnings without parsing them.
+     */
+    public enum WarningId {
+        /**
+         * A row didn't have enough fields to match any records and let us extract
+         * a line type.
+         */
+        TOO_FEW_FIELDS_FOR_LINE_TYPE,
+
+        /**
+         * We couldn't find a Record for the given line type.
+         */
+        NO_MATCHING_LINE_TYPE,
+
+        /**
+         * Couldn't set the value of a field. Usually this is because the
+         * contents of a numeric type couldn't be parsed.
+         */
+        BAD_FIELD_TYPE,
+
+        /**
+         * There were extra field values in the input text.
+         */
+        TOO_MANY_FIELDS,
+
+        /**
+         * There were fields that we were expecting (for this version
+         * of the dumpsys) that weren't provided in the CSV data.
+         */
+        NOT_ENOUGH_FIELDS,
+
+        /**
+         * The dumpsys version in the 'vers' CSV line couldn't be parsed.
+         */
+        BAD_DUMPSYS_VERSION
+    }
+
+    /**
+     * A non-fatal problem detected during parsing.
+     */
+    public static class Warning {
+        private int mLineNumber;
+        private WarningId mId;
+        private ArrayList<String> mFields;
+        private String mMessage;
+        private String[] mExtras;
+
+        public Warning(int lineNumber, WarningId id, ArrayList<String> fields, String message,
+                String[] extras) {
+            mLineNumber = lineNumber;
+            mId = id;
+            mFields = fields;
+            mMessage = message;
+            mExtras = extras;
+        }
+
+        public int getLineNumber() {
+            return mLineNumber;
+        }
+
+        public ArrayList<String> getFields() {
+            return mFields;
+        }
+
+        public String getMessage() {
+            return mMessage;
+        }
+
+        public String[] getExtras() {
+            return mExtras;
+        }
+    }
+
+    /**
+     * Base class for classes to set fields on Record objects via reflection.
+     */
+    private abstract static class FieldSetter {
+        private int mIndex;
+        private int mAdded;
+        protected java.lang.reflect.Field mField;
+
+        FieldSetter(int index, int added, java.lang.reflect.Field field) {
+            mIndex = index;
+            mAdded = added;
+            mField = field;
+        }
+
+        String getName() {
+            return mField.getName();
+        }
+
+        int getIndex() {
+            return mIndex;
+        }
+
+        int getAdded() {
+            return mAdded;
+        }
+
+        boolean isArray() {
+            return mField.getType().isArray();
+        }
+
+        abstract void setField(int lineNumber, Record object, String value) throws ParseException;
+        abstract void setArray(int lineNumber, Record object, ArrayList<String> values,
+                int startIndex, int endIndex) throws ParseException;
+
+        @Override
+        public String toString() {
+            final String className = getClass().getName();
+            int startIndex = Math.max(className.lastIndexOf('.'), className.lastIndexOf('$'));
+            if (startIndex < 0) {
+                startIndex = 0;
+            } else {
+                startIndex++;
+            }
+            return className.substring(startIndex) + "(index=" + mIndex + " added=" + mAdded
+                    + " field=" + mField.getName()
+                    + " type=" + mField.getType().getSimpleName()
+                    + ")";
+        }
+    }
+
+    /**
+     * Sets int fields on Record objects using reflection.
+     */
+    private static class IntFieldSetter extends FieldSetter {
+        IntFieldSetter(int index, int added, java.lang.reflect.Field field) {
+            super(index, added, field);
+        }
+
+        void setField(int lineNumber, Record object, String value) throws ParseException {
+            try {
+                mField.setInt(object, Integer.parseInt(value.trim()));
+            } catch (NumberFormatException ex) {
+                throw new ParseException(lineNumber, "can't parse as integer: " + value);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        void setArray(int lineNumber, Record object, ArrayList<String> values,
+                int startIndex, int endIndex) throws ParseException {
+            try {
+                final int[] array = new int[endIndex-startIndex];
+                for (int i=startIndex; i<endIndex; i++) {
+                    final String value = values.get(startIndex+i);
+                    try {
+                        array[i] = Integer.parseInt(value.trim());
+                    } catch (NumberFormatException ex) {
+                        throw new ParseException(lineNumber, "can't parse field "
+                                + i + " as integer: " + value);
+                    }
+                }
+                mField.set(object, array);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    /**
+     * Sets long fields on Record objects using reflection.
+     */
+    private static class LongFieldSetter extends FieldSetter {
+        LongFieldSetter(int index, int added, java.lang.reflect.Field field) {
+            super(index, added, field);
+        }
+
+        void setField(int lineNumber, Record object, String value) throws ParseException {
+            try {
+                mField.setLong(object, Long.parseLong(value.trim()));
+            } catch (NumberFormatException ex) {
+                throw new ParseException(lineNumber, "can't parse as long: " + value);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        void setArray(int lineNumber, Record object, ArrayList<String> values,
+                int startIndex, int endIndex) throws ParseException {
+            try {
+                final long[] array = new long[endIndex-startIndex];
+                for (int i=0; i<(endIndex-startIndex); i++) {
+                    final String value = values.get(startIndex+i);
+                    try {
+                        array[i] = Long.parseLong(value.trim());
+                    } catch (NumberFormatException ex) {
+                        throw new ParseException(lineNumber, "can't parse field "
+                                + i + " as long: " + value);
+                    }
+                }
+                mField.set(object, array);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    /**
+     * Sets String fields on Record objects using reflection.
+     */
+    private static class StringFieldSetter extends FieldSetter {
+        StringFieldSetter(int index, int added, java.lang.reflect.Field field) {
+            super(index, added, field);
+        }
+
+        void setField(int lineNumber, Record object, String value) throws ParseException {
+            try {
+                mField.set(object, value);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        void setArray(int lineNumber, Record object, ArrayList<String> values,
+                int startIndex, int endIndex) throws ParseException {
+            try {
+                final String[] array = new String[endIndex-startIndex];
+                for (int i=0; i<(endIndex-startIndex); i++) {
+                    array[i] = values.get(startIndex+1);
+                }
+                mField.set(object, array);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    /**
+     * Sets enum fields on Record objects using reflection.
+     *
+     * To be parsed automatically, enums must have a public final String tag
+     * field, which is the string that will appear in the csv for that enum value.
+     */
+    private static class EnumFieldSetter extends FieldSetter {
+        private final HashMap<String,Enum> mTags = new HashMap<String,Enum>();
+
+        EnumFieldSetter(int index, int added, java.lang.reflect.Field field) {
+            super(index, added, field);
+
+            // Build the mapping of tags to values.
+            final Class<?> fieldType = field.getType();
+
+            java.lang.reflect.Field tagField = null;
+            try {
+                tagField = fieldType.getField("tag");
+            } catch (NoSuchFieldException ex) {
+                throw new RuntimeException("Missing tag field."
+                        + " To be parsed automatically, enums must have"
+                        + " a String field called tag.  Enum class: " + fieldType.getName()
+                        + " Containing class: " + field.getDeclaringClass().getName()
+                        + " Field: " + field.getName());
+
+            }
+            if (!String.class.equals(tagField.getType())) {
+                throw new RuntimeException("Tag field is not string."
+                        + " To be parsed automatically, enums must have"
+                        + " a String field called tag.  Enum class: " + fieldType.getName()
+                        + " Containing class: " + field.getDeclaringClass().getName()
+                        + " Field: " + field.getName()
+                        + " Tag field type: " + tagField.getType().getName());
+            }
+
+            for (final Object enumValue: fieldType.getEnumConstants()) {
+                String tag = null;
+                try {
+                    tag = (String)tagField.get(enumValue);
+                } catch (IllegalAccessException | IllegalArgumentException
+                        | ExceptionInInitializerError ex) {
+                    throw new RuntimeException(ex);
+                }
+                mTags.put(tag, (Enum)enumValue);
+            }
+        }
+
+        void setField(int lineNumber, Record object, String value) throws ParseException {
+            final Enum enumValue = mTags.get(value);
+            if (enumValue == null) {
+                throw new ParseException(lineNumber, "Could not find enum for field "
+                        + getName() + " for tag: " + value);
+            }
+            try {
+                mField.set(object, enumValue);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        void setArray(int lineNumber, Record object, ArrayList<String> values,
+                int startIndex, int endIndex) throws ParseException {
+            try {
+                final Object array = Array.newInstance(mField.getType().getComponentType(),
+                        endIndex-startIndex);
+                for (int i=0; i<(endIndex-startIndex); i++) {
+                    final String value = values.get(startIndex+i);
+                    final Enum enumValue = mTags.get(value);
+                    if (enumValue == null) {
+                        throw new ParseException(lineNumber, "Could not find enum for field "
+                                + getName() + " for tag: " + value);
+                    }
+                    Array.set(array, i, enumValue);
+                }
+                mField.set(object, array);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | ExceptionInInitializerError ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    /**
+     * Factory for the record classes.  Uses reflection to create
+     * the fields.
+     */
+    private static class RecordFactory {
+        private String mTag;
+        private Class<? extends Record> mSubclass;
+        private ArrayList<FieldSetter> mFieldSetters;
+
+        RecordFactory(String tag, Class<? extends Record> subclass,
+                ArrayList<FieldSetter> fieldSetters) {
+            mTag = tag;
+            mSubclass = subclass;
+            mFieldSetters = fieldSetters;
+        }
+
+        /**
+         * Create an object of one of the subclasses of Record, and fill
+         * in the fields marked with the Field annotation.
+         *
+         * @return a new Record with the fields filled in. If there are missing
+         *      fields, the {@link Record.complete} field will be set to false.
+         */
+        Record create(RawBatteryStats bs, int dumpsysVersion, int lineNumber,
+                ArrayList<String> fieldValues) {
+            final boolean debug = false;
+            Record record = null;
+            try {
+                if (debug) {
+                    System.err.println("Creating object: " + mSubclass.getSimpleName());
+                }
+                record = mSubclass.newInstance();
+            } catch (IllegalAccessException | InstantiationException
+                    | ExceptionInInitializerError | SecurityException ex) {
+                throw new RuntimeException("Exception creating " + mSubclass.getName()
+                        + " for '" + mTag + "' record.", ex);
+            }
+            record.complete = true;
+            int fieldIndex = 0;
+            int setterIndex = 0;
+            while (fieldIndex < fieldValues.size() && setterIndex < mFieldSetters.size()) {
+                final FieldSetter setter = mFieldSetters.get(setterIndex);
+
+                if (dumpsysVersion >= 0 && dumpsysVersion < setter.getAdded()) {
+                    // The version being parsed doesn't have the field for this setter,
+                    // so skip the setter but not the field.
+                    setterIndex++;
+                    continue;
+                }
+
+                final String value = fieldValues.get(fieldIndex);
+                try {
+                    if (debug) {
+                        System.err.println("   setting field " + setter + " to: " + value);
+                    }
+                    if (setter.isArray()) {
+                        setter.setArray(lineNumber, record, fieldValues,
+                                fieldIndex, fieldValues.size());
+                        // The rest of the fields have been consumed.
+                        fieldIndex = fieldValues.size();
+                        setterIndex = mFieldSetters.size();
+                        break;
+                    } else {
+                        setter.setField(lineNumber, record, value);
+                    }
+                } catch (ParseException ex) {
+                    bs.addWarning(lineNumber, WarningId.BAD_FIELD_TYPE, fieldValues,
+                            ex.getMessage(), mTag, value);
+                    record.complete = false;
+                }
+
+                fieldIndex++;
+                setterIndex++;
+            }
+
+            // If there are extra fields, this record is complete, there are just
+            // extra values, so we issue a warning but don't mark it incomplete.
+            if (fieldIndex < fieldValues.size()) {
+                bs.addWarning(lineNumber, WarningId.TOO_MANY_FIELDS, fieldValues,
+                        "Line '" + mTag + "' has extra fields.",
+                        mTag, Integer.toString(fieldIndex), Integer.toString(fieldValues.size()));
+                if (debug) {
+                    for (int i=0; i<mFieldSetters.size(); i++) {
+                        System.err.println("    setter: [" + i + "] " + mFieldSetters.get(i));
+                    }
+                }
+            }
+
+            // If we have any fields that are missing, add a warning and return null.
+            for (; setterIndex < mFieldSetters.size(); setterIndex++) {
+                final FieldSetter setter = mFieldSetters.get(setterIndex);
+                if (dumpsysVersion >= 0 && dumpsysVersion >= setter.getAdded()) {
+                    bs.addWarning(lineNumber, WarningId.NOT_ENOUGH_FIELDS, fieldValues,
+                            "Line '" + mTag + "' missing field: index=" + setterIndex
+                                + " name=" + setter.getName(),
+                            mTag, Integer.toString(setterIndex));
+                    record.complete = false;
+                }
+            }
+
+            return record;
+        }
+    }
+
+    /**
+     * Parse the input stream and return a RawBatteryStats object.
+     */
+    public static RawBatteryStats parse(InputStream input) throws ParseException, IOException {
+        final RawBatteryStats result = new RawBatteryStats();
+        result.parseImpl(input);
+        return result;
+    }
+
+    /**
+     * Get a record.
+     * <p>
+     * If multiple of that record are found, returns the first one.  There will already
+     * have been a warning recorded if the count annotation did not match what was in the
+     * csv.
+     * <p>
+     * Returns null if there are no records of that type.
+     */
+    public <T extends Record> T getSingle(Class<T> cl) {
+        final List<Record> list = mRecordsByType.get(cl.getName());
+        if (list == null) {
+            return null;
+        }
+        // Notes:
+        //   - List can never be empty because the list itself wouldn't have been added.
+        //   - Cast is safe because list was populated based on class name (let's assume
+        //     there's only one class loader involved here).
+        return (T)list.get(0);
+    }
+
+    /**
+     * Get a record.
+     * <p>
+     * If multiple of that record are found, returns the first one that matches that uid.
+     * <p>
+     * Returns null if there are no records of that type that match the given uid.
+     */
+    public <T extends Record> T getSingle(Class<T> cl, int uid) {
+        final List<Record> list = mRecordsByType.get(cl.getName());
+        if (list == null) {
+            return null;
+        }
+        for (final Record record: list) {
+            if (record.uid == uid) {
+                // Cast is safe because list was populated based on class name (let's assume
+                // there's only one class loader involved here).
+                return (T)record;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get all the records of the given type.
+     */
+    public <T extends Record> List<T> getMultiple(Class<T> cl) {
+        final List<Record> list = mRecordsByType.get(cl.getName());
+        if (list == null) {
+            return ImmutableList.<T>of();
+        }
+        // Cast is safe because list was populated based on class name (let's assume
+        // there's only one class loader involved here).
+        return ImmutableList.copyOf((List<T>)list);
+    }
+
+    /**
+     * Get the UIDs that are covered by this batterystats dump.
+     */
+    public Set<AttributionKey> getApps() {
+        return mApps;
+    }
+
+    /**
+     * No public constructor. Use {@link #parse}.
+     */
+    private RawBatteryStats() {
+    }
+
+    /**
+     * Get the list of Record objects that were parsed from the csv.
+     */
+    public List<Record> getRecords() {
+        return mRecords;
+    }
+
+    /**
+     * Gets the warnings that were encountered during parsing.
+     */
+    public List<Warning> getWarnings() {
+        return mWarnings;
+    }
+
+    /**
+     * Implementation of the csv parsing.
+     */
+    private void parseImpl(InputStream input) throws ParseException, IOException {
+        // Parse the csv
+        CsvParser.parse(input, new CsvParser.LineProcessor() {
+                    @Override
+                    public void onLine(int lineNumber, ArrayList<String> fields)
+                            throws ParseException {
+                        handleCsvLine(lineNumber, fields);
+                    }
+                });
+
+        // Gather the records by class name for the getSingle() and getMultiple() functions.
+        indexRecords();
+
+        // Gather the uids from all the places UIDs come from, for getApps().
+        indexApps();
+    }
+
+    /**
+     * Handle a line of CSV input, creating the right Record object.
+     */
+    private void handleCsvLine(int lineNumber, ArrayList<String> fields) throws ParseException {
+        // The standard rows all have the 4 core fields. Anything less isn't what we're
+        // looking for.
+        if (fields.size() <= 4) {
+            addWarning(lineNumber, WarningId.TOO_FEW_FIELDS_FOR_LINE_TYPE, fields,
+                    "Line with too few fields (" + fields.size() + ")",
+                    Integer.toString(fields.size()));
+            return;
+        }
+
+        final String lineType = fields.get(3);
+
+        // Handle the vers line specially, because we need the version number
+        // to make the rest of the machinery work.
+        if ("vers".equals(lineType)) {
+            final String versionText = fields.get(4);
+            try {
+                mDumpsysVersion = Integer.parseInt(versionText);
+            } catch (NumberFormatException ex) {
+                addWarning(lineNumber, WarningId.BAD_DUMPSYS_VERSION, fields,
+                        "Couldn't parse dumpsys version number: '" + versionText,
+                        versionText);
+            }
+        }
+
+        // Find the right factory.
+        final RecordFactory factory = sFactories.get(lineType);
+        if (factory == null) {
+            addWarning(lineNumber, WarningId.NO_MATCHING_LINE_TYPE, fields,
+                    "No Record for line type '" + lineType + "'",
+                    lineType);
+            return;
+        }
+
+        // Create the record.
+        final Record record = factory.create(this, mDumpsysVersion, lineNumber, fields);
+        mRecords.add(record);
+    }
+
+    /**
+     * Add to the list of warnings.
+     */
+    private void addWarning(int lineNumber, WarningId id,
+            ArrayList<String> fields, String message, String... extras) {
+        mWarnings.add(new Warning(lineNumber, id, fields, message, extras));
+        final boolean debug = false;
+        if (debug) {
+            final StringBuilder text = new StringBuilder("line ");
+            text.append(lineNumber);
+            text.append(": WARNING: ");
+            text.append(message);
+            text.append("\n    fields: ");
+            for (int i=0; i<fields.size(); i++) {
+                final String field = fields.get(i);
+                if (field.indexOf('"') >= 0) {
+                    text.append('"');
+                    text.append(field.replace("\"", "\"\""));
+                    text.append('"');
+                } else {
+                    text.append(field);
+                }
+                if (i != fields.size() - 1) {
+                    text.append(',');
+                }
+            }
+            text.append('\n');
+            for (String extra: extras) {
+                text.append("    extra: ");
+                text.append(extra);
+                text.append('\n');
+            }
+            System.err.print(text.toString());
+        }
+    }
+
+    /**
+     * Group records by class name.
+     */
+    private void indexRecords() {
+        final HashMap<String,ArrayList<Record>> map = new HashMap<String,ArrayList<Record>>();
+
+        // Iterate over all of the records
+        for (Record record: mRecords) {
+            final String className = record.getClass().getName();
+
+            ArrayList<Record> list = map.get(className);
+            if (list == null) {
+                list = new ArrayList<Record>();
+                map.put(className, list);
+            }
+
+            list.add(record);
+        }
+
+        // Make it immutable
+        final HashMap<String,ImmutableList<Record>> result
+                = new HashMap<String,ImmutableList<Record>>();
+        for (HashMap.Entry<String,ArrayList<Record>> entry: map.entrySet()) {
+            result.put(entry.getKey(), ImmutableList.copyOf(entry.getValue()));
+        }
+
+        // Initialize here so uninitialized access will result in NPE.
+        mRecordsByType = ImmutableMap.copyOf(result);
+    }
+
+    /**
+     * Collect the UIDs from the csv.
+     *
+     * They come from two places.
+     * <ul>
+     *   <li>The uid to package name map entries ({@link #Uid}) at the beginning.
+     *   <li>The uid fields of the rest of the entries, some of which might not
+     *       have package names associated with them.
+     * </ul>
+     *
+     * TODO: Is this where we should also do the logic about the special UIDs?
+     */
+    private void indexApps() {
+        final HashMap<Integer,HashSet<String>> uids = new HashMap<Integer,HashSet<String>>();
+
+        // The Uid rows, from which we get package names
+        for (Uid record: getMultiple(Uid.class)) {
+            HashSet<String> list = uids.get(record.uidKey);
+            if (list == null) {
+                list = new HashSet<String>();
+                uids.put(record.uidKey, list);
+            }
+            list.add(record.pkg);
+        }
+
+        // The uid fields of everything
+        for (Record record: mRecords) {
+            // The 0 in the INFO records isn't really root, it's just unfilled data.
+            // The root uid (0) will show up practically in every record, but don't force it.
+            if (record.category != Category.INFO) {
+                if (uids.get(record.uid) == null) {
+                    // There is no other data about this UID, but it does exist!
+                    uids.put(record.uid, new HashSet<String>());
+                }
+            }
+        }
+
+        // Turn our temporary lists of package names into AttributionKeys.
+        final HashSet<AttributionKey> result = new HashSet<AttributionKey>();
+        for (HashMap.Entry<Integer,HashSet<String>> entry: uids.entrySet()) {
+            result.add(new AttributionKey(entry.getKey(), entry.getValue()));
+        }
+
+        // Initialize here so uninitialized access will result in NPE.
+        mApps = ImmutableSet.copyOf(result);
+    }
+
+    /**
+     * Init the factory classes.
+     */
+    static {
+        for (Class<?> cl: RawBatteryStats.class.getClasses()) {
+            final Line lineAnnotation = cl.getAnnotation(Line.class);
+            if (lineAnnotation != null && Record.class.isAssignableFrom(cl)) {
+                final ArrayList<FieldSetter> fieldSetters = new ArrayList<FieldSetter>();
+
+                for (java.lang.reflect.Field field: cl.getFields()) {
+                    final Field fa = field.getAnnotation(Field.class);
+                    if (fa != null) {
+                        final Class<?> fieldType = field.getType();
+                        final Class<?> innerType = fieldType.isArray()
+                                ? fieldType.getComponentType()
+                                : fieldType;
+                        if (Integer.TYPE.equals(innerType)) {
+                            fieldSetters.add(new IntFieldSetter(fa.index(), fa.added(), field));
+                        } else if (Long.TYPE.equals(innerType)) {
+                            fieldSetters.add(new LongFieldSetter(fa.index(), fa.added(), field));
+                        } else if (String.class.equals(innerType)) {
+                            fieldSetters.add(new StringFieldSetter(fa.index(), fa.added(), field));
+                        } else if (innerType.isEnum()) {
+                            fieldSetters.add(new EnumFieldSetter(fa.index(), fa.added(), field));
+                        } else {
+                            throw new RuntimeException("Unsupported field type '"
+                                    + fieldType.getName() + "' on "
+                                    + cl.getName() + "." + field.getName());
+                        }
+                    }
+                }
+                // Sort by index
+                Collections.sort(fieldSetters, new Comparator<FieldSetter>() {
+                            @Override
+                            public int compare(FieldSetter a, FieldSetter b) {
+                                return a.getIndex() - b.getIndex();
+                            }
+                        });
+                // Only the last one can be an array
+                for (int i=0; i<fieldSetters.size()-1; i++) {
+                    if (fieldSetters.get(i).isArray()) {
+                        throw new RuntimeException("Only the last (highest index) @Field"
+                                + " in class " + cl.getName() + " can be an array: "
+                                + fieldSetters.get(i).getName());
+                    }
+                }
+                // Add to the map
+                sFactories.put(lineAnnotation.tag(), new RecordFactory(lineAnnotation.tag(),
+                            (Class<Record>)cl, fieldSetters));
+            }
+        }
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/SpecialApp.java b/tools/powermodel/src/com/android/powermodel/SpecialApp.java
new file mode 100644
index 0000000..df1e1fb
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/SpecialApp.java
@@ -0,0 +1,85 @@
+/*
+ * 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.powermodel;
+
+/**
+ * Identifiers for well-known apps that have unique characteristics.
+ *
+ * @more
+ * This includes three categories:
+ * <ul>
+ *   <li><b>Built-in system components</b> – These have predefined UIDs that are
+ *   always the same. For example, the system UID is always 1000.</li>
+ *   <li><b>Well known apps with shared UIDs</b> – These do not have predefined
+ *   UIDs (i.e. are different on each device), but since they have shared UIDs
+ *   with varying sets of package names (GmsCore is the canonical example), we
+ *   have special logic to capture these into a single entity with a well defined
+ *   key. These have the {@link #uid uid} field set to
+ *   {@link Uid#UID_VARIES Uid.UID_VARIES}.</li>
+ *   <li><b>Synthetic remainder app</b> – The {@link #REMAINDER REMAINDER} app doesn't
+ *   represent a real app. It contains accounting for usage which is not attributed
+ *   to any UID. This app has the {@link #uid uid} field set to
+ *   {@link Uid#UID_SYNTHETIC Uid.UID_SYNTHETIC}.</li>
+ * </ul>
+ */
+public enum SpecialApp {
+
+    /**
+     * Synthetic app that accounts for the remaining amount of resources used
+     * that is unaccounted for by apps, or overcounted because of inaccuracies
+     * in the model.
+     */
+    REMAINDER(Uid.UID_SYNTHETIC),
+
+    /**
+     * Synthetic app that holds system-wide numbers, for example the total amount
+     * of various resources used, device-wide.
+     */
+    GLOBAL(Uid.UID_SYNTHETIC),
+
+    SYSTEM(1000),
+
+    GOOGLE_SERVICES(Uid.UID_VARIES);
+
+    /**
+     * Constants for SpecialApps where the uid is not actually a UID.
+     */
+    public static class Uid {
+        /**
+         * Constant to indicate that this special app does not have a fixed UID.
+         */
+        public static final int UID_VARIES = -1;
+
+        /**
+         * Constant to indicate that this special app is not actually an app with a UID.
+         * 
+         * @see SpecialApp#REMAINDER
+         * @see SpecialApp#GLOBAL
+         */
+        public static final int UID_SYNTHETIC = -2;
+    }
+
+    /**
+     * The fixed UID value of this special app, or {@link #UID_VARIES} if there
+     * isn't one.
+     */
+    public final int uid;
+
+    private SpecialApp(int uid) {
+        this.uid = uid;
+    }
+}
diff --git a/tools/powermodel/src/com/android/powermodel/component/AudioProfile.java b/tools/powermodel/src/com/android/powermodel/component/AudioProfile.java
new file mode 100644
index 0000000..63ff3a6
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/AudioProfile.java
@@ -0,0 +1,27 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class AudioProfile extends ComponentProfile {
+    public float onMa;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/BluetoothProfile.java b/tools/powermodel/src/com/android/powermodel/component/BluetoothProfile.java
new file mode 100644
index 0000000..8f5e7d0
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/BluetoothProfile.java
@@ -0,0 +1,29 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class BluetoothProfile extends ComponentProfile {
+    public float idleMa;
+    public float rxMa;
+    public float txMa;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/CameraProfile.java b/tools/powermodel/src/com/android/powermodel/component/CameraProfile.java
new file mode 100644
index 0000000..8ee22d0
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/CameraProfile.java
@@ -0,0 +1,27 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class CameraProfile extends ComponentProfile {
+    public float onMa;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/CpuProfile.java b/tools/powermodel/src/com/android/powermodel/component/CpuProfile.java
new file mode 100644
index 0000000..0b34fc8
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/CpuProfile.java
@@ -0,0 +1,145 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class CpuProfile extends ComponentProfile {
+    public float suspendMa;
+    public float idleMa;
+    public float activeMa;
+    public Cluster[] clusters;
+
+    public static class Cluster {
+        public int coreCount;
+        public float onMa;
+        public Frequency[] frequencies;
+    }
+
+    public static class Frequency {
+        public int speedHz;
+        public float onMa;
+    }
+
+    public static class Builder {
+        private float mSuspendMa;
+        private float mIdleMa;
+        private float mActiveMa;
+        private int[] mCoreCount;
+        private HashMap<Integer,Float> mClusterOnPower = new HashMap<Integer,Float>();
+        private HashMap<Integer,int[]> mCoreSpeeds = new HashMap<Integer,int[]>();
+        private HashMap<Integer,float[]> mCorePower = new HashMap<Integer,float[]>();
+
+        public Builder() {
+        }
+
+        public void setSuspendMa(float value) throws ParseException {
+            mSuspendMa = value;
+        }
+
+        public void setIdleMa(float value) throws ParseException {
+            mIdleMa = value;
+        }
+
+        public void setActiveMa(float value) throws ParseException {
+            mActiveMa = value;
+        }
+
+        public void setCoreCount(int[] value) throws ParseException {
+            mCoreCount = Arrays.copyOf(value, value.length);
+        }
+
+        public void setClusterPower(int cluster, float value) throws ParseException {
+            mClusterOnPower.put(cluster, value);
+        }
+
+        public void setCoreSpeeds(int cluster, int[] value) throws ParseException {
+            mCoreSpeeds.put(cluster, Arrays.copyOf(value, value.length));
+            float[] power = mCorePower.get(cluster);
+            if (power != null && value.length != power.length) {
+                throw new ParseException("length of cpu.core_speeds.cluster" + cluster
+                        + " (" + value.length + ") is different from length of"
+                        + " cpu.core_power.cluster" + cluster + " (" + power.length + ")");
+            }
+            if (mCoreCount != null && cluster >= mCoreCount.length) {
+                throw new ParseException("cluster " + cluster
+                        + " in cpu.core_speeds.cluster" + cluster
+                        + " is larger than the number of clusters specified in cpu.clusters.cores ("
+                        + mCoreCount.length + ")");
+            }
+        }
+
+        public void setCorePower(int cluster, float[] value) throws ParseException {
+            mCorePower.put(cluster, Arrays.copyOf(value, value.length));
+            int[] speeds = mCoreSpeeds.get(cluster);
+            if (speeds != null && value.length != speeds.length) {
+                throw new ParseException("length of cpu.core_power.cluster" + cluster
+                        + " (" + value.length + ") is different from length of"
+                        + " cpu.clusters.cores" + cluster + " (" + speeds.length + ")");
+            }
+            if (mCoreCount != null && cluster >= mCoreCount.length) {
+                throw new ParseException("cluster " + cluster
+                        + " in cpu.core_power.cluster" + cluster
+                        + " is larger than the number of clusters specified in cpu.clusters.cores ("
+                        + mCoreCount.length + ")");
+            }
+        }
+
+        public CpuProfile build() throws ParseException {
+            final CpuProfile result = new CpuProfile();
+
+            // Validate cluster count
+
+            // All null or none null
+            // TODO
+
+            // Same size
+            // TODO
+
+            // No gaps
+            // TODO
+
+            // Fill in values
+            result.suspendMa = mSuspendMa;
+            result.idleMa = mIdleMa;
+            result.activeMa = mActiveMa;
+            if (mCoreCount != null) {
+                result.clusters = new Cluster[mCoreCount.length];
+                for (int i = 0; i < result.clusters.length; i++) {
+                    final Cluster cluster = result.clusters[i] = new Cluster();
+                    cluster.coreCount = mCoreCount[i];
+                    cluster.onMa = mClusterOnPower.get(i);
+                    int[] speeds = mCoreSpeeds.get(i);
+                    float[] power = mCorePower.get(i);
+                    cluster.frequencies = new Frequency[speeds.length];
+                    for (int j = 0; j < speeds.length; j++) {
+                        final Frequency freq = cluster.frequencies[j] = new Frequency();
+                        freq.speedHz = speeds[j];
+                        freq.onMa = power[j];
+                    }
+                }
+            }
+
+            return result;
+        }
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/FlashlightProfile.java b/tools/powermodel/src/com/android/powermodel/component/FlashlightProfile.java
new file mode 100644
index 0000000..c85f3ff
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/FlashlightProfile.java
@@ -0,0 +1,27 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class FlashlightProfile extends ComponentProfile {
+    public float onMa;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/GpsProfile.java b/tools/powermodel/src/com/android/powermodel/component/GpsProfile.java
new file mode 100644
index 0000000..83c06a7
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/GpsProfile.java
@@ -0,0 +1,53 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class GpsProfile extends ComponentProfile {
+    public float onMa;
+    public float[] signalQualityMa;
+
+    public static class Builder {
+        private float onMa;
+        private float[] mSignalQualityMa;
+
+        public Builder() {
+        }
+
+        public void setOnMa(float value) throws ParseException {
+            onMa = value;
+        }
+
+        public void setSignalMa(float[] value) throws ParseException {
+            mSignalQualityMa = value;
+        }
+
+        public GpsProfile build() throws ParseException {
+            GpsProfile result = new GpsProfile();
+            result.onMa = onMa;
+            result.signalQualityMa = mSignalQualityMa == null
+                    ? new float[0]
+                    : Arrays.copyOf(mSignalQualityMa, mSignalQualityMa.length);
+            return result;
+        }
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java
new file mode 100644
index 0000000..cb70051
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemAppActivity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.powermodel.component;
+
+import com.android.powermodel.ActivityReport;
+import com.android.powermodel.AttributionKey;
+import com.android.powermodel.Component;
+import com.android.powermodel.ComponentActivity;
+import com.android.powermodel.PowerProfile;
+import com.android.powermodel.util.Conversion;
+
+/**
+ * Encapsulates the work done by the celluar modem on behalf of an app.
+ */
+public class ModemAppActivity extends ComponentActivity {
+    /**
+     * Construct a new ModemAppActivity.
+     */
+    public ModemAppActivity(AttributionKey attribution) {
+        super(attribution);
+    }
+
+    /**
+     * The number of packets received by the app.
+     */
+    public long rxPacketCount;
+
+    /**
+     * The number of packets sent by the app.
+     */
+    public long txPacketCount;
+
+    @Override
+    public ModemAppPower applyProfile(ActivityReport activityReport, PowerProfile profile) {
+        // Profile
+        final ModemProfile modemProfile = (ModemProfile)profile.getComponent(Component.MODEM);
+        if (modemProfile == null) {
+            // TODO: This is kind of a big problem...  Should this throw instead?
+            return null;
+        }
+
+        // Activity
+        final ModemGlobalActivity global
+                = (ModemGlobalActivity)activityReport.findGlobalComponent(Component.MODEM);
+        if (global == null) {
+            return null;
+        }
+
+        final double averageModemPowerMa = getAverageModemPowerMa(modemProfile);
+        final long totalPacketCount = global.rxPacketCount + global.txPacketCount;
+        final long appPacketCount = this.rxPacketCount + this.txPacketCount;
+
+        final ModemAppPower result = new ModemAppPower();
+        result.attribution = this.attribution;
+        result.activity = this;
+        result.powerMah = Conversion.msToHr(
+                (totalPacketCount > 0 ? (appPacketCount / (double)totalPacketCount) : 0)
+                * global.totalActiveTimeMs
+                * averageModemPowerMa);
+        return result;
+    }
+
+    static final double getAverageModemPowerMa(ModemProfile profile) {
+        double sumMa = profile.getRxMa();
+        for (float powerAtTxLevelMa: profile.getTxMa()) {
+            sumMa += powerAtTxLevelMa;
+        }
+        return sumMa / (profile.getTxMa().length + 1);
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemAppPower.java b/tools/powermodel/src/com/android/powermodel/component/ModemAppPower.java
new file mode 100644
index 0000000..f553127
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemAppPower.java
@@ -0,0 +1,24 @@
+/*
+ * 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.powermodel.component;
+
+import com.android.powermodel.Component;
+import com.android.powermodel.ComponentPower;
+
+public class ModemAppPower extends ComponentPower<ModemAppActivity> {
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemBatteryStatsReader.java b/tools/powermodel/src/com/android/powermodel/component/ModemBatteryStatsReader.java
new file mode 100644
index 0000000..6dbfbc2
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemBatteryStatsReader.java
@@ -0,0 +1,110 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.android.powermodel.AttributionKey;
+import com.android.powermodel.ComponentActivity;
+import com.android.powermodel.RawBatteryStats;
+import com.android.powermodel.SpecialApp;
+
+public class ModemBatteryStatsReader {
+    private ModemBatteryStatsReader() {
+    }
+
+    public static List<ComponentActivity> createActivities(RawBatteryStats bs) {
+        final List<ComponentActivity> result = new ArrayList<ComponentActivity>();
+
+        // The whole system
+        createGlobal(result, bs);
+
+        // The apps
+        createApps(result, bs);
+
+        // The synthetic "cell" app.
+        createRemainder(result, bs);
+
+        return result;
+    }
+
+    private static void createGlobal(List<ComponentActivity> result, RawBatteryStats bs) {
+        final ModemGlobalActivity global
+                = new ModemGlobalActivity(new AttributionKey(SpecialApp.GLOBAL));
+
+        final RawBatteryStats.GlobalNetwork gn = bs.getSingle(RawBatteryStats.GlobalNetwork.class);
+        final RawBatteryStats.Misc misc = bs.getSingle(RawBatteryStats.Misc.class);
+
+        // Null here just means no network activity.
+        if (gn != null && misc != null) {
+            global.rxPacketCount = gn.mobileRxTotalPackets;
+            global.txPacketCount = gn.mobileTxTotalPackets;
+
+            global.totalActiveTimeMs = misc.mobileRadioActiveTimeMs;
+        }
+
+        result.add(global);
+    }
+
+    private static void createApps(List<ComponentActivity> result, RawBatteryStats bs) {
+        for (AttributionKey key: bs.getApps()) {
+            final int uid = key.getUid();
+            final RawBatteryStats.Network network
+                    = bs.getSingle(RawBatteryStats.Network.class, uid);
+
+            // Null here just means no network activity.
+            if (network != null) {
+                final ModemAppActivity app = new ModemAppActivity(key);
+
+                app.rxPacketCount = network.mobileRxPackets;
+                app.txPacketCount = network.mobileTxPackets;
+
+                result.add(app);
+            }
+        }
+    }
+
+    private static void createRemainder(List<ComponentActivity> result, RawBatteryStats bs) {
+        final RawBatteryStats.SignalStrengthTime strength
+                = bs.getSingle(RawBatteryStats.SignalStrengthTime.class);
+        final RawBatteryStats.SignalScanningTime scanning
+                = bs.getSingle(RawBatteryStats.SignalScanningTime.class);
+        final RawBatteryStats.Misc misc = bs.getSingle(RawBatteryStats.Misc.class);
+
+        if (strength != null && scanning != null && misc != null) {
+            final ModemRemainderActivity remainder
+                    = new ModemRemainderActivity(new AttributionKey(SpecialApp.REMAINDER));
+
+            // Signal strength buckets
+            remainder.strengthTimeMs = strength.phoneSignalStrengthTimeMs;
+
+            // Time spent scanning
+            remainder.scanningTimeMs = scanning.phoneSignalScanningTimeMs;
+
+            // Unaccounted for active time
+            final long totalActiveTimeMs = misc.mobileRadioActiveTimeMs;
+            long appActiveTimeMs = 0;
+            for (RawBatteryStats.Network nw: bs.getMultiple(RawBatteryStats.Network.class)) {
+                appActiveTimeMs += nw.mobileRadioActiveTimeUs / 1000;
+            }
+            remainder.activeTimeMs = totalActiveTimeMs - appActiveTimeMs;
+
+            result.add(remainder);
+        }
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java
new file mode 100644
index 0000000..a53b53e
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemGlobalActivity.java
@@ -0,0 +1,51 @@
+/*
+ * 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.powermodel.component;
+
+import com.android.powermodel.ActivityReport;
+import com.android.powermodel.AttributionKey;
+import com.android.powermodel.ComponentActivity;
+import com.android.powermodel.ComponentPower;
+import com.android.powermodel.PowerProfile;
+
+/**
+ * Encapsulates total work done by the modem for the device.
+ */
+public class ModemGlobalActivity extends ComponentActivity {
+    /**
+     * Construct a new ModemGlobalActivity.
+     */
+    public ModemGlobalActivity(AttributionKey attribution) {
+        super(attribution);
+    }
+
+    /**
+     * Returns the total number of packets received in the whole device.
+     */
+    public long rxPacketCount;
+
+    /**
+     * Returns the total number of packets sent in the whole device.
+     */
+    public long txPacketCount;
+
+    /**
+     * Returns the total time the radio was active in the whole device.
+     */
+    public long totalActiveTimeMs;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemProfile.java b/tools/powermodel/src/com/android/powermodel/component/ModemProfile.java
new file mode 100644
index 0000000..cda72ee
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemProfile.java
@@ -0,0 +1,92 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class ModemProfile extends ComponentProfile {
+    public float sleepMa;
+    public float idleMa;
+    public float scanningMa;
+    public float rxMa;
+    public float[] txMa;
+
+    public float getSleepMa() {
+        return sleepMa;
+    }
+
+    public float getIdleMa() {
+        return idleMa;
+    }
+
+    public float getRxMa() {
+        return rxMa;
+    }
+
+    public float[] getTxMa() {
+        return Arrays.copyOf(txMa, txMa.length);
+    }
+
+    public float getScanningMa() {
+        return scanningMa;
+    }
+
+    public static class Builder {
+        private float mSleepMa;
+        private float mIdleMa;
+        private float mRxMa;
+        private float[] mTxMa;
+        private float mScanningMa;
+
+        public Builder() {
+        }
+
+        public void setSleepMa(float value) throws ParseException {
+            mSleepMa = value;
+        }
+
+        public void setIdleMa(float value) throws ParseException {
+            mIdleMa = value;
+        }
+
+        public void setRxMa(float value) throws ParseException {
+            mRxMa = value;
+        }
+
+        public void setTxMa(float[] value) throws ParseException {
+            mTxMa = Arrays.copyOf(value, value.length);
+        }
+
+        public void setScanningMa(float value) throws ParseException {
+            mScanningMa = value;
+        }
+
+        public ModemProfile build() throws ParseException {
+            ModemProfile result = new ModemProfile();
+            result.sleepMa = mSleepMa;
+            result.idleMa = mIdleMa;
+            result.rxMa = mRxMa;
+            result.txMa = mTxMa == null ? new float[0] : mTxMa;
+            result.scanningMa = mScanningMa;
+            return result;
+        }
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java
new file mode 100644
index 0000000..0e268c2
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderActivity.java
@@ -0,0 +1,87 @@
+/*
+ * 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.powermodel.component;
+
+import com.android.powermodel.ActivityReport;
+import com.android.powermodel.AttributionKey;
+import com.android.powermodel.Component;
+import com.android.powermodel.ComponentActivity;
+import com.android.powermodel.PowerProfile;
+import com.android.powermodel.util.Conversion;
+
+/**
+ * Encapsulates the work done by the remaining 
+ */
+public class ModemRemainderActivity extends ComponentActivity {
+    /**
+     * Construct a new ModemRemainderActivity.
+     */
+    public ModemRemainderActivity(AttributionKey attribution) {
+        super(attribution);
+    }
+
+    /**
+     * Number of milliseconds spent at each of the signal strengths.
+     */
+    public long[] strengthTimeMs;
+
+    /**
+     * Number of milliseconds spent scanning for a network.
+     */
+    public long scanningTimeMs;
+
+    /**
+     * Number of milliseconds that the radio is active for reasons other
+     * than an app transmitting and receiving data.
+     */
+    public long activeTimeMs;
+
+    @Override
+    public ModemRemainderPower applyProfile(ActivityReport activityReport, PowerProfile profile) {
+        // Profile
+        final ModemProfile modemProfile = (ModemProfile)profile.getComponent(Component.MODEM);
+        if (modemProfile == null) {
+            return null;
+        }
+
+        // Activity
+        final ModemRemainderPower result = new ModemRemainderPower();
+        result.attribution = this.attribution;
+        result.activity = this;
+
+        // strengthMah
+        // TODO: If the array lengths don't match... then?
+        result.strengthMah = new double[this.strengthTimeMs.length];
+        for (int i=0; i<this.strengthTimeMs.length; i++) {
+            result.strengthMah[i] = Conversion.msToHr(
+                    this.strengthTimeMs[i] * modemProfile.getTxMa()[i]);
+            result.powerMah += result.strengthMah[i];
+        }
+
+        // scanningMah
+        result.scanningMah = Conversion.msToHr(this.scanningTimeMs * modemProfile.getScanningMa());
+        result.powerMah += result.scanningMah;
+
+        // activeMah
+        result.activeMah = Conversion.msToHr(
+                this.activeTimeMs * ModemAppActivity.getAverageModemPowerMa(modemProfile));
+        result.powerMah += result.activeMah;
+
+        return result;
+    }
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ModemRemainderPower.java b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderPower.java
new file mode 100644
index 0000000..7f38cd3
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ModemRemainderPower.java
@@ -0,0 +1,30 @@
+/*
+ * 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.powermodel.component;
+
+import com.android.powermodel.Component;
+import com.android.powermodel.ComponentPower;
+
+public class ModemRemainderPower extends ComponentPower<ModemRemainderActivity> {
+
+    public double[] strengthMah;
+
+    public double scanningMah;
+
+    public double activeMah;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/ScreenProfile.java b/tools/powermodel/src/com/android/powermodel/component/ScreenProfile.java
new file mode 100644
index 0000000..e1051c6
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/ScreenProfile.java
@@ -0,0 +1,29 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class ScreenProfile extends ComponentProfile {
+    public float onMa;
+    public float fullMa;
+    public float ambientMa;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/VideoProfile.java b/tools/powermodel/src/com/android/powermodel/component/VideoProfile.java
new file mode 100644
index 0000000..5152795
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/VideoProfile.java
@@ -0,0 +1,28 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class VideoProfile extends ComponentProfile {
+    public float onMa;
+}
+
+
diff --git a/tools/powermodel/src/com/android/powermodel/component/WifiProfile.java b/tools/powermodel/src/com/android/powermodel/component/WifiProfile.java
new file mode 100644
index 0000000..6f424bf
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/component/WifiProfile.java
@@ -0,0 +1,29 @@
+/*
+ * 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.powermodel.component;
+
+import java.util.Arrays;
+
+import com.android.powermodel.ComponentProfile;
+import com.android.powermodel.ParseException;
+
+public class WifiProfile extends ComponentProfile {
+    public float idleMa;
+    public float rxMa;
+    public float txMa;
+}
+
diff --git a/tools/powermodel/src/com/android/powermodel/util/Conversion.java b/tools/powermodel/src/com/android/powermodel/util/Conversion.java
new file mode 100644
index 0000000..e556c25
--- /dev/null
+++ b/tools/powermodel/src/com/android/powermodel/util/Conversion.java
@@ -0,0 +1,47 @@
+/*
+ * 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.powermodel.util;
+
+public class Conversion {
+
+    /**
+     * Convert the the float[] to an int[].
+     * <p>
+     * Values are rounded to the nearest integral value. Null input
+     * results in null output.
+     */
+    public static int[] toIntArray(float[] value) {
+        if (value == null) {
+            return null;
+        }
+        int[] result = new int[value.length];
+        for (int i=0; i<result.length; i++) {
+            result[i] = (int)(value[i] + 0.5f);
+        }
+        return result;
+    }
+    
+    public static double msToHr(double ms) {
+        return ms / 3600.0 / 1000.0;
+    }
+
+    /**
+     * No public constructor.
+     */
+    private Conversion() {
+    }
+}
diff --git a/tools/powermodel/test-resource/bs.csv b/tools/powermodel/test-resource/bs.csv
new file mode 100644
index 0000000..6e84120
--- /dev/null
+++ b/tools/powermodel/test-resource/bs.csv
@@ -0,0 +1,7 @@
+9,0,i,vers,32,177,PPR1.180326.002,PQ1A.181105.015
+9,0,i,uid,10139,com.google.android.gm
+9,0,l,gn,108060756,17293456,4896592,3290614,97840,72941,6903,8107,390,105
+9,0,l,m,2590630,0,384554,3943868,5113727,265,2565483,0,16,0,0,0,0,192,25331,3472068,17,3543323,14,614050,0
+9,10139,l,nt,13688501,534571,13842,7792,9925,5577,30,67,190051799,27,0,0,5,3,126020,42343,13842,7792,207,167,30,67
+9,0,l,sgt,3066958,0,34678,1643364,7045084
+9,0,l,sst,2443805
diff --git a/tools/powermodel/test-resource/power_profile.xml b/tools/powermodel/test-resource/power_profile.xml
new file mode 100644
index 0000000..8e388ea
--- /dev/null
+++ b/tools/powermodel/test-resource/power_profile.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+-->
+
+<!-- Test power profile that parses correctly. -->
+<device>
+    <item name="battery.capacity">2915</item>
+
+    <!-- Number of cores each CPU cluster contains -->
+    <array name="cpu.clusters.cores">
+        <value>4</value>
+        <value>2</value>
+    </array>
+
+    <!-- Power consumption when CPU is suspended -->
+    <item name="cpu.suspend">1.3</item>
+
+    <!-- Additional power consumption when CPU is in a kernel idle loop -->
+    <item name="cpu.idle">3.9</item>
+
+    <!-- Additional power consumption by CPU excluding cluster and core when
+         running -->
+    <item name="cpu.active">18.33</item>
+
+    <!-- Additional power consumption by CPU cluster0 itself when running
+         excluding cores in it -->
+    <item name="cpu.cluster_power.cluster0">2.41</item>
+
+    <!-- Additional power consumption by CPU cluster1 itself when running
+         excluding cores in it -->
+    <item name="cpu.cluster_power.cluster1">5.29</item>
+
+    <!-- Different CPU speeds as reported in
+         /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies -->
+    <array name="cpu.core_speeds.cluster0">
+        <value>100000</value>
+        <value>303200</value>
+        <value>380000</value>
+        <value>476000</value>
+        <value>552800</value>
+        <value>648800</value>
+        <value>725600</value>
+        <value>802400</value>
+        <value>879200</value>
+    </array>
+
+    <!-- Different CPU speeds as reported in
+         /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies -->
+    <array name="cpu.core_speeds.cluster1">
+        <value>825600</value>
+        <value>902400</value>
+        <value>979200</value>
+        <value>1056000</value>
+        <value>1209600</value>
+        <value>1286400</value>
+        <value>1363200</value>
+    </array>
+
+    <!-- Additional power used by a CPU core from cluster 0 when running at
+         different speeds, excluding cluster and active cost -->
+    <array name="cpu.core_power.cluster0">
+        <value>0.29</value>
+        <value>0.63</value>
+        <value>1.23</value>
+        <value>1.24</value>
+        <value>2.47</value>
+        <value>2.54</value>
+        <value>3.60</value>
+        <value>3.64</value>
+        <value>4.42</value>
+    </array>
+
+    <!-- Additional power used by a CPU core from cluster 1 when running at
+         different speeds, excluding cluster and active cost -->
+    <array name="cpu.core_power.cluster1">
+        <value>28.98</value>
+        <value>31.40</value>
+        <value>33.33</value>
+        <value>40.12</value>
+        <value>44.10</value>
+        <value>90.14</value>
+        <value>100</value>
+    </array>
+
+    <!-- Additional power used when screen is ambient mode -->
+    <item name="ambient.on">12</item>
+
+    <!-- Additional power used when screen is turned on at minimum brightness -->
+    <item name="screen.on">102.4</item>
+    <!-- Additional power used when screen is at maximum brightness, compared to
+         screen at minimum brightness -->
+    <item name="screen.full">1234</item>
+
+    <!-- Average power used by the camera flash module when on -->
+    <item name="camera.flashlight">1233.47</item>
+
+    <!-- Average power use by the camera subsystem for a typical camera
+         application. Intended as a rough estimate for an application running a
+         preview and capturing approximately 10 full-resolution pictures per
+         minute. -->
+    <item name="camera.avg">941</item>
+
+    <!-- Additional power used when video is playing -->
+    <item name="video">123</item>
+
+    <!-- Additional power used when audio is playing -->
+    <item name="audio">12</item>
+
+    <!-- Cellular modem related values.-->
+    <item name="modem.controller.sleep">1</item>
+    <item name="modem.controller.idle">44</item>
+    <item name="modem.controller.rx">11</item>
+    <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+      <value>16</value>
+      <value>19</value>
+      <value>22</value>
+      <value>73</value>
+      <value>132</value>
+    </array>
+    <item name="modem.controller.voltage">1400</item>
+    <item name="radio.scanning">12</item>
+
+    <!-- GPS related values.-->
+    <item name="gps.on">1</item>
+    <array name="gps.signalqualitybased"> <!-- Strength 0 to 1 -->
+      <value>88</value>
+      <value>07</value>
+    </array>
+    <item name="gps.voltage">1500</item>
+
+    <!-- Idle Receive current for wifi radio in mA.-->
+    <item name="wifi.controller.idle">2</item>
+
+    <!-- Rx current for wifi radio in mA.-->
+    <item name="wifi.controller.rx">123</item>
+
+    <!-- Tx current for wifi radio in mA-->
+    <item name="wifi.controller.tx">333</item>
+
+    <!-- Operating volatage for wifi radio in mV.-->
+    <item name="wifi.controller.voltage">3700</item>
+
+    <!-- Idle current for bluetooth in mA.-->
+    <item name="bluetooth.controller.idle">0.02</item>
+
+    <!-- Rx current for bluetooth in mA.-->
+    <item name="bluetooth.controller.rx">3</item>
+
+    <!-- Tx current for bluetooth in mA-->
+    <item name="bluetooth.controller.tx">5</item>
+
+    <!-- Operating voltage for bluetooth in mV.-->
+    <item name="bluetooth.controller.voltage">3300</item>
+
+</device>
+
+
diff --git a/tools/powermodel/test/com/android/powermodel/BatteryStatsReaderTest.java b/tools/powermodel/test/com/android/powermodel/BatteryStatsReaderTest.java
new file mode 100644
index 0000000..e7b2c37
--- /dev/null
+++ b/tools/powermodel/test/com/android/powermodel/BatteryStatsReaderTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.powermodel;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.junit.Test;
+import org.junit.Assert;
+
+import com.android.powermodel.component.ModemAppActivity;
+import com.android.powermodel.component.ModemGlobalActivity;
+import com.android.powermodel.component.ModemRemainderActivity;
+
+/**
+ * Tests {@link BatteryStatsReader}.
+ */
+public class BatteryStatsReaderTest {
+    private static InputStream loadCsvStream() {
+        return BatteryStatsReaderTest.class.getResourceAsStream("/bs.csv");
+    }
+
+    @Test public void testModemGlobal() throws Exception {
+        final ActivityReport report = BatteryStatsReader.parse(loadCsvStream());
+
+        final AppActivity global = report.findApp(SpecialApp.GLOBAL);
+        Assert.assertNotNull(global);
+
+        final ModemGlobalActivity modem
+                = (ModemGlobalActivity)global.getComponentActivity(Component.MODEM);
+        Assert.assertNotNull(modem);
+        Assert.assertEquals(97840, modem.rxPacketCount);
+        Assert.assertEquals(72941, modem.txPacketCount);
+        Assert.assertEquals(5113727, modem.totalActiveTimeMs);
+    }
+
+    @Test public void testModemApp() throws Exception {
+        final ActivityReport report = BatteryStatsReader.parse(loadCsvStream());
+
+        final List<AppActivity> gmailList = report.findApp("com.google.android.gm");
+        Assert.assertEquals(1, gmailList.size());
+        final AppActivity gmail = gmailList.get(0);
+
+        final ModemAppActivity modem
+                = (ModemAppActivity)gmail.getComponentActivity(Component.MODEM);
+        Assert.assertNotNull(modem);
+        Assert.assertEquals(9925, modem.rxPacketCount);
+        Assert.assertEquals(5577, modem.txPacketCount);
+    }
+
+    @Test public void testModemRemainder() throws Exception {
+        final ActivityReport report = BatteryStatsReader.parse(loadCsvStream());
+
+        final AppActivity remainder = report.findApp(SpecialApp.REMAINDER);
+        Assert.assertNotNull(remainder);
+
+        final ModemRemainderActivity modem
+                = (ModemRemainderActivity)remainder.getComponentActivity(Component.MODEM);
+        Assert.assertNotNull(modem);
+        Assert.assertArrayEquals(new long[] { 3066958, 0, 34678, 1643364, 7045084 },
+                modem.strengthTimeMs);
+        Assert.assertEquals(2443805, modem.scanningTimeMs);
+        Assert.assertEquals(4923676, modem.activeTimeMs);
+    }
+}
diff --git a/tools/powermodel/test/com/android/powermodel/CsvParserTest.java b/tools/powermodel/test/com/android/powermodel/CsvParserTest.java
new file mode 100644
index 0000000..55dde41
--- /dev/null
+++ b/tools/powermodel/test/com/android/powermodel/CsvParserTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.powermodel;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests {@link PowerProfile}
+ */
+public class CsvParserTest {
+
+    class LineCollector implements CsvParser.LineProcessor {
+        ArrayList<ArrayList<String>> results = new ArrayList<ArrayList<String>>();
+
+        @Override
+        public void onLine(int lineNumber, ArrayList<String> fields) {
+            System.out.println(lineNumber);
+            for (String str: fields) {
+                System.out.println("-->" + str + "<--");
+            }
+            results.add(fields);
+        }
+    }
+
+    private void assertEquals(String[][] expected, ArrayList<ArrayList<String>> results) {
+        final String[][] resultArray = new String[results.size()][];
+        for (int i=0; i<results.size(); i++) {
+            final ArrayList<String> list = results.get(i);
+            resultArray[i] = list.toArray(new String[list.size()]);
+        }
+        Assert.assertArrayEquals(expected, resultArray);
+    }
+
+    private String makeString(int length) {
+        final StringBuilder str = new StringBuilder();
+        for (int i=0; i<length; i++) {
+            str.append('a');
+        }
+        return str.toString();
+    }
+
+    @Test public void testEmpty() throws Exception {
+        final String text = "";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                }, collector.results);
+    }
+
+    @Test public void testOnlyNewline() throws Exception {
+        final String text = "\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                }, collector.results);
+    }
+
+    @Test public void testTwoLines() throws Exception {
+        final String text = "one,twoo,3\nfour,5,six\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "one", "twoo", "3", },
+                    { "four", "5", "six", },
+                }, collector.results);
+    }
+
+    
+    @Test public void testEscapedEmpty() throws Exception {
+        final String text = "\"\",\"\",\"\"\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "", "", "", },
+                }, collector.results);
+    }
+
+    @Test public void testEscapedText() throws Exception {
+        final String text = "\"one\",\"twoo\",\"3\"\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "one", "twoo", "3", },
+                }, collector.results);
+    }
+
+    @Test public void testEscapedQuotes() throws Exception {
+        final String text = "\"\"\"\",\"\"\"\"\"\",\"\"\"\"\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "\"", "\"\"", "\"", },
+                }, collector.results);
+    }
+
+    @Test public void testEscapedCommas() throws Exception {
+        final String text = "\",\",\",\",\",\"\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { ",", ",", ",", },
+                }, collector.results);
+    }
+
+    @Test public void testEscapedQuotesAndCommas() throws Exception {
+        final String text = "\"\"\",\",\"\"\",\",\"\"\",\"\n";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "\",", "\",", "\",", },
+                }, collector.results);
+    }
+
+    @Test public void testNoNewline() throws Exception {
+        final String text = "a,b,c";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "a", "b", "c", }
+                }, collector.results);
+    }
+
+    @Test public void testNoNewlineWithCommas() throws Exception {
+        final String text = "a,b,,";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "a", "b", "", "" }
+                }, collector.results);
+    }
+
+    @Test public void testNoNewlineWithQuote() throws Exception {
+        final String text = "a,b,\",\"";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "a", "b", "," }
+                }, collector.results);
+    }
+
+    @Test public void testNoCommas() throws Exception {
+        final String text = "aasdfadfadfad";
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { "aasdfadfadfad", }
+                }, collector.results);
+    }
+
+    @Test public void testMaxLength() throws Exception {
+        final String text = makeString(CsvParser.MAX_FIELD_SIZE);
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { text, }
+                }, collector.results);
+    }
+
+    @Test public void testMaxLengthTwice() throws Exception {
+        String big = makeString(CsvParser.MAX_FIELD_SIZE);
+        final String text = big + "," + big;
+        System.out.println("Test: [" + text + "]");
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { big, big, }
+                }, collector.results);
+    }
+
+    @Test public void testTooLong() throws Exception {
+        final String text = makeString(CsvParser.MAX_FIELD_SIZE+1);
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        try {
+            CsvParser.parse(is, collector);
+            throw new RuntimeException("Expected CsvParser.parse to throw ParseException");
+        } catch (ParseException ex) {
+            // good
+        }
+    }
+
+    @Test public void testBufferBoundary() throws Exception {
+        final String big = makeString(CsvParser.MAX_FIELD_SIZE-3);
+        final String text = big + ",b,c,d,e,f,g";
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { big, "b", "c", "d", "e", "f", "g", }
+                }, collector.results);
+    }
+
+    @Test public void testBufferBoundaryEmpty() throws Exception {
+        final String big = makeString(CsvParser.MAX_FIELD_SIZE-3);
+        final String text = big + ",,,,,,";
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { big, "", "", "", "", "", "", }
+                }, collector.results);
+    }
+
+    // Checks that the escaping and sawQuote behavior is correct at the buffer boundary
+    @Test public void testBufferBoundaryEscapingEven() throws Exception {
+        final String big = makeString(CsvParser.MAX_FIELD_SIZE-2);
+        final String text = big + ",\"\"\"\"\"\"\"\"\"\"\"\"," + big;
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { big, "\"\"\"\"\"", big }
+                }, collector.results);
+    }
+
+    // Checks that the escaping and sawQuote behavior is correct at the buffer boundary
+    @Test public void testBufferBoundaryEscapingOdd() throws Exception {
+        final String big = makeString(CsvParser.MAX_FIELD_SIZE-3);
+        final String text = big + ",\"\"\"\"\"\"\"\"\"\"\"\"," + big;
+        final InputStream is = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        LineCollector collector = new LineCollector();
+
+        CsvParser.parse(is, collector);
+
+        assertEquals(new String[][] {
+                    { big, "\"\"\"\"\"", big }
+                }, collector.results);
+    }
+
+}
diff --git a/tools/powermodel/test/com/android/powermodel/PowerProfileTest.java b/tools/powermodel/test/com/android/powermodel/PowerProfileTest.java
new file mode 100644
index 0000000..ab45831
--- /dev/null
+++ b/tools/powermodel/test/com/android/powermodel/PowerProfileTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.powermodel;
+
+import java.io.InputStream;
+
+import com.android.powermodel.component.CpuProfile;
+import com.android.powermodel.component.AudioProfile;
+import com.android.powermodel.component.BluetoothProfile;
+import com.android.powermodel.component.CameraProfile;
+import com.android.powermodel.component.FlashlightProfile;
+import com.android.powermodel.component.GpsProfile;
+import com.android.powermodel.component.ModemProfile;
+import com.android.powermodel.component.ScreenProfile;
+import com.android.powermodel.component.VideoProfile;
+import com.android.powermodel.component.WifiProfile;
+import org.junit.Assert;
+import org.junit.Test;
+
+/*
+ * Additional tests needed:
+ *   - CPU clusters with mismatching counts of speeds and coefficients
+ *   - Extra fields
+ *   - Name listed twice
+ */
+
+/**
+ * Tests {@link PowerProfile}
+ */
+public class PowerProfileTest {
+    private static final float EPSILON = 0.00001f;
+
+    private static InputStream loadPowerProfileStream() {
+        return PowerProfileTest.class.getResourceAsStream("/power_profile.xml");
+    }
+
+    @Test public void testReadGood() throws Exception {
+        final InputStream is = loadPowerProfileStream();
+
+        final PowerProfile profile = PowerProfile.parse(is);
+
+        // Audio
+        final AudioProfile audio = (AudioProfile)profile.getComponent(Component.AUDIO);
+        Assert.assertEquals(12.0f, audio.onMa, EPSILON);
+
+        // Bluetooth
+        final BluetoothProfile bluetooth
+                = (BluetoothProfile)profile.getComponent(Component.BLUETOOTH);
+        Assert.assertEquals(0.02f, bluetooth.idleMa, EPSILON);
+        Assert.assertEquals(3.0f, bluetooth.rxMa, EPSILON);
+        Assert.assertEquals(5.0f, bluetooth.txMa, EPSILON);
+
+        // Camera
+        final CameraProfile camera = (CameraProfile)profile.getComponent(Component.CAMERA);
+        Assert.assertEquals(941.0f, camera.onMa, EPSILON);
+
+        // CPU
+        final CpuProfile cpu = (CpuProfile)profile.getComponent(Component.CPU);
+        Assert.assertEquals(1.3f, cpu.suspendMa, EPSILON);
+        Assert.assertEquals(3.9f, cpu.idleMa, EPSILON);
+        Assert.assertEquals(18.33f, cpu.activeMa, EPSILON);
+        Assert.assertEquals(2, cpu.clusters.length);
+        // Cluster 0
+        Assert.assertEquals(4, cpu.clusters[0].coreCount);
+        Assert.assertEquals(2.41f, cpu.clusters[0].onMa, EPSILON);
+        Assert.assertEquals(9, cpu.clusters[0].frequencies.length, EPSILON);
+        Assert.assertEquals(100000, cpu.clusters[0].frequencies[0].speedHz);
+        Assert.assertEquals(0.29f, cpu.clusters[0].frequencies[0].onMa, EPSILON);
+        Assert.assertEquals(303200, cpu.clusters[0].frequencies[1].speedHz);
+        Assert.assertEquals(0.63f, cpu.clusters[0].frequencies[1].onMa, EPSILON);
+        Assert.assertEquals(380000, cpu.clusters[0].frequencies[2].speedHz);
+        Assert.assertEquals(1.23f, cpu.clusters[0].frequencies[2].onMa, EPSILON);
+        Assert.assertEquals(476000, cpu.clusters[0].frequencies[3].speedHz);
+        Assert.assertEquals(1.24f, cpu.clusters[0].frequencies[3].onMa, EPSILON);
+        Assert.assertEquals(552800, cpu.clusters[0].frequencies[4].speedHz);
+        Assert.assertEquals(2.47f, cpu.clusters[0].frequencies[4].onMa, EPSILON);
+        Assert.assertEquals(648800, cpu.clusters[0].frequencies[5].speedHz);
+        Assert.assertEquals(2.54f, cpu.clusters[0].frequencies[5].onMa, EPSILON);
+        Assert.assertEquals(725600, cpu.clusters[0].frequencies[6].speedHz);
+        Assert.assertEquals(3.60f, cpu.clusters[0].frequencies[6].onMa, EPSILON);
+        Assert.assertEquals(802400, cpu.clusters[0].frequencies[7].speedHz);
+        Assert.assertEquals(3.64f, cpu.clusters[0].frequencies[7].onMa, EPSILON);
+        Assert.assertEquals(879200, cpu.clusters[0].frequencies[8].speedHz);
+        Assert.assertEquals(4.42f, cpu.clusters[0].frequencies[8].onMa, EPSILON);
+        // Cluster 1
+        Assert.assertEquals(2, cpu.clusters[1].coreCount);
+        Assert.assertEquals(5.29f, cpu.clusters[1].onMa, EPSILON);
+        Assert.assertEquals(7, cpu.clusters[1].frequencies.length, EPSILON);
+        Assert.assertEquals(825600, cpu.clusters[1].frequencies[0].speedHz);
+        Assert.assertEquals(28.98f, cpu.clusters[1].frequencies[0].onMa, EPSILON);
+        Assert.assertEquals(902400, cpu.clusters[1].frequencies[1].speedHz);
+        Assert.assertEquals(31.40f, cpu.clusters[1].frequencies[1].onMa, EPSILON);
+        Assert.assertEquals(979200, cpu.clusters[1].frequencies[2].speedHz);
+        Assert.assertEquals(33.33f, cpu.clusters[1].frequencies[2].onMa, EPSILON);
+        Assert.assertEquals(1056000, cpu.clusters[1].frequencies[3].speedHz);
+        Assert.assertEquals(40.12f, cpu.clusters[1].frequencies[3].onMa, EPSILON);
+        Assert.assertEquals(1209600, cpu.clusters[1].frequencies[4].speedHz);
+        Assert.assertEquals(44.10f, cpu.clusters[1].frequencies[4].onMa, EPSILON);
+        Assert.assertEquals(1286400, cpu.clusters[1].frequencies[5].speedHz);
+        Assert.assertEquals(90.14f, cpu.clusters[1].frequencies[5].onMa, EPSILON);
+        Assert.assertEquals(1363200, cpu.clusters[1].frequencies[6].speedHz);
+        Assert.assertEquals(100f, cpu.clusters[1].frequencies[6].onMa, EPSILON);
+
+        // Flashlight
+        final FlashlightProfile flashlight
+                = (FlashlightProfile)profile.getComponent(Component.FLASHLIGHT);
+        Assert.assertEquals(1233.47f, flashlight.onMa, EPSILON);
+
+        // GPS
+        final GpsProfile gps = (GpsProfile)profile.getComponent(Component.GPS);
+        Assert.assertEquals(1.0f, gps.onMa, EPSILON);
+        Assert.assertEquals(2, gps.signalQualityMa.length);
+        Assert.assertEquals(88.0f, gps.signalQualityMa[0], EPSILON);
+        Assert.assertEquals(7.0f, gps.signalQualityMa[1], EPSILON);
+
+        // Modem
+        final ModemProfile modem = (ModemProfile)profile.getComponent(Component.MODEM);
+        Assert.assertEquals(1.0f, modem.sleepMa, EPSILON);
+        Assert.assertEquals(44.0f, modem.idleMa, EPSILON);
+        Assert.assertEquals(12.0f, modem.scanningMa, EPSILON);
+        Assert.assertEquals(11.0f, modem.rxMa, EPSILON);
+        Assert.assertEquals(5, modem.txMa.length);
+        Assert.assertEquals(16.0f, modem.txMa[0], EPSILON);
+        Assert.assertEquals(19.0f, modem.txMa[1], EPSILON);
+        Assert.assertEquals(22.0f, modem.txMa[2], EPSILON);
+        Assert.assertEquals(73.0f, modem.txMa[3], EPSILON);
+        Assert.assertEquals(132.0f, modem.txMa[4], EPSILON);
+
+        // Screen
+        final ScreenProfile screen = (ScreenProfile)profile.getComponent(Component.SCREEN);
+        Assert.assertEquals(102.4f, screen.onMa, EPSILON);
+        Assert.assertEquals(1234.0f, screen.fullMa, EPSILON);
+        Assert.assertEquals(12.0f, screen.ambientMa, EPSILON);
+
+        // Video
+        final VideoProfile video = (VideoProfile)profile.getComponent(Component.VIDEO);
+        Assert.assertEquals(123.0f, video.onMa, EPSILON);
+
+        // Wifi
+        final WifiProfile wifi = (WifiProfile)profile.getComponent(Component.WIFI);
+        Assert.assertEquals(2.0f, wifi.idleMa, EPSILON);
+        Assert.assertEquals(123.0f, wifi.rxMa, EPSILON);
+        Assert.assertEquals(333.0f, wifi.txMa, EPSILON);
+    }
+}
diff --git a/tools/powermodel/test/com/android/powermodel/PowerReportTest.java b/tools/powermodel/test/com/android/powermodel/PowerReportTest.java
new file mode 100644
index 0000000..1a61737
--- /dev/null
+++ b/tools/powermodel/test/com/android/powermodel/PowerReportTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.powermodel;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.junit.Test;
+import org.junit.Assert;
+
+import com.android.powermodel.component.ModemAppPower;
+import com.android.powermodel.component.ModemRemainderPower;
+
+/**
+ * Tests {@link PowerReport}.
+ */
+public class PowerReportTest {
+    private static final double EPSILON = 0.001;
+    private static final double MS_PER_HR = 3600000.0;
+
+    private static final double AVERAGE_MODEM_POWER = ((11+16+19+22+73+132) / 6.0);
+    private static final double GMAIL_MODEM_MAH = ((9925+5577) / (double)(97840+72941))
+            * 5113727 * AVERAGE_MODEM_POWER * (1.0 / 3600 / 1000);
+    private static final double GMAIL_MAH
+            = GMAIL_MODEM_MAH;
+
+    private static final double REMAINDER_MODEM_MAH
+            =  (1.0 / 3600 / 1000)
+            * ((3066958 * 16) + (0 * 19) + (34678 * 22) + (1643364 * 73) + (7045084 * 132)
+                + (2443805 * 12)
+                + (4923676 * AVERAGE_MODEM_POWER));
+    private static final double REMAINDER_MAH
+            = REMAINDER_MODEM_MAH;
+
+    private static final double TOTAL_MAH
+            = GMAIL_MAH
+            + REMAINDER_MAH;
+
+    private static InputStream loadPowerProfileStream() {
+        return PowerProfileTest.class.getResourceAsStream("/power_profile.xml");
+    }
+
+    private static InputStream loadCsvStream() {
+        return BatteryStatsReaderTest.class.getResourceAsStream("/bs.csv");
+    }
+
+    private static PowerReport loadPowerReport() throws Exception {
+        final PowerProfile profile = PowerProfile.parse(loadPowerProfileStream());
+        final ActivityReport activity = BatteryStatsReader.parse(loadCsvStream());
+        return PowerReport.createReport(profile, activity);
+    }
+
+    @Test public void testModemApp() throws Exception {
+        final PowerReport report = loadPowerReport();
+
+        final List<AppPower> gmailList = report.findApp("com.google.android.gm");
+        Assert.assertEquals(1, gmailList.size());
+        final AppPower gmail = gmailList.get(0);
+
+        final ModemAppPower modem = (ModemAppPower)gmail.getComponentPower(Component.MODEM);
+        Assert.assertNotNull(modem);
+        Assert.assertEquals(GMAIL_MODEM_MAH, modem.powerMah, EPSILON);
+    }
+
+    @Test public void testModemRemainder() throws Exception {
+        final PowerReport report = loadPowerReport();
+
+        final AppPower remainder = report.findApp(SpecialApp.REMAINDER);
+        Assert.assertNotNull(remainder);
+
+        final ModemRemainderPower modem
+                = (ModemRemainderPower)remainder.getComponentPower(Component.MODEM);
+        Assert.assertNotNull(modem);
+
+        Assert.assertArrayEquals(new double[] {
+                    3066958 * 16.0 / MS_PER_HR,
+                    0 * 19.0 / MS_PER_HR,
+                    34678 * 22.0 / MS_PER_HR,
+                    1643364 * 73.0 / MS_PER_HR,
+                    7045084 * 132.0 / MS_PER_HR },
+                modem.strengthMah, EPSILON);
+        Assert.assertEquals(2443805 * 12 / MS_PER_HR, modem.scanningMah, EPSILON);
+        Assert.assertEquals(4923676 * AVERAGE_MODEM_POWER / MS_PER_HR, modem.activeMah, EPSILON);
+
+        Assert.assertEquals(REMAINDER_MODEM_MAH, modem.powerMah, EPSILON);
+    }
+
+    @Test public void testAppTotal() throws Exception {
+        final PowerReport report = loadPowerReport();
+
+        final List<AppPower> gmailList = report.findApp("com.google.android.gm");
+        Assert.assertEquals(1, gmailList.size());
+        final AppPower gmail = gmailList.get(0);
+
+        Assert.assertEquals(GMAIL_MAH, gmail.getAppPowerMah(), EPSILON);
+    }
+
+    @Test public void testRemainderTotal() throws Exception {
+        final PowerReport report = loadPowerReport();
+
+        final AppPower remainder = report.findApp(SpecialApp.REMAINDER);
+        Assert.assertNotNull(remainder);
+
+        Assert.assertEquals(REMAINDER_MAH, remainder.getAppPowerMah(), EPSILON);
+    }
+
+    @Test public void testTotal() throws Exception {
+        final PowerReport report = loadPowerReport();
+
+        Assert.assertEquals(TOTAL_MAH, report.getTotalPowerMah(), EPSILON);
+    }
+}
+
diff --git a/tools/powermodel/test/com/android/powermodel/RawBatteryStatsTest.java b/tools/powermodel/test/com/android/powermodel/RawBatteryStatsTest.java
new file mode 100644
index 0000000..fbcac41
--- /dev/null
+++ b/tools/powermodel/test/com/android/powermodel/RawBatteryStatsTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.powermodel;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.junit.Test;
+import org.junit.Assert;
+
+/**
+ * Tests {@link RawBatteryStats}.
+ */
+public class RawBatteryStatsTest {
+    private static final int BS_VERSION = 32;
+
+    private static InputStream makeCsv(String... lines) {
+        return makeCsv(BS_VERSION, lines);
+    }
+
+    private static InputStream makeCsv(int version, String... lines) {
+        final StringBuilder result = new StringBuilder("9,0,i,vers,");
+        result.append(version);
+        result.append(",177,PPR1.180326.002,PQ1A.181105.015\n");
+        for (String line: lines) {
+            result.append(line);
+            result.append('\n');
+        }
+        return new ByteArrayInputStream(result.toString().getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Test public void testVersion() throws Exception {
+        final InputStream is = makeCsv();
+
+        final RawBatteryStats bs = RawBatteryStats.parse(is);
+        final List<RawBatteryStats.Record> records = bs.getRecords();
+        final RawBatteryStats.Version line = (RawBatteryStats.Version)records.get(0);
+
+        Assert.assertEquals(0, bs.getWarnings().size());
+        Assert.assertEquals(true, line.complete);
+
+        Assert.assertEquals(9, line.lineVersion);
+        Assert.assertEquals(0, line.uid);
+        Assert.assertEquals(RawBatteryStats.Category.INFO, line.category);
+        Assert.assertEquals("vers", line.lineType);
+
+        Assert.assertEquals(BS_VERSION, line.dumpsysVersion);
+        Assert.assertEquals(177, line.parcelVersion);
+        Assert.assertEquals("PPR1.180326.002", line.startPlatformVersion);
+        Assert.assertEquals("PQ1A.181105.015", line.endPlatformVersion);
+    }
+
+    @Test public void testUid() throws Exception {
+        final InputStream is = makeCsv("9,0,i,uid,1000,com.example.app");
+
+        final RawBatteryStats bs = RawBatteryStats.parse(is);
+        final List<RawBatteryStats.Record> records = bs.getRecords();
+        final RawBatteryStats.Uid line = (RawBatteryStats.Uid)records.get(1);
+
+        Assert.assertEquals(1000, line.uidKey);
+        Assert.assertEquals("com.example.app", line.pkg);
+    }
+
+    @Test public void testVarargs() throws Exception {
+        final InputStream is = makeCsv("9,0,i,gmcd,1,2,3,4,5,6,7");
+
+        final RawBatteryStats bs = RawBatteryStats.parse(is);
+        final List<RawBatteryStats.Record> records = bs.getRecords();
+        final RawBatteryStats.GlobalModemController line
+                = (RawBatteryStats.GlobalModemController)records.get(1);
+
+        Assert.assertEquals(1, line.idleMs);
+        Assert.assertEquals(2, line.rxTimeMs);
+        Assert.assertEquals(3, line.powerMaMs);
+        Assert.assertEquals(4, line.txTimeMs.length);
+        Assert.assertEquals(4, line.txTimeMs[0]);
+        Assert.assertEquals(5, line.txTimeMs[1]);
+        Assert.assertEquals(6, line.txTimeMs[2]);
+        Assert.assertEquals(7, line.txTimeMs[3]);
+    }
+}
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 8585ae9..88b7e2e 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -1128,7 +1128,10 @@
                 hadStringOrChain = true;
                 fprintf(out, "    jbyte* jbyte_array%d;\n", argIndex);
                 fprintf(out, "    const char* str%d;\n", argIndex);
-                fprintf(out, "    if (arg%d != NULL) {\n", argIndex);
+                fprintf(out,
+                        "    if (arg%d != NULL && env->GetArrayLength(arg%d) > "
+                        "0) {\n",
+                        argIndex, argIndex);
                 fprintf(out,
                         "        jbyte_array%d = "
                         "env->GetByteArrayElements(arg%d, NULL);\n",
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 3ec8a41..364d5084 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -58,7 +58,7 @@
      */
     oneway void requestActivityInfo(in ResultReceiver result);
 
-    ParceledListSlice getConfiguredNetworks();
+    ParceledListSlice getConfiguredNetworks(String packageName);
 
     ParceledListSlice getPrivilegedConfiguredNetworks();
 
@@ -90,11 +90,11 @@
 
     List<ScanResult> getScanResults(String callingPackage);
 
-    void disconnect(String packageName);
+    boolean disconnect(String packageName);
 
-    void reconnect(String packageName);
+    boolean reconnect(String packageName);
 
-    void reassociate(String packageName);
+    boolean reassociate(String packageName);
 
     WifiInfo getConnectionInfo(String callingPackage);
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7aff03c..8dd6c77 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1067,7 +1067,7 @@
     public List<WifiConfiguration> getConfiguredNetworks() {
         try {
             ParceledListSlice<WifiConfiguration> parceledList =
-                mService.getConfiguredNetworks();
+                    mService.getConfiguredNetworks(mContext.getOpPackageName());
             if (parceledList == null) {
                 return Collections.emptyList();
             }
@@ -1761,8 +1761,7 @@
     @Deprecated
     public boolean disconnect() {
         try {
-            mService.disconnect(mContext.getOpPackageName());
-            return true;
+            return mService.disconnect(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1786,8 +1785,7 @@
     @Deprecated
     public boolean reconnect() {
         try {
-            mService.reconnect(mContext.getOpPackageName());
-            return true;
+            return mService.reconnect(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1811,8 +1809,7 @@
     @Deprecated
     public boolean reassociate() {
         try {
-            mService.reassociate(mContext.getOpPackageName());
-            return true;
+            return mService.reassociate(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2132,14 +2129,14 @@
      * existing networks. You should assume the network IDs can be different
      * after calling this method.
      *
-     * @return {@code false} Will always return true.
+     * @return {@code false}.
      * @deprecated There is no need to call this method -
      * {@link #addNetwork(WifiConfiguration)}, {@link #updateNetwork(WifiConfiguration)}
      * and {@link #removeNetwork(int)} already persist the configurations automatically.
      */
     @Deprecated
     public boolean saveConfiguration() {
-        return true;
+        return false;
     }
 
     /**
@@ -3406,6 +3403,11 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK
+    })
     public void connect(WifiConfiguration config, ActionListener listener) {
         if (config == null) throw new IllegalArgumentException("config cannot be null");
         // Use INVALID_NETWORK_ID for arg1 when passing a config object
@@ -3426,7 +3428,12 @@
      * initialized again
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK
+    })
     public void connect(int networkId, ActionListener listener) {
         if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative");
         getChannel().sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
@@ -3452,7 +3459,12 @@
      * initialized again
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK
+    })
     public void save(WifiConfiguration config, ActionListener listener) {
         if (config == null) throw new IllegalArgumentException("config cannot be null");
         getChannel().sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
@@ -3471,7 +3483,12 @@
      * initialized again
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK
+    })
     public void forget(int netId, ActionListener listener) {
         if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
         getChannel().sendMessage(FORGET_NETWORK, netId, putListener(listener));
@@ -3486,7 +3503,12 @@
      * initialized again
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK
+    })
     public void disable(int netId, ActionListener listener) {
         if (netId < 0) throw new IllegalArgumentException("Network id cannot be negative");
         getChannel().sendMessage(DISABLE_NETWORK, netId, putListener(listener));
@@ -3498,6 +3520,12 @@
      * @param SSID, in the format of WifiConfiguration's SSID.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
+            android.Manifest.permission.NETWORK_STACK
+    })
     public void disableEphemeralNetwork(String SSID) {
         if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
         try {
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 529548f..6622a25 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -184,6 +184,9 @@
     public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings";
     /** {@hide} */
     public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource";
+    /** {@hide} */
+    public static final String REQUEST_PACKAGE_NAME_KEY = "PackageName";
+
     /**
      * scan configuration parameters to be sent to {@link #startBackgroundScan}
      */
@@ -798,6 +801,7 @@
         Bundle scanParams = new Bundle();
         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
+        scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
         mAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, scanParams);
     }
 
@@ -812,8 +816,11 @@
         int key = removeListener(listener);
         if (key == INVALID_KEY) return;
         validateChannel();
-        mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
+        Bundle scanParams = new Bundle();
+        scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+        mAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key, scanParams);
     }
+
     /**
      * reports currently available scan results on appropriate listeners
      * @return true if all scan results were reported correctly
@@ -821,7 +828,10 @@
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public boolean getScanResults() {
         validateChannel();
-        Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
+        Bundle scanParams = new Bundle();
+        scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+        Message reply =
+                mAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0, 0, scanParams);
         return reply.what == CMD_OP_SUCCEEDED;
     }
 
@@ -856,6 +866,7 @@
         Bundle scanParams = new Bundle();
         scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
         scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
+        scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
         mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
     }
 
@@ -870,7 +881,9 @@
         int key = removeListener(listener);
         if (key == INVALID_KEY) return;
         validateChannel();
-        mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
+        Bundle scanParams = new Bundle();
+        scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+        mAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key, scanParams);
     }
 
     /**
@@ -879,7 +892,10 @@
      */
     public List<ScanResult> getSingleScanResults() {
         validateChannel();
-        Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SINGLE_SCAN_RESULTS, 0);
+        Bundle scanParams = new Bundle();
+        scanParams.putString(REQUEST_PACKAGE_NAME_KEY, mContext.getOpPackageName());
+        Message reply = mAsyncChannel.sendMessageSynchronously(CMD_GET_SINGLE_SCAN_RESULTS, 0, 0,
+                scanParams);
         if (reply.what == WifiScanner.CMD_OP_SUCCEEDED) {
             return Arrays.asList(((ParcelableScanResults) reply.obj).getResults());
         }
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index 6772096..6631fa8 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -16,10 +16,16 @@
 
 package android.net.wifi.p2p;
 
+import android.annotation.IntDef;
 import android.annotation.UnsupportedAppUsage;
+import android.net.MacAddress;
 import android.net.wifi.WpsInfo;
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * A class representing a Wi-Fi P2p configuration for setting up a connection
@@ -38,12 +44,46 @@
      */
     public WpsInfo wps;
 
+    /**
+     * The network name of a group, should be configured by helper method
+     */
+    /** @hide */
+    public String networkName = "";
+
+    /**
+     * The passphrase of a group, should be configured by helper method
+     */
+    /** @hide */
+    public String passphrase = "";
+
+    /**
+     * The required band for Group Owner
+     */
+    /** @hide */
+    public int groupOwnerBand = GROUP_OWNER_BAND_AUTO;
+
     /** @hide */
     public static final int MAX_GROUP_OWNER_INTENT   =   15;
     /** @hide */
     @UnsupportedAppUsage
     public static final int MIN_GROUP_OWNER_INTENT   =   0;
 
+    /** @hide */
+    @IntDef(flag = false, prefix = { "GROUP_OWNER_BAND_" }, value = {
+        GROUP_OWNER_BAND_AUTO,
+        GROUP_OWNER_BAND_2GHZ,
+        GROUP_OWNER_BAND_5GHZ
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GroupOwnerBandType {}
+
+    /**
+     * Recognized Group Owner required band.
+     */
+    public static final int GROUP_OWNER_BAND_AUTO = 0;
+    public static final int GROUP_OWNER_BAND_2GHZ = 1;
+    public static final int GROUP_OWNER_BAND_5GHZ = 2;
+
     /**
      * This is an integer value between 0 and 15 where 0 indicates the least
      * inclination to be a group owner and 15 indicates the highest inclination
@@ -115,6 +155,10 @@
         sbuf.append("\n wps: ").append(wps);
         sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent);
         sbuf.append("\n persist: ").append(netId);
+        sbuf.append("\n networkName: ").append(networkName);
+        sbuf.append("\n passphrase: ").append(
+                TextUtils.isEmpty(passphrase) ? "<empty>" : "<non-empty>");
+        sbuf.append("\n groupOwnerBand: ").append(groupOwnerBand);
         return sbuf.toString();
     }
 
@@ -130,6 +174,9 @@
             wps = new WpsInfo(source.wps);
             groupOwnerIntent = source.groupOwnerIntent;
             netId = source.netId;
+            networkName = source.networkName;
+            passphrase = source.passphrase;
+            groupOwnerBand = source.groupOwnerBand;
         }
     }
 
@@ -139,6 +186,9 @@
         dest.writeParcelable(wps, flags);
         dest.writeInt(groupOwnerIntent);
         dest.writeInt(netId);
+        dest.writeString(networkName);
+        dest.writeString(passphrase);
+        dest.writeInt(groupOwnerBand);
     }
 
     /** Implement the Parcelable interface */
@@ -150,6 +200,9 @@
                 config.wps = (WpsInfo) in.readParcelable(null);
                 config.groupOwnerIntent = in.readInt();
                 config.netId = in.readInt();
+                config.networkName = in.readString();
+                config.passphrase = in.readString();
+                config.groupOwnerBand = in.readInt();
                 return config;
             }
 
@@ -157,4 +210,140 @@
                 return new WifiP2pConfig[size];
             }
         };
+
+    /**
+     * Builder used to build {@link WifiP2pConfig} objects for
+     * creating or joining a group.
+     */
+    public static final class Builder {
+
+        private static final MacAddress MAC_ANY_ADDRESS =
+                MacAddress.fromString("00:00:00:00:00:00");
+
+        private MacAddress mDeviceAddress = MAC_ANY_ADDRESS;
+        private String mNetworkName = "";
+        private String mPassphrase = "";
+        private int mGroupOwnerBand = GROUP_OWNER_BAND_AUTO;
+        private int mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
+
+        /**
+         * Specify the peer's MAC address. If not set, the device will
+         * try to find a peer whose SSID matches the network name as
+         * specified by {@link #setNetworkName(String)}. Specifying null will
+         * reset the peer's MAC address to "00:00:00:00:00:00".
+         * <p>
+         *     Optional. "00:00:00:00:00:00" by default.
+         *
+         * @param deviceAddress the peer's MAC address.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setDeviceAddress(MacAddress deviceAddress) {
+            if (deviceAddress == null) {
+                mDeviceAddress = MAC_ANY_ADDRESS;
+            } else {
+                mDeviceAddress = deviceAddress;
+            }
+            return this;
+        }
+
+        /**
+         * Specify the network name, a.k.a. group name,
+         * for creating or joining a group.
+         * <p>
+         *     Must be called - an empty network name is not valid.
+         *
+         * @param networkName network name of a group.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setNetworkName(String networkName) {
+            if (TextUtils.isEmpty(networkName)) {
+                throw new IllegalArgumentException(
+                        "network name must be non-empty.");
+            }
+            mNetworkName = networkName;
+            return this;
+        }
+
+        /**
+         * Specify the passphrase for creating or joining a group.
+         * <p>
+         *     Must be called - an empty passphrase is not valid.
+         *
+         * @param passphrase the passphrase of a group.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setPassphrase(String passphrase) {
+            if (TextUtils.isEmpty(passphrase)) {
+                throw new IllegalArgumentException(
+                        "passphrase must be non-empty.");
+            }
+            mPassphrase = passphrase;
+            return this;
+        }
+
+        /**
+         * Specify the band to use for creating the group. This method only applies when
+         * creating a group as Group Owner using {@link WifiP2pManager#createGroup}.
+         * The band should be {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ},
+         * or allow the system to pick the band by specifying {@link #GROUP_OWNER_BAND_AUTO}.
+         * If the Group Owner cannot create a group in the specified band, the operation will fail.
+         * <p>
+         *     Optional. {@link #GROUP_OWNER_BAND_AUTO} by default.
+         *
+         * @param band the required band of group owner.
+         *             This should be one of {@link #GROUP_OWNER_BAND_AUTO},
+         *             {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setGroupOwnerBand(int band) {
+            mGroupOwnerBand = band;
+            return this;
+        }
+
+        /**
+         * Specify that the group configuration be persisted (i.e. saved).
+         * By default the group configuration will not be saved.
+         * <p>
+         *     Optional. false by default.
+         *
+         * @param persistent is this group persistent group.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder enablePersistentMode(boolean persistent) {
+            if (persistent) {
+                mNetId = WifiP2pGroup.PERSISTENT_NET_ID;
+            } else {
+                mNetId = WifiP2pGroup.TEMPORARY_NET_ID;
+            }
+            return this;
+        }
+
+        /**
+         * Build {@link WifiP2pConfig} given the current requests made on the builder.
+         * @return {@link WifiP2pConfig} constructed based on builder method calls.
+         */
+        public WifiP2pConfig build() {
+            if (TextUtils.isEmpty(mNetworkName)) {
+                throw new IllegalStateException(
+                        "network name must be non-empty.");
+            }
+            if (TextUtils.isEmpty(mPassphrase)) {
+                throw new IllegalStateException(
+                        "passphrase must be non-empty.");
+            }
+
+            WifiP2pConfig config = new WifiP2pConfig();
+            config.deviceAddress = mDeviceAddress.toString();
+            config.networkName = mNetworkName;
+            config.passphrase = mPassphrase;
+            config.groupOwnerBand = mGroupOwnerBand;
+            config.netId = mNetId;
+            return config;
+        }
+    }
 }
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index 04bc557..aa526d2 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -73,7 +73,7 @@
     }
 
     @Override
-    public ParceledListSlice getConfiguredNetworks() {
+    public ParceledListSlice getConfiguredNetworks(String packageName) {
         throw new UnsupportedOperationException();
     }
 
@@ -188,17 +188,17 @@
     }
 
     @Override
-    public void disconnect(String packageName) {
+    public boolean disconnect(String packageName) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void reconnect(String packageName) {
+    public boolean reconnect(String packageName) {
         throw new UnsupportedOperationException();
     }
 
     @Override
-    public void reassociate(String packageName) {
+    public boolean reassociate(String packageName) {
         throw new UnsupportedOperationException();
     }