Merge "Readd missing null check"
diff --git a/api/current.txt b/api/current.txt
index bc41d65..6da09e3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1234,6 +1234,7 @@
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportsAssist = 16844016; // 0x10104f0
+    field public static final int supportsDismissingWindow = 16844104; // 0x1010548
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
     field public static final int supportsLocalInteraction = 16844047; // 0x101050f
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
@@ -8959,6 +8960,7 @@
     field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+    field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
     field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
     field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
     field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 24f9aad..5640eaf 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1347,6 +1347,7 @@
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportsAssist = 16844016; // 0x10104f0
+    field public static final int supportsDismissingWindow = 16844104; // 0x1010548
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
     field public static final int supportsLocalInteraction = 16844047; // 0x101050f
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
@@ -9335,6 +9336,7 @@
     field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+    field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
     field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
     field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
     field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
@@ -41395,6 +41397,8 @@
     method public java.lang.String getDeviceSoftwareVersion();
     method public java.lang.String getGroupIdLevel1();
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
+    method public java.lang.String getImei();
+    method public java.lang.String getImei(int);
     method public java.lang.String getLine1Number();
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
diff --git a/api/test-current.txt b/api/test-current.txt
index 4c277e4..714cac9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1234,6 +1234,7 @@
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
     field public static final int supportsAssist = 16844016; // 0x10104f0
+    field public static final int supportsDismissingWindow = 16844104; // 0x1010548
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
     field public static final int supportsLocalInteraction = 16844047; // 0x101050f
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
@@ -8984,6 +8985,7 @@
     field public static final java.lang.String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
     field public static final java.lang.String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
+    field public static final java.lang.String EXTRA_CONTENT_ANNOTATIONS = "android.intent.extra.CONTENT_ANNOTATIONS";
     field public static final java.lang.String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
     field public static final java.lang.String EXTRA_DOCK_STATE = "android.intent.extra.DOCK_STATE";
     field public static final int EXTRA_DOCK_STATE_CAR = 2; // 0x2
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index f2a8777..a248bce 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -768,6 +768,10 @@
     /**
      * Called when a new batch of security logs can be retrieved.
      *
+     * <p>If a secondary user or profile is created, this callback won't be received until all users
+     * become affiliated again (even if security logging is enabled).
+     * See {@link DevicePolicyManager#setAffiliationIds}
+     *
      * <p>This callback is only applicable to device owners.
      *
      * @param context The running context as per {@link #onReceive}.
@@ -782,13 +786,18 @@
      * ever be called when network logging is enabled. The logs can only be retrieved while network
      * logging is enabled.
      *
+     * <p>If a secondary user or profile is created, this callback won't be received until all users
+     * become affiliated again (even if network logging is enabled). It will also no longer be
+     * possible to retrieve the network logs batch with the most recent {@code batchToken} provided
+     * by this callback. See {@link DevicePolicyManager#setAffiliationIds}.
+     *
      * <p>This callback is only applicable to device owners.
      *
      * @param context The running context as per {@link #onReceive}.
      * @param intent The received intent as per {@link #onReceive}.
      * @param batchToken The token representing the current batch of network logs.
      * @param networkLogsCount The total count of events in the current batch of network logs.
-     * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName)
+     * @see DevicePolicyManager#retrieveNetworkLogs
      */
     public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
             int networkLogsCount) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 074326f..c95e011 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3651,15 +3651,16 @@
     /**
      * Called by a device owner to request a bugreport.
      * <p>
-     * There must be only one user on the device, managed by the device owner. Otherwise a
-     * {@link SecurityException} will be thrown.
+     * If the device contains secondary users or profiles, they must be affiliated with the device
+     * owner user. Otherwise a {@link SecurityException} will be thrown. See
+     * {@link #setAffiliationIds}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @return {@code true} if the bugreport collection started successfully, or {@code false} if it
      *         wasn't triggered because a previous bugreport operation is still active (either the
      *         bugreport is still running or waiting for the user to share or decline)
-     * @throws SecurityException if {@code admin} is not a device owner, or if there are users other
-     *             than the one managed by the device owner.
+     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+     *         profile or secondary user that is not affiliated with the device owner user.
      */
     public boolean requestBugreport(@NonNull ComponentName admin) {
         throwIfParentInstance("requestBugreport");
@@ -6631,14 +6632,16 @@
     }
 
     /**
-     * Called by device owner to control the security logging feature. Logging can only be
-     * enabled on single user devices where the sole user is managed by the device owner.
+     * Called by device owner to control the security logging feature.
      *
      * <p> Security logs contain various information intended for security auditing purposes.
      * See {@link SecurityEvent} for details.
      *
-     * <p>There must be only one user on the device, managed by the device owner.
-     * Otherwise a {@link SecurityException} will be thrown.
+     * <p><strong>Note:</strong> The device owner won't be able to retrieve security logs if there
+     * are unaffiliated secondary users or profiles on the device, regardless of whether the
+     * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
+     * all users to become affiliated. Therefore it's recommended that affiliation ids are set for
+     * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
      *
      * @param admin Which device owner this request is associated with.
      * @param enabled whether security logging should be enabled or not.
@@ -6680,13 +6683,16 @@
      * <p> Access to the logs is rate limited and it will only return new logs after the device
      * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}.
      *
-     * <p>There must be only one user on the device, managed by the device owner.
-     * Otherwise a {@link SecurityException} will be thrown.
+     * <p>If there is any other user or profile on the device, it must be affiliated with the
+     * device owner. Otherwise a {@link SecurityException} will be thrown. See
+     * {@link #setAffiliationIds}
      *
      * @param admin Which device owner this request is associated with.
      * @return the new batch of security logs which is a list of {@link SecurityEvent},
      * or {@code null} if rate limitation is exceeded or if logging is currently disabled.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+     * profile or secondary user that is not affiliated with the device owner user.
+     * @see DeviceAdminReceiver#onSecurityLogsAvailable
      */
     public @Nullable List<SecurityEvent> retrieveSecurityLogs(@NonNull ComponentName admin) {
         throwIfParentInstance("retrieveSecurityLogs");
@@ -6726,14 +6732,17 @@
      * will result in {@code null} being returned. The device logs are retrieved from a RAM region
      * which is not guaranteed to be corruption-free during power cycles, as a result be cautious
      * about data corruption when parsing. </strong>
-     * <p>
-     * There must be only one user on the device, managed by the device owner. Otherwise a
-     * {@link SecurityException} will be thrown.
+     *
+     * <p>If there is any other user or profile on the device, it must be affiliated with the
+     * device owner. Otherwise a {@link SecurityException} will be thrown. See
+     * {@link #setAffiliationIds}
      *
      * @param admin Which device owner this request is associated with.
      * @return Device logs from before the latest reboot of the system, or {@code null} if this API
      *         is not supported on the device.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+     * profile or secondary user that is not affiliated with the device owner user.
+     * @see #retrieveSecurityLogs
      */
     public @Nullable List<SecurityEvent> retrievePreRebootSecurityLogs(
             @NonNull ComponentName admin) {
@@ -6939,6 +6948,12 @@
      * Indicates the entity that controls the device or profile owner. Two users/profiles are
      * affiliated if the set of ids set by their device or profile owners intersect.
      *
+     * <p><strong>Note:</strong> Features that depend on user affiliation (such as security logging
+     * or {@link #bindDeviceAdminServiceAsUser}) won't be available when a secondary user or profile
+     * is created, until it becomes affiliated. Therefore it is recommended that the appropriate
+     * affiliation ids are set by its profile owner as soon as possible after the user/profile is
+     * created.
+     *
      * @param admin Which profile or device owner this request is associated with.
      * @param ids A list of opaque non-empty affiliation ids. Duplicate elements will be ignored.
      *
@@ -7138,15 +7153,19 @@
     }
 
     /**
-     * Called by a device owner to control the network logging feature. Logging can only be
-     * enabled on single user devices where the sole user is managed by the device owner. If a new
-     * user is added on the device, logging is disabled.
+     * Called by a device owner to control the network logging feature.
      *
      * <p> Network logs contain DNS lookup and connect() library call events.
      *
+     * <p><strong>Note:</strong> The device owner won't be able to retrieve network logs if there
+     * are unaffiliated secondary users or profiles on the device, regardless of whether the
+     * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
+     * all users to become affiliated. Therefore it's recommended that affiliation ids are set for
+     * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param enabled whether network logging should be enabled or not.
-     * @throws {@link SecurityException} if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner.
      * @see #retrieveNetworkLogs
      */
     public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) {
@@ -7164,7 +7183,7 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Can only
      * be {@code null} if the caller has MANAGE_USERS permission.
      * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
-     * @throws {@link SecurityException} if {@code admin} is not a device owner and caller has
+     * @throws SecurityException if {@code admin} is not a device owner and caller has
      * no MANAGE_USERS permission
      */
     public boolean isNetworkLoggingEnabled(@Nullable ComponentName admin) {
@@ -7190,12 +7209,19 @@
      * after the device device owner has been notified via
      * {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
      *
+     * <p>If a secondary user or profile is created, calling this method will throw a
+     * {@link SecurityException} until all users become affiliated again. It will also no longer be
+     * possible to retrieve the network logs batch with the most recent batchToken provided
+     * by {@link DeviceAdminReceiver#onNetworkLogsAvailable}. See
+     * {@link DevicePolicyManager#setAffiliationIds}.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param batchToken A token of the batch to retrieve
      * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
      *        {@code null} if the batch represented by batchToken is no longer available or if
      *        logging is disabled.
-     * @throws {@link SecurityException} if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
+     * profile or secondary user that is not affiliated with the device owner user.
      * @see DeviceAdminReceiver#onNetworkLogsAvailable
      */
     public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b05ceaa..d8358f9 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3843,6 +3843,52 @@
             = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
 
     /**
+     * An {@code ArrayList} of {@code String} annotations describing content for
+     * {@link #ACTION_CHOOSER}.
+     *
+     * <p>If {@link #EXTRA_CONTENT_ANNOTATIONS} is present in an intent used to start a
+     * {@link #ACTION_CHOOSER} activity, the first three annotations will be used to rank apps.</p>
+     *
+     * <p>Annotations should describe the major components or topics of the content. It is up to
+     * apps initiating {@link #ACTION_CHOOSER} to learn and add annotations. Annotations should be
+     * learned in advance, e.g., when creating or saving content, to avoid increasing latency to
+     * start {@link #ACTION_CHOOSER}. Performance on customized annotations can suffer, if they are
+     * rarely used for {@link #ACTION_CHOOSER} in the past 14 days. Therefore, it is recommended to
+     * use the following annotations when applicable:</p>
+     * <ul>
+     *     <li>"product": represents that the topic of the content is mainly about products, e.g.,
+     *     health & beauty, and office supplies.</li>
+     *     <li>"emotion": represents that the topic of the content is mainly about emotions, e.g.,
+     *     happy, and sad.</li>
+     *     <li>"person": represents that the topic of the content is mainly about persons, e.g.,
+     *     face, finger, standing, and walking.</li>
+     *     <li>"child": represents that the topic of the content is mainly about children, e.g.,
+     *     child, and baby.</li>
+     *     <li>"selfie": represents that the topic of the content is mainly about selfies.</li>
+     *     <li>"crowd": represents that the topic of the content is mainly about crowds.</li>
+     *     <li>"party": represents that the topic of the content is mainly about parties.</li>
+     *     <li>"animal": represent that the topic of the content is mainly about animals.</li>
+     *     <li>"plant": represents that the topic of the content is mainly about plants, e.g.,
+     *     flowers.</li>
+     *     <li>"vacation": represents that the topic of the content is mainly about vacations.</li>
+     *     <li>"fashion": represents that the topic of the content is mainly about fashion, e.g.
+     *     sunglasses, jewelry, handbags and clothing.</li>
+     *     <li>"material": represents that the topic of the content is mainly about materials, e.g.,
+     *     paper, and silk.</li>
+     *     <li>"vehicle": represents that the topic of the content is mainly about vehicles, like
+     *     cars, and boats.</li>
+     *     <li>"document": represents that the topic of the content is mainly about documents, e.g.
+     *     posters.</li>
+     *     <li>"design": represents that the topic of the content is mainly about design, e.g. arts
+     *     and designs of houses.</li>
+     *     <li>"holiday": represents that the topic of the content is mainly about holidays, e.g.,
+     *     Christmas and Thanksgiving.</li>
+     * </ul>
+     */
+    public static final String EXTRA_CONTENT_ANNOTATIONS
+            = "android.intent.extra.CONTENT_ANNOTATIONS";
+
+    /**
      * A {@link ResultReceiver} used to return data back to the sender.
      *
      * <p>Used to complete an app-specific
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index d67b6a8..9b72757a 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -438,7 +438,8 @@
      * Soft iron - These distortions arise due to the interaction with the earth's magnetic
      * field.
      * </p>
-     * <h4> {@link android.hardware.Sensor#TYPE_GAME_ROTATION_VECTOR}:</h4>
+     * <h4> {@link android.hardware.Sensor#TYPE_GAME_ROTATION_VECTOR
+     * Sensor.TYPE_GAME_ROTATION_VECTOR}:</h4>
      * Identical to {@link android.hardware.Sensor#TYPE_ROTATION_VECTOR} except that it
      * doesn't use the geomagnetic field. Therefore the Y axis doesn't
      * point north, but instead to some other reference, that reference is
@@ -482,22 +483,6 @@
      * <p><b>Pro Tip:</b> Always use the length of the values array while performing operations
      * on it. In earlier versions, this used to be always 3 which has changed now. </p>
      *
-     * @see GeomagneticField
-     *
-     * <h4> {@link android.hardware.Sensor#TYPE_DEVICE_ORIENTATION
-     * Sensor.TYPE_DEVICE_ORIENTATION}:</h4>
-     * The current device orientation will be available in values[0]. The only
-     * available values are:
-     * <ul>
-     * <li> 0: device is in default orientation (Y axis is vertical and points up)
-     * <li> 1: device is rotated 90 degrees counter-clockwise from default
-     *         orientation (X axis is vertical and points up)
-     * <li> 2: device is rotated 180 degrees from default orientation (Y axis is
-     *         vertical and points down)
-     * <li> 3: device is rotated 90 degrees clockwise from default orientation (X axis
-     *         is vertical and points down)
-     * </ul>
-     *
      *   <h4>{@link android.hardware.Sensor#TYPE_POSE_6DOF
      * Sensor.TYPE_POSE_6DOF}:</h4>
      *
@@ -605,6 +590,8 @@
      * temperature compensation is allowed).
      * x_bias, y_bias, z_bias are the estimated biases.
      * </p>
+     *
+     * @see GeomagneticField
      */
     public final float[] values;
 
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index c7612d1..a265dd0 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -212,7 +212,7 @@
     public native final HwBlob readBuffer();
 
     public native final HwBlob readEmbeddedBuffer(
-            long parentHandle, long offset);
+            long parentHandle, long offset, boolean nullable);
 
     public native final void writeBuffer(HwBlob blob);
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 1c3d6bd..dbe2f6d 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -312,11 +312,6 @@
      */
     void setDnsConfigurationForNetwork(int netId, in String[] servers, String domains);
 
-    /**
-     * Bind name servers to a network in the DNS resolver.
-     */
-    void setDnsServersForNetwork(int netId, in String[] servers, String domains);
-
     void setFirewallEnabled(boolean enabled);
     boolean isFirewallEnabled();
     void setFirewallInterfaceRule(String iface, boolean allow);
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index c5ceecd..1ee83ae 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -25,5 +25,5 @@
     boolean uncrypt(in String packageFile, IRecoverySystemProgressListener listener);
     boolean setupBcb(in String command);
     boolean clearBcb();
-    void rebootRecoveryWithCommand(in String command);
+    void rebootRecoveryWithCommand(in String command, in boolean update);
 }
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index d48431a..7f9ea438 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -491,15 +491,10 @@
                 command += securityArg;
             }
 
+            // RECOVERY_SERVICE writes to BCB (bootloader control block) and triggers the reboot.
             RecoverySystem rs = (RecoverySystem) context.getSystemService(
                     Context.RECOVERY_SERVICE);
-            if (!rs.setupBcb(command)) {
-                throw new IOException("Setup BCB failed");
-            }
-
-            // Having set up the BCB (bootloader control block), go ahead and reboot
-            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-            pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
+            rs.rebootRecoveryWithCommand(command, true /* update */);
 
             throw new IOException("Reboot failed (no permissions?)");
         }
@@ -713,7 +708,7 @@
         // Write the command into BCB (bootloader control block) and boot from
         // there. Will not return unless failed.
         RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
-        rs.rebootRecoveryWithCommand(command.toString());
+        rs.rebootRecoveryWithCommand(command.toString(), false);
 
         throw new IOException("Reboot failed (no permissions?)");
     }
@@ -913,9 +908,9 @@
      * Talks to RecoverySystemService via Binder to set up the BCB command and
      * reboot into recovery accordingly.
      */
-    private void rebootRecoveryWithCommand(String command) {
+    private void rebootRecoveryWithCommand(String command, boolean update) {
         try {
-            mService.rebootRecoveryWithCommand(command);
+            mService.rebootRecoveryWithCommand(command, update);
         } catch (RemoteException ignored) {
         }
     }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 5c8e6dc..b6da1d8 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -57,6 +57,7 @@
  * @attr ref android.R.styleable#InputMethod_settingsActivity
  * @attr ref android.R.styleable#InputMethod_isDefault
  * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod
+ * @attr ref android.R.styleable#InputMethod_supportsDismissingWindow
  */
 public final class InputMethodInfo implements Parcelable {
     static final String TAG = "InputMethodInfo";
@@ -104,6 +105,11 @@
     private final boolean mSupportsSwitchingToNextInputMethod;
 
     /**
+     * The flag whether this IME supports ways to dismiss its window (e.g. dismiss button.)
+     */
+    private final boolean mSupportsDismissingWindow;
+
+    /**
      * Constructor.
      *
      * @param context The Context in which we are parsing the input method.
@@ -132,6 +138,7 @@
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
         boolean isAuxIme = true;
         boolean supportsSwitchingToNextInputMethod = false; // false as default
+        boolean supportsDismissingWindow = false; // false as default
         mForceDefault = false;
 
         PackageManager pm = context.getPackageManager();
@@ -171,6 +178,8 @@
             supportsSwitchingToNextInputMethod = sa.getBoolean(
                     com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod,
                     false);
+            supportsDismissingWindow = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_supportsDismissingWindow, false);
             sa.recycle();
 
             final int depth = parser.getDepth();
@@ -242,6 +251,7 @@
         mIsDefaultResId = isDefaultResId;
         mIsAuxIme = isAuxIme;
         mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+        mSupportsDismissingWindow = supportsDismissingWindow;
     }
 
     InputMethodInfo(Parcel source) {
@@ -250,6 +260,7 @@
         mIsDefaultResId = source.readInt();
         mIsAuxIme = source.readInt() == 1;
         mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
+        mSupportsDismissingWindow = source.readInt() == 1;
         mService = ResolveInfo.CREATOR.createFromParcel(source);
         mSubtypes = new InputMethodSubtypeArray(source);
         mForceDefault = false;
@@ -260,8 +271,10 @@
      */
     public InputMethodInfo(String packageName, String className,
             CharSequence label, String settingsActivity) {
-        this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
-                0, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */);
+        this(buildDummyResolveInfo(packageName, className, label), false /* isAuxIme */,
+                settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
+                false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+                true /* supportsDismissingWindow */);
     }
 
     /**
@@ -271,17 +284,18 @@
     public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
             String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
             boolean forceDefault) {
-        this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId,
-                forceDefault, true /* supportsSwitchingToNextInputMethod */);
+        this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
+                 true /* supportsSwitchingToNextInputMethod */,
+                 true /* supportsDismissingWindow */);
     }
 
     /**
      * Temporary API for creating a built-in input method for test.
      * @hide
      */
-    public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
-            String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
-            boolean forceDefault, boolean supportsSwitchingToNextInputMethod) {
+    public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
+            List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
+            boolean supportsSwitchingToNextInputMethod, boolean supportsDismissingWindow) {
         final ServiceInfo si = ri.serviceInfo;
         mService = ri;
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -291,6 +305,7 @@
         mSubtypes = new InputMethodSubtypeArray(subtypes);
         mForceDefault = forceDefault;
         mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod;
+        mSupportsDismissingWindow = supportsDismissingWindow;
     }
 
     private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
@@ -431,7 +446,8 @@
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
                 + " mSettingsActivityName=" + mSettingsActivityName
-                + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod);
+                + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
+                + " mSupportsDismissingWindow=" + mSupportsDismissingWindow);
         pw.println(prefix + "mIsDefaultResId=0x"
                 + Integer.toHexString(mIsDefaultResId));
         pw.println(prefix + "Service:");
@@ -484,6 +500,14 @@
     }
 
     /**
+     * @return true if this input method supports ways to dismiss its window.
+     * @hide
+     */
+    public boolean supportsDismissingWindow() {
+        return mSupportsDismissingWindow;
+    }
+
+    /**
      * Used to package this object into a {@link Parcel}.
      *
      * @param dest The {@link Parcel} to be written.
@@ -496,6 +520,7 @@
         dest.writeInt(mIsDefaultResId);
         dest.writeInt(mIsAuxIme ? 1 : 0);
         dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
+        dest.writeInt(mSupportsDismissingWindow ? 1 : 0);
         mService.writeToParcel(dest, flags);
         mSubtypes.writeToParcel(dest);
     }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 7878611..d4baa18 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -418,7 +418,7 @@
                 }
             }
         }
-        updateChooserCounts(target);
+        updateModelAndChooserCounts(target);
         return super.onTargetSelected(target, alwaysCheck);
     }
 
@@ -575,27 +575,18 @@
         // Do nothing. We'll send the voice stuff ourselves.
     }
 
-    void updateChooserCounts(TargetInfo info) {
+    void updateModelAndChooserCounts(TargetInfo info) {
         if (info != null) {
-            UsageStatsManager usageStatsManager =
-                    (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
-            if (usageStatsManager == null) {
-                if (DEBUG) {
-                    Log.d(TAG, "Can not start UsageStatsManager");
-                }
-                return;
-            }
             final ResolveInfo ri = info.getResolveInfo();
             Intent targetIntent = getTargetIntent();
             if (ri != null && ri.activityInfo != null && targetIntent != null) {
-                usageStatsManager.reportChooserSelection(ri.activityInfo.packageName, getUserId(),
-                        targetIntent.getType(), null, targetIntent.getAction());
                 if (mAdapter != null) {
                     mAdapter.updateModel(info.getResolvedComponentName());
+                    mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
+                            targetIntent.getAction());
                 }
                 if (DEBUG) {
                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
-                    Log.d(TAG, "Annotation to be updated is " + targetIntent.getType());
                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
                 }
             } else if(DEBUG) {
@@ -618,7 +609,7 @@
         } else {
             TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
             if (super.onTargetSelected(clonedTarget, false)) {
-                updateChooserCounts(clonedTarget);
+                updateModelAndChooserCounts(clonedTarget);
                 finish();
                 return;
             }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0950630..d734d17 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1299,6 +1299,10 @@
             mResolverListController.updateModel(componentName);
         }
 
+        public void updateChooserCounts(String packageName, int userId, String action) {
+            mResolverListController.updateChooserCounts(packageName, userId, action);
+        }
+
         /**
          * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
          * to complete.
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 45fad97f..d9ab47e 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -52,6 +52,8 @@
 
     private static final boolean DEBUG = false;
 
+    private static final int NUM_OF_TOP_ANNOTATIONS_TO_USE = 3;
+
     // One week
     private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
 
@@ -74,7 +76,8 @@
     private final long mSinceTime;
     private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
     private final String mReferrerPackage;
-    public String mContentType;
+    private String mContentType;
+    private String[] mAnnotations;
     private String mAction;
     private LogisticRegressionAppRanker mRanker;
 
@@ -91,10 +94,26 @@
         mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
         mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
         mContentType = intent.getType();
+        getContentAnnotations(intent);
         mAction = intent.getAction();
         mRanker = new LogisticRegressionAppRanker(context);
     }
 
+    public void getContentAnnotations(Intent intent) {
+        ArrayList<String> annotations = intent.getStringArrayListExtra(
+                Intent.EXTRA_CONTENT_ANNOTATIONS);
+        if (annotations != null) {
+            int size = annotations.size();
+            if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
+                size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
+            }
+            mAnnotations = new String[size];
+            for (int i = 0; i < size; i++) {
+                mAnnotations[i] = annotations.get(i);
+            }
+        }
+    }
+
     public void compute(List<ResolvedComponentInfo> targets) {
         mScoredTargets.clear();
 
@@ -132,12 +151,18 @@
                 if (launched > mostLaunched) {
                     mostLaunched = launched;
                 }
-                // TODO(kanlig): get and combine counts of categories.
 
                 int selected = 0;
                 if (pkStats.mChooserCounts != null && mAction != null
                         && pkStats.mChooserCounts.get(mAction) != null) {
                     selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0);
+                    if (mAnnotations != null) {
+                        final int size = mAnnotations.length;
+                        for (int i = 0; i < size; i++) {
+                            selected += pkStats.mChooserCounts.get(mAction)
+                                    .getOrDefault(mAnnotations[i], 0);
+                        }
+                    }
                 }
                 if (DEBUG) {
                     if (mAction == null) {
@@ -288,6 +313,12 @@
         }
     }
 
+    public void updateChooserCounts(String packageName, int userId, String action) {
+        if (mUsm != null) {
+            mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
+        }
+    }
+
     public void updateModel(ComponentName componentName) {
         if (mScoredTargets == null || componentName == null ||
                 !mScoredTargets.containsKey(componentName)) {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index d864a31..f88f6f9 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -224,4 +224,10 @@
             mResolverComparator.updateModel(componentName);
         }
     }
+
+    public void updateChooserCounts(String packageName, int userId, String action) {
+        if (mResolverComparator != null) {
+            mResolverComparator.updateChooserCounts(packageName, userId, action);
+        }
+    }
 }
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index aefdc84..1bd2333 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -822,7 +822,8 @@
 }
 
 static jobject JHwParcel_native_readEmbeddedBuffer(
-        JNIEnv *env, jobject thiz, jlong parentHandle, jlong offset) {
+        JNIEnv *env, jobject thiz, jlong parentHandle, jlong offset,
+        jboolean nullable) {
     hardware::Parcel *parcel =
         JHwParcel::GetNativeContext(env, thiz)->getParcel();
 
@@ -830,11 +831,15 @@
 
     const void *ptr;
     status_t status =
-        parcel->readEmbeddedBuffer(&childHandle, parentHandle, offset, &ptr);
+        parcel->readNullableEmbeddedBuffer(&childHandle, parentHandle, offset,
+                &ptr);
 
     if (status != OK) {
         jniThrowException(env, "java/util/NoSuchElementException", NULL);
         return 0;
+    } else if (status == OK && !nullable && ptr == nullptr) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return 0;
     }
 
     return JHwBlob::NewObject(env, ptr, childHandle);
@@ -945,7 +950,7 @@
     { "readBuffer", "()L" PACKAGE_PATH "/HwBlob;",
         (void *)JHwParcel_native_readBuffer },
 
-    { "readEmbeddedBuffer", "(JJ)L" PACKAGE_PATH "/HwBlob;",
+    { "readEmbeddedBuffer", "(JJZ)L" PACKAGE_PATH "/HwBlob;",
         (void *)JHwParcel_native_readEmbeddedBuffer },
 
     { "writeBuffer", "(L" PACKAGE_PATH "/HwBlob;)V",
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5458e7c..dd33718 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3178,6 +3178,18 @@
              and subtype in order to provide the consistent user experience in switching
              between IMEs and subtypes. -->
         <attr name="supportsSwitchingToNextInputMethod" format="boolean" />
+        <!-- Set to true if this input method supports ways to dismiss the windows assigned to
+             the input method (e.g. a dismiss button rendered by the input method itself).  The
+             System UI may optimize the UI by not showing system-level dismiss button if this
+             value is true.
+             <p> Must be a boolean value, either "true" or "false". The default value is "false".
+             <p> This may also be a reference to a resource (in the form "@[package:]type:name")
+             or theme attribute (in the form "?[package:]type:name") containing a value of this
+             type.
+             <p> A UI element that dismisses the input method window should report
+             {@link android.view.accessibility.AccessibilityNodeInfo#ACTION_DISMISS} action, so
+             that accessibility services can handle it accordingly. -->
+        <attr name="supportsDismissingWindow" format="boolean" />
     </declare-styleable>
 
     <!-- This is the subtype of InputMethod. Subtype can describe locales (e.g. en_US, fr_FR...)
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 73cba89..064d31e 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2784,6 +2784,7 @@
         <public name="focusedByDefault" />
         <public name="appCategory" />
         <public name="autoSizeMaxTextSize" />
+        <public name="supportsDismissingWindow" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/coretests/res/xml/ime_meta.xml
new file mode 100644
index 0000000..a975718
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    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.
+-->
+
+<input-method
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+>
+  <subtype
+      android:label="subtype1"
+      android:imeSubtypeLocale="en_US"
+      android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/res/xml/ime_meta_dismiss.xml b/core/tests/coretests/res/xml/ime_meta_dismiss.xml
new file mode 100644
index 0000000..59f8ecc
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta_dismiss.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    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.
+-->
+
+<input-method
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+    android:supportsDismissingWindow="true"
+>
+  <subtype
+      android:label="subtype1"
+      android:imeSubtypeLocale="en_US"
+      android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/coretests/res/xml/ime_meta_sw_next.xml
new file mode 100644
index 0000000..2e2ee33
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta_sw_next.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    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.
+-->
+
+<input-method
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+    android:supportsSwitchingToNextInputMethod="true"
+>
+  <subtype
+      android:label="subtype1"
+      android:imeSubtypeLocale="en_US"
+      android:imeSubtypeMode="keyboard" />
+</input-method>
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
new file mode 100644
index 0000000..23dc80f
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 android.view.inputmethod;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.annotation.XmlRes;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodInfoTest {
+
+    @Test
+    public void testEqualsAndHashCode() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.equals(imi), is(true));
+        assertThat(clone.hashCode(), equalTo(imi.hashCode()));
+    }
+
+    @Test
+    public void testBooleanAttributes_DefaultValues() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta);
+
+        assertThat(imi.supportsSwitchingToNextInputMethod(), is(false));
+        assertThat(imi.supportsDismissingWindow(), is(false));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.supportsSwitchingToNextInputMethod(), is(false));
+        assertThat(clone.supportsDismissingWindow(), is(false));
+    }
+
+    @Test
+    public void testSupportsSwitchingToNextInputMethod() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_sw_next);
+
+        assertThat(imi.supportsSwitchingToNextInputMethod(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.supportsSwitchingToNextInputMethod(), is(true));
+    }
+
+    @Test
+    public void testSupportsDismissingWindow() throws Exception {
+        final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_dismiss);
+
+        assertThat(imi.supportsDismissingWindow(), is(true));
+
+        final InputMethodInfo clone = cloneViaParcel(imi);
+
+        assertThat(clone.supportsDismissingWindow(), is(true));
+    }
+
+    private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes)
+            throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+        final ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.applicationInfo = context.getApplicationInfo();
+        serviceInfo.packageName = context.getPackageName();
+        serviceInfo.name = "DummyImeForTest";
+        serviceInfo.metaData = new Bundle();
+        serviceInfo.metaData.putInt(InputMethod.SERVICE_META_DATA, metaDataRes);
+        final ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        return new InputMethodInfo(context, resolveInfo, null /* additionalSubtypesMap */);
+    }
+
+    private InputMethodInfo cloneViaParcel(final InputMethodInfo original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return InputMethodInfo.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 8385b69..aab4698 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -124,7 +124,7 @@
     }
 
     @Test
-    public void updateChooserCountsAfterUserSelection() throws InterruptedException {
+    public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
         Intent sendIntent = createSendImageIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
 
@@ -142,19 +142,15 @@
         sOverrides.onSafelyStartCallback = targetInfo -> {
             return true;
         };
-        String action = sendIntent.getAction();
-        String annotation = sendIntent.getType();
         ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
-        String packageName = toChoose.activityInfo.packageName;
-        long toChooseCount = getCount(usm, packageName, action, annotation);
         onView(withText(toChoose.activityInfo.name))
                 .perform(click());
         waitForIdle();
         verify(sOverrides.resolverListController, times(1))
+                .updateChooserCounts(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString());
+        verify(sOverrides.resolverListController, times(1))
                 .updateModel(toChoose.activityInfo.getComponentName());
         assertThat(activity.getIsSelected(), is(true));
-        long updatedCount = getCount(usm, packageName, action, annotation);
-        assertThat(updatedCount, is(toChooseCount + 1l));
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index ba5206a..34c34d7 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -74,7 +74,8 @@
         }
         final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
                 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
-                DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod);
+                DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod,
+                false /* supportsDismissingWindow */);
         if (subtypes == null) {
             items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
                     NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
index f33362f8..4df199c 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/users/AppRestrictionsHelperTest.java
@@ -159,14 +159,14 @@
         for (String pkg : defaultImes) {
             final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
             final InputMethodInfo inputMethodInfo = new InputMethodInfo(
-                    ri, false, null, null, 0, true, true);
+                    ri, false, null, null, 0, true, true, false);
             inputMethods.add(inputMethodInfo);
             addInstalledApp(ri);
         }
         for (String pkg : otherImes) {
             final ResolveInfo ri = createResolveInfoForSystemApp(pkg);
             final InputMethodInfo inputMethodInfo = new InputMethodInfo(
-                    ri, false, null, null, 0, false, true);
+                    ri, false, null, null, 0, false, true, false);
             inputMethods.add(inputMethodInfo);
             addInstalledApp(ri);
         }
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 05963ac..69515fa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -460,7 +460,7 @@
     <string name="accessibility_desc_settings">Settings</string>
     <!-- Content description for the recent apps panel (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_desc_recent_apps">Overview.</string>
-    <!-- Content description for the graphic shown instead of an activity window while the activity is locked (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <!-- Content description for the work profile lock screen. This prevents work profile apps from being used, but personal apps can be used as normal (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_desc_work_lock">Work lock screen</string>
     <!-- Content description for the close button in the zen mode panel introduction message. [CHAR LIMIT=NONE] -->
     <string name="accessibility_desc_close">Close</string>
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index f271b08..0a088e9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -883,7 +883,7 @@
 
     private void handleMobileDataAlwaysOn() {
         final boolean enable = (Settings.Global.getInt(
-                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1);
+                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 1) == 1);
         final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
         if (enable == isEnabled) {
             return;  // Nothing to do.
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 5654096..74e44d5 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1912,31 +1912,6 @@
     }
 
     @Override
-    public void setDnsServersForNetwork(int netId, String[] servers, String domains) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        Command cmd;
-        if (servers.length > 0) {
-            cmd = new Command("resolver", "setnetdns", netId,
-                    (domains == null ? "" : domains));
-            for (String s : servers) {
-                InetAddress a = NetworkUtils.numericToInetAddress(s);
-                if (a.isAnyLocalAddress() == false) {
-                    cmd.appendArg(a.getHostAddress());
-                }
-            }
-        } else {
-            cmd = new Command("resolver", "clearnetdns", netId);
-        }
-
-        try {
-            mConnector.execute(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
     public void addVpnUidRanges(int netId, UidRange[] ranges) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND];
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 3c8c699..2010e64 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -181,7 +181,7 @@
         }
 
         @Override // Binder call
-        public void rebootRecoveryWithCommand(String command) {
+        public void rebootRecoveryWithCommand(String command, boolean update) {
             if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
             synchronized (sRequestLock) {
                 if (!setupOrClearBcb(true, command)) {
@@ -190,7 +190,10 @@
 
                 // Having set up the BCB, go ahead and reboot.
                 PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-                pm.reboot(PowerManager.REBOOT_RECOVERY);
+                // PowerManagerService may additionally request uncrypting the package when it's
+                // to install an update (REBOOT_RECOVERY_UPDATE).
+                pm.reboot(update ? PowerManager.REBOOT_RECOVERY_UPDATE :
+                        PowerManager.REBOOT_RECOVERY);
             }
         }
 
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 57381f2..0c80166 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -32,7 +32,6 @@
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.LinkProperties;
@@ -73,6 +72,7 @@
 import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
 import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
 import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
+import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
 import com.android.server.net.BaseNetworkObserver;
 
 import java.io.FileDescriptor;
@@ -1031,249 +1031,6 @@
         }
     }
 
-    /**
-     * A class to centralize all the network and link properties information
-     * pertaining to the current and any potential upstream network.
-     *
-     * Calling #start() registers two callbacks: one to track the system default
-     * network and a second to specifically observe TYPE_MOBILE_DUN networks.
-     *
-     * The methods and data members of this class are only to be accessed and
-     * modified from the tethering master state machine thread. Any other
-     * access semantics would necessitate the addition of locking.
-     *
-     * TODO: Investigate whether more "upstream-specific" logic/functionality
-     * could/should be moved here.
-     */
-    public class UpstreamNetworkMonitor {
-        public static final int EVENT_ON_AVAILABLE      = 1;
-        public static final int EVENT_ON_CAPABILITIES   = 2;
-        public static final int EVENT_ON_LINKPROPERTIES = 3;
-        public static final int EVENT_ON_LOST           = 4;
-
-        private final Context mContext;
-        private final StateMachine mTarget;
-        private final int mWhat;
-        private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
-        private ConnectivityManager mCM;
-        private NetworkCallback mDefaultNetworkCallback;
-        private NetworkCallback mDunTetheringCallback;
-        private NetworkCallback mMobileNetworkCallback;
-        private boolean mDunRequired;
-
-        public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
-            mContext = ctx;
-            mTarget = tgt;
-            mWhat = what;
-        }
-
-        public void start() {
-            stop();
-
-            mDefaultNetworkCallback = new UpstreamNetworkCallback();
-            cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
-
-            final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
-                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                    .build();
-            mDunTetheringCallback = new UpstreamNetworkCallback();
-            cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
-        }
-
-        public void stop() {
-            releaseMobileNetworkRequest();
-
-            releaseCallback(mDefaultNetworkCallback);
-            mDefaultNetworkCallback = null;
-
-            releaseCallback(mDunTetheringCallback);
-            mDunTetheringCallback = null;
-
-            mNetworkMap.clear();
-        }
-
-        public void mobileUpstreamRequiresDun(boolean dunRequired) {
-            final boolean valueChanged = (mDunRequired != dunRequired);
-            mDunRequired = dunRequired;
-            if (valueChanged && mobileNetworkRequested()) {
-                releaseMobileNetworkRequest();
-                registerMobileNetworkRequest();
-            }
-        }
-
-        public boolean mobileNetworkRequested() {
-            return (mMobileNetworkCallback != null);
-        }
-
-        public void registerMobileNetworkRequest() {
-            if (mMobileNetworkCallback != null) return;
-
-            final NetworkRequest.Builder builder = new NetworkRequest.Builder()
-                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-            if (mDunRequired) {
-                builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                       .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-            } else {
-                builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-            }
-            final NetworkRequest mobileUpstreamRequest = builder.build();
-
-            // The existing default network and DUN callbacks will be notified.
-            // Therefore, to avoid duplicate notifications, we only register a no-op.
-            mMobileNetworkCallback = new NetworkCallback();
-
-            // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
-            // moderate callback time (once timeout callbacks are implemented). This might
-            // be useful for updating some UI. Additionally, we should definitely log a
-            // message to aid in any subsequent debugging
-            if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
-
-            cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
-        }
-
-        public void releaseMobileNetworkRequest() {
-            if (mMobileNetworkCallback == null) return;
-
-            cm().unregisterNetworkCallback(mMobileNetworkCallback);
-            mMobileNetworkCallback = null;
-        }
-
-        public NetworkState lookup(Network network) {
-            return (network != null) ? mNetworkMap.get(network) : null;
-        }
-
-        private void handleAvailable(Network network) {
-            if (VDBG) {
-                Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
-            }
-            if (!mNetworkMap.containsKey(network)) {
-                mNetworkMap.put(network,
-                        new NetworkState(null, null, null, network, null, null));
-            }
-
-            final ConnectivityManager cm = cm();
-
-            if (mDefaultNetworkCallback != null) {
-                cm.requestNetworkCapabilities(mDefaultNetworkCallback);
-                cm.requestLinkProperties(mDefaultNetworkCallback);
-            }
-
-            // Requesting updates for mDunTetheringCallback is not
-            // necessary. Because it's a listen, it will already have
-            // heard all NetworkCapabilities and LinkProperties updates
-            // since UpstreamNetworkMonitor was started. Because we
-            // start UpstreamNetworkMonitor before chooseUpstreamType()
-            // is ever invoked (it can register a DUN request) this is
-            // mostly safe. However, if a DUN network is already up for
-            // some reason (unlikely, because DUN is restricted and,
-            // unless the DUN network is shared with another APN, only
-            // the system can request it and this is the only part of
-            // the system that requests it) we won't know its
-            // LinkProperties or NetworkCapabilities.
-
-            notifyTarget(EVENT_ON_AVAILABLE, network);
-        }
-
-        private void handleNetCap(Network network, NetworkCapabilities newNc) {
-            if (!mNetworkMap.containsKey(network)) {
-                // Ignore updates for networks for which we have not yet
-                // received onAvailable() - which should never happen -
-                // or for which we have already received onLost().
-                return;
-            }
-            if (VDBG) {
-                Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
-                        network, newNc));
-            }
-
-            final NetworkState prev = mNetworkMap.get(network);
-            mNetworkMap.put(network,
-                    new NetworkState(null, prev.linkProperties, newNc,
-                                     network, null, null));
-            notifyTarget(EVENT_ON_CAPABILITIES, network);
-        }
-
-        private void handleLinkProp(Network network, LinkProperties newLp) {
-            if (!mNetworkMap.containsKey(network)) {
-                // Ignore updates for networks for which we have not yet
-                // received onAvailable() - which should never happen -
-                // or for which we have already received onLost().
-                return;
-            }
-            if (VDBG) {
-                Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
-                        network, newLp));
-            }
-
-            final NetworkState prev = mNetworkMap.get(network);
-            mNetworkMap.put(network,
-                    new NetworkState(null, newLp, prev.networkCapabilities,
-                                     network, null, null));
-            notifyTarget(EVENT_ON_LINKPROPERTIES, network);
-        }
-
-        private void handleLost(Network network) {
-            if (!mNetworkMap.containsKey(network)) {
-                // Ignore updates for networks for which we have not yet
-                // received onAvailable() - which should never happen -
-                // or for which we have already received onLost().
-                return;
-            }
-            if (VDBG) {
-                Log.d(TAG, "EVENT_ON_LOST for " + network);
-            }
-            notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
-        }
-
-        // Fetch (and cache) a ConnectivityManager only if and when we need one.
-        private ConnectivityManager cm() {
-            if (mCM == null) {
-                mCM = mContext.getSystemService(ConnectivityManager.class);
-            }
-            return mCM;
-        }
-
-        /**
-         * A NetworkCallback class that relays information of interest to the
-         * tethering master state machine thread for subsequent processing.
-         */
-        private class UpstreamNetworkCallback extends NetworkCallback {
-            @Override
-            public void onAvailable(Network network) {
-                mTarget.getHandler().post(() -> handleAvailable(network));
-            }
-
-            @Override
-            public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
-                mTarget.getHandler().post(() -> handleNetCap(network, newNc));
-            }
-
-            @Override
-            public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
-                mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
-            }
-
-            @Override
-            public void onLost(Network network) {
-                mTarget.getHandler().post(() -> handleLost(network));
-            }
-        }
-
-        private void releaseCallback(NetworkCallback cb) {
-            if (cb != null) cm().unregisterNetworkCallback(cb);
-        }
-
-        private void notifyTarget(int which, Network network) {
-            notifyTarget(which, mNetworkMap.get(network));
-        }
-
-        private void notifyTarget(int which, NetworkState netstate) {
-            mTarget.sendMessage(mWhat, which, 0, netstate);
-        }
-    }
-
     // Needed because the canonical source of upstream truth is just the
     // upstream interface name, |mCurrentUpstreamIface|.  This is ripe for
     // future simplification, once the upstream Network is canonical.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index afc6247..a5876dd 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1586,9 +1586,6 @@
         public void exit() {
             // We assume that everything is reset after stopping the daemons.
             interrupt();
-            for (LocalSocket socket : mSockets) {
-                IoUtils.closeQuietly(socket);
-            }
             agentDisconnect();
             try {
                 mContext.unregisterReceiver(mBroadcastReceiver);
@@ -1601,8 +1598,26 @@
             Log.v(TAG, "Waiting");
             synchronized (TAG) {
                 Log.v(TAG, "Executing");
-                execute();
-                monitorDaemons();
+                try {
+                    execute();
+                    monitorDaemons();
+                    interrupted(); // Clear interrupt flag if execute called exit.
+                } catch (InterruptedException e) {
+                } finally {
+                    for (LocalSocket socket : mSockets) {
+                        IoUtils.closeQuietly(socket);
+                    }
+                    // This sleep is necessary for racoon to successfully complete sending delete
+                    // message to server.
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException e) {
+                    }
+                    for (String daemon : mDaemons) {
+                        SystemService.stop(daemon);
+                    }
+                }
+                agentDisconnect();
             }
         }
 
@@ -1801,18 +1816,6 @@
                 Log.i(TAG, "Aborting", e);
                 updateState(DetailedState.FAILED, e.getMessage());
                 exit();
-            } finally {
-                // Kill the daemons if they fail to stop.
-                if (!initFinished) {
-                    for (String daemon : mDaemons) {
-                        SystemService.stop(daemon);
-                    }
-                }
-
-                // Do not leave an unstable state.
-                if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
-                    agentDisconnect();
-                }
             }
         }
 
@@ -1820,28 +1823,17 @@
          * Monitor the daemons we started, moving to disconnected state if the
          * underlying services fail.
          */
-        private void monitorDaemons() {
+        private void monitorDaemons() throws InterruptedException{
             if (!mNetworkInfo.isConnected()) {
                 return;
             }
-
-            try {
-                while (true) {
-                    Thread.sleep(2000);
-                    for (int i = 0; i < mDaemons.length; i++) {
-                        if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
-                            return;
-                        }
+            while (true) {
+                Thread.sleep(2000);
+                for (int i = 0; i < mDaemons.length; i++) {
+                    if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
+                        return;
                     }
                 }
-            } catch (InterruptedException e) {
-                Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
-            } finally {
-                for (String daemon : mDaemons) {
-                    SystemService.stop(daemon);
-                }
-
-                agentDisconnect();
             }
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
new file mode 100644
index 0000000..927dfd5
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -0,0 +1,287 @@
+/*
+ * 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.connectivity.tethering;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.NetworkState;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.StateMachine;
+
+import java.util.HashMap;
+
+
+/**
+ * A class to centralize all the network and link properties information
+ * pertaining to the current and any potential upstream network.
+ *
+ * Calling #start() registers two callbacks: one to track the system default
+ * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+ *
+ * The methods and data members of this class are only to be accessed and
+ * modified from the tethering master state machine thread. Any other
+ * access semantics would necessitate the addition of locking.
+ *
+ * TODO: Move upstream selection logic here.
+ *
+ * @hide
+ */
+public class UpstreamNetworkMonitor {
+    private static final String TAG = UpstreamNetworkMonitor.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    public static final int EVENT_ON_AVAILABLE      = 1;
+    public static final int EVENT_ON_CAPABILITIES   = 2;
+    public static final int EVENT_ON_LINKPROPERTIES = 3;
+    public static final int EVENT_ON_LOST           = 4;
+
+    private final Context mContext;
+    private final StateMachine mTarget;
+    private final int mWhat;
+    private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
+    private ConnectivityManager mCM;
+    private NetworkCallback mDefaultNetworkCallback;
+    private NetworkCallback mDunTetheringCallback;
+    private NetworkCallback mMobileNetworkCallback;
+    private boolean mDunRequired;
+
+    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
+        mContext = ctx;
+        mTarget = tgt;
+        mWhat = what;
+    }
+
+    @VisibleForTesting
+    public UpstreamNetworkMonitor(StateMachine tgt, int what, ConnectivityManager cm) {
+        this(null, tgt, what);
+        mCM = cm;
+    }
+
+    public void start() {
+        stop();
+
+        mDefaultNetworkCallback = new UpstreamNetworkCallback();
+        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+
+        final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                .build();
+        mDunTetheringCallback = new UpstreamNetworkCallback();
+        cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
+    }
+
+    public void stop() {
+        releaseMobileNetworkRequest();
+
+        releaseCallback(mDefaultNetworkCallback);
+        mDefaultNetworkCallback = null;
+
+        releaseCallback(mDunTetheringCallback);
+        mDunTetheringCallback = null;
+
+        mNetworkMap.clear();
+    }
+
+    public void mobileUpstreamRequiresDun(boolean dunRequired) {
+        final boolean valueChanged = (mDunRequired != dunRequired);
+        mDunRequired = dunRequired;
+        if (valueChanged && mobileNetworkRequested()) {
+            releaseMobileNetworkRequest();
+            registerMobileNetworkRequest();
+        }
+    }
+
+    public boolean mobileNetworkRequested() {
+        return (mMobileNetworkCallback != null);
+    }
+
+    public void registerMobileNetworkRequest() {
+        if (mMobileNetworkCallback != null) return;
+
+        final NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        if (mDunRequired) {
+            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                   .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+        } else {
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+        final NetworkRequest mobileUpstreamRequest = builder.build();
+
+        // The existing default network and DUN callbacks will be notified.
+        // Therefore, to avoid duplicate notifications, we only register a no-op.
+        mMobileNetworkCallback = new NetworkCallback();
+
+        // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
+        // moderate callback time (once timeout callbacks are implemented). This might
+        // be useful for updating some UI. Additionally, we should definitely log a
+        // message to aid in any subsequent debugging
+        if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
+
+        cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
+    }
+
+    public void releaseMobileNetworkRequest() {
+        if (mMobileNetworkCallback == null) return;
+
+        cm().unregisterNetworkCallback(mMobileNetworkCallback);
+        mMobileNetworkCallback = null;
+    }
+
+    public NetworkState lookup(Network network) {
+        return (network != null) ? mNetworkMap.get(network) : null;
+    }
+
+    private void handleAvailable(Network network) {
+        if (VDBG) {
+            Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
+        }
+        if (!mNetworkMap.containsKey(network)) {
+            mNetworkMap.put(network,
+                    new NetworkState(null, null, null, network, null, null));
+        }
+
+        final ConnectivityManager cm = cm();
+
+        if (mDefaultNetworkCallback != null) {
+            cm.requestNetworkCapabilities(mDefaultNetworkCallback);
+            cm.requestLinkProperties(mDefaultNetworkCallback);
+        }
+
+        // Requesting updates for mDunTetheringCallback is not
+        // necessary. Because it's a listen, it will already have
+        // heard all NetworkCapabilities and LinkProperties updates
+        // since UpstreamNetworkMonitor was started. Because we
+        // start UpstreamNetworkMonitor before chooseUpstreamType()
+        // is ever invoked (it can register a DUN request) this is
+        // mostly safe. However, if a DUN network is already up for
+        // some reason (unlikely, because DUN is restricted and,
+        // unless the DUN network is shared with another APN, only
+        // the system can request it and this is the only part of
+        // the system that requests it) we won't know its
+        // LinkProperties or NetworkCapabilities.
+
+        notifyTarget(EVENT_ON_AVAILABLE, network);
+    }
+
+    private void handleNetCap(Network network, NetworkCapabilities newNc) {
+        if (!mNetworkMap.containsKey(network)) {
+            // Ignore updates for networks for which we have not yet
+            // received onAvailable() - which should never happen -
+            // or for which we have already received onLost().
+            return;
+        }
+        if (VDBG) {
+            Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
+                    network, newNc));
+        }
+
+        final NetworkState prev = mNetworkMap.get(network);
+        mNetworkMap.put(network,
+                new NetworkState(null, prev.linkProperties, newNc,
+                                 network, null, null));
+        notifyTarget(EVENT_ON_CAPABILITIES, network);
+    }
+
+    private void handleLinkProp(Network network, LinkProperties newLp) {
+        if (!mNetworkMap.containsKey(network)) {
+            // Ignore updates for networks for which we have not yet
+            // received onAvailable() - which should never happen -
+            // or for which we have already received onLost().
+            return;
+        }
+        if (VDBG) {
+            Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
+                    network, newLp));
+        }
+
+        final NetworkState prev = mNetworkMap.get(network);
+        mNetworkMap.put(network,
+                new NetworkState(null, newLp, prev.networkCapabilities,
+                                 network, null, null));
+        notifyTarget(EVENT_ON_LINKPROPERTIES, network);
+    }
+
+    private void handleLost(Network network) {
+        if (!mNetworkMap.containsKey(network)) {
+            // Ignore updates for networks for which we have not yet
+            // received onAvailable() - which should never happen -
+            // or for which we have already received onLost().
+            return;
+        }
+        if (VDBG) {
+            Log.d(TAG, "EVENT_ON_LOST for " + network);
+        }
+        notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
+    }
+
+    // Fetch (and cache) a ConnectivityManager only if and when we need one.
+    private ConnectivityManager cm() {
+        if (mCM == null) {
+            mCM = mContext.getSystemService(ConnectivityManager.class);
+        }
+        return mCM;
+    }
+
+    /**
+     * A NetworkCallback class that relays information of interest to the
+     * tethering master state machine thread for subsequent processing.
+     */
+    private class UpstreamNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(Network network) {
+            mTarget.getHandler().post(() -> handleAvailable(network));
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
+            mTarget.getHandler().post(() -> handleNetCap(network, newNc));
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
+            mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
+        }
+
+        @Override
+        public void onLost(Network network) {
+            mTarget.getHandler().post(() -> handleLost(network));
+        }
+    }
+
+    private void releaseCallback(NetworkCallback cb) {
+        if (cb != null) cm().unregisterNetworkCallback(cb);
+    }
+
+    private void notifyTarget(int which, Network network) {
+        notifyTarget(which, mNetworkMap.get(network));
+    }
+
+    private void notifyTarget(int which, NetworkState netstate) {
+        mTarget.sendMessage(mWhat, which, 0, netstate);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 7c5550a..d81e092 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -796,7 +796,7 @@
     void onNewAvrAdded(HdmiDeviceInfo avr) {
         assertRunOnServiceThread();
         addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress()));
-        if (isArcFeatureEnabled(avr.getPortId())
+        if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId())
                 && !hasAction(SetArcTransmissionStateAction.class)) {
             startArcAction(true);
         }
@@ -900,29 +900,6 @@
     }
 
     @ServiceThreadOnly
-    private void updateArcFeatureStatus(int portId, boolean isConnected) {
-        assertRunOnServiceThread();
-        HdmiPortInfo portInfo = mService.getPortInfo(portId);
-        if (!portInfo.isArcSupported()) {
-            return;
-        }
-        HdmiDeviceInfo avr = getAvrDeviceInfo();
-        if (avr == null) {
-            if (isConnected) {
-                // Update the status (since TV may not have seen AVR yet) so
-                // that ARC can be initiated after discovery.
-                mArcFeatureEnabled.put(portId, isConnected);
-            }
-            return;
-        }
-        // HEAC 2.4, HEACT 5-15
-        // Should not activate ARC if +5V status is false.
-        if (avr.getPortId() == portId) {
-            changeArcFeatureEnabled(portId, isConnected);
-        }
-    }
-
-    @ServiceThreadOnly
     boolean isConnected(int portId) {
         assertRunOnServiceThread();
         return mService.isConnected(portId);
@@ -952,18 +929,18 @@
     @ServiceThreadOnly
     void changeArcFeatureEnabled(int portId, boolean enabled) {
         assertRunOnServiceThread();
-
-        if (mArcFeatureEnabled.get(portId) != enabled) {
-            mArcFeatureEnabled.put(portId, enabled);
-            if (enabled) {
-                if (!mArcEstablished) {
-                    startArcAction(true);
-                }
-            } else {
-                if (mArcEstablished) {
-                    startArcAction(false);
-                }
-            }
+        if (mArcFeatureEnabled.get(portId) == enabled) {
+            return;
+        }
+        mArcFeatureEnabled.put(portId, enabled);
+        HdmiDeviceInfo avr = getAvrDeviceInfo();
+        if (avr == null || avr.getPortId() != portId) {
+            return;
+        }
+        if (enabled && !mArcEstablished) {
+            startArcAction(true);
+        } else if (!enabled && mArcEstablished) {
+            startArcAction(false);
         }
     }
 
@@ -1097,14 +1074,14 @@
         return true;
     }
 
-    private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) {
+    private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) {
         HdmiDeviceInfo avr = getAvrDeviceInfo();
         if (avr != null
                 && (avrAddress == avr.getLogicalAddress())
                 && isConnectedToArcPort(avr.getPhysicalAddress())
                 && isDirectConnectAddress(avr.getPhysicalAddress())) {
-            if (shouldCheckArcFeatureEnabled) {
-                return isArcFeatureEnabled(avr.getPortId());
+            if (enabled) {
+                return isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId());
             } else {
                 return true;
             }
@@ -1566,7 +1543,6 @@
             // It covers seq #40, #43.
             hotplugActions.get(0).pollAllDevicesNow();
         }
-        updateArcFeatureStatus(portId, connected);
     }
 
     private void removeCecSwitches(int portId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c9c855b..7362a51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10639,12 +10639,30 @@
                             int flags = permissionState != null
                                     ? permissionState.getFlags() : 0;
                             if (origPermissions.hasRuntimePermission(bp.name, userId)) {
-                                if (permissionsState.grantRuntimePermission(bp, userId) ==
-                                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // If we cannot put the permission as it was, we have to write.
+                                // Don't propagate the permission in a permission review mode if
+                                // the former was revoked, i.e. marked to not propagate on upgrade.
+                                // Note that in a permission review mode install permissions are
+                                // represented as constantly granted runtime ones since we need to
+                                // keep a per user state associated with the permission. Also the
+                                // revoke on upgrade flag is no longer applicable and is reset.
+                                final boolean revokeOnUpgrade = (flags & PackageManager
+                                        .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+                                if (revokeOnUpgrade) {
+                                    flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                    // Since we changed the flags, we have to write.
                                     changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                             changedRuntimePermissionUserIds, userId);
                                 }
+                                if (!mPermissionReviewRequired || !revokeOnUpgrade) {
+                                    if (permissionsState.grantRuntimePermission(bp, userId) ==
+                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // If we cannot put the permission as it was,
+                                        // we have to write.
+                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
+                                                changedRuntimePermissionUserIds, userId);
+                                    }
+                                }
+
                                 // If the app supports runtime permissions no need for a review.
                                 if (mPermissionReviewRequired
                                         && appSupportsRuntimePermissions
@@ -13018,8 +13036,10 @@
                         + " is not installer for " + packageName);
             }
 
-            ps.categoryHint = categoryHint;
-            scheduleWriteSettingsLocked();
+            if (ps.categoryHint != categoryHint) {
+                ps.categoryHint = categoryHint;
+                scheduleWriteSettingsLocked();
+            }
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bdd1a0f51..040188d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,7 +17,6 @@
 package com.android.server.devicepolicy;
 
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
-import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG;
 import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
 import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
 import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
@@ -49,7 +48,6 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -180,8 +178,6 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -337,7 +333,8 @@
     private static final long MINIMUM_STRONG_AUTH_TIMEOUT_MS = 1 * 60 * 60 * 1000; // 1h
 
     /**
-     * Strings logged with {@link #PROVISIONING_ENTRY_POINT_ADB}.
+     * Strings logged with {@link
+     * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}.
      */
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -552,11 +549,25 @@
             }
             if (Intent.ACTION_USER_ADDED.equals(action)) {
                 sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
-                disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
+                synchronized (DevicePolicyManagerService.this) {
+                    // It might take a while for the user to become affiliated. Make security
+                    // and network logging unavailable in the meantime.
+                    maybePauseDeviceWideLoggingLocked();
+                }
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_REMOVED, userHandle);
-                disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
-                removeUserData(userHandle);
+                synchronized (DevicePolicyManagerService.this) {
+                    // Check whether the user is affiliated, *before* removing its data.
+                    boolean isRemovedUserAffiliated = isUserAffiliatedWithDeviceLocked(userHandle);
+                    removeUserData(userHandle);
+                    if (!isRemovedUserAffiliated) {
+                        // We discard the logs when unaffiliated users are deleted (so that the
+                        // device owner cannot retrieve data about that user after it's gone).
+                        discardDeviceWideLogsLocked();
+                        // Resume logging if all remaining users are affiliated.
+                        maybeResumeDeviceWideLoggingLocked();
+                    }
+                }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 synchronized (DevicePolicyManagerService.this) {
                     // Reset the policy data
@@ -1858,9 +1869,10 @@
             if (mOwners.hasDeviceOwner()) {
                 mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true");
                 Slog.i(LOG_TAG, "Set ro.device_owner property to true");
-                disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
+
                 if (mInjector.securityLogGetLoggingEnabledProperty()) {
                     mSecurityLogMonitor.start();
+                    maybePauseDeviceWideLoggingLocked();
                 }
             } else {
                 mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "false");
@@ -3155,7 +3167,7 @@
     }
 
     // It's temporary solution to clear DISALLOW_ADD_USER after CTS
-    // TODO: b/31952368 when the restriction is moved from system to the device owner,
+    // STOPSHIP(b/31952368) when the restriction is moved from system to the device owner,
     // it can be removed.
     private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) {
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
@@ -5650,34 +5662,12 @@
         }
     }
 
-    private boolean isDeviceOwnerManagedSingleUserDevice() {
-        synchronized (this) {
-            if (!mOwners.hasDeviceOwner()) {
-                return false;
-            }
-        }
-        final long callingIdentity = mInjector.binderClearCallingIdentity();
-        try {
-            if (mInjector.userManagerIsSplitSystemUser()) {
-                // In split system user mode, only allow the case where the device owner is managing
-                // the only non-system user of the device
-                return (mUserManager.getUserCount() == 2
-                        && mOwners.getDeviceOwnerUserId() != UserHandle.USER_SYSTEM);
-            } else  {
-                return mUserManager.getUserCount() == 1;
-            }
-        } finally {
-            mInjector.binderRestoreCallingIdentity(callingIdentity);
-        }
-    }
-
-    private void ensureDeviceOwnerManagingSingleUser(ComponentName who) throws SecurityException {
+    private void ensureDeviceOwnerAndAllUsersAffiliated(ComponentName who) throws SecurityException {
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        }
-        if (!isDeviceOwnerManagedSingleUserDevice()) {
-            throw new SecurityException(
-                    "There should only be one user, managed by Device Owner");
+            if (!areAllUsersAffiliatedWithDeviceLocked()) {
+                throw new SecurityException("Not all users are affiliated.");
+            }
         }
     }
 
@@ -5687,7 +5677,11 @@
             return false;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
-        ensureDeviceOwnerManagingSingleUser(who);
+
+        // TODO: If an unaffiliated user is removed, the admin will be able to request a bugreport
+        // which could still contain data related to that user. Should we disallow that, e.g. until
+        // next boot? Might not be needed given that this still requires user consent.
+        ensureDeviceOwnerAndAllUsersAffiliated(who);
 
         if (mRemoteBugreportServiceIsActive.get()
                 || (getDeviceOwnerRemoteBugreportUri() != null)) {
@@ -5712,7 +5706,8 @@
             mRemoteBugreportServiceIsActive.set(true);
             mRemoteBugreportSharingAccepted.set(false);
             registerRemoteBugreportReceivers();
-            mInjector.getNotificationManager().notifyAsUser(LOG_TAG, RemoteBugreportUtils.NOTIFICATION_ID,
+            mInjector.getNotificationManager().notifyAsUser(LOG_TAG,
+                    RemoteBugreportUtils.NOTIFICATION_ID,
                     RemoteBugreportUtils.buildNotification(mContext,
                             DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
             mHandler.postDelayed(mRemoteBugreportTimeoutRunnable,
@@ -6259,6 +6254,7 @@
             admin.userRestrictions = null;
             admin.defaultEnabledRestrictionsAlreadySet.clear();
             admin.forceEphemeralUsers = false;
+            admin.isNetworkLoggingEnabled = false;
             mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
             final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
             policyData.mLastSecurityLogRetrievalTime = -1;
@@ -6271,7 +6267,11 @@
         mOwners.clearDeviceOwner();
         mOwners.writeDeviceOwner();
         updateDeviceOwnerLocked();
-        disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
+
+        mInjector.securityLogSetLoggingEnabledProperty(false);
+        mSecurityLogMonitor.stop();
+        setNetworkLoggingActiveInternal(false);
+
         try {
             if (mInjector.getIBackupManager() != null) {
                 // Reactivate backup service.
@@ -8261,7 +8261,7 @@
         synchronized (this) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             final int userHandle = mInjector.userHandleGetCallingUserId();
-            if (isUserAffiliatedWithDevice(userHandle)) {
+            if (isUserAffiliatedWithDeviceLocked(userHandle)) {
                 setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
             } else {
                 throw new SecurityException("Admin " + who +
@@ -9442,6 +9442,12 @@
                 getUserData(UserHandle.USER_SYSTEM).mAffiliationIds = affiliationIds;
                 saveSettingsLocked(UserHandle.USER_SYSTEM);
             }
+
+            // Affiliation status for any user, not just the calling user, might have changed.
+            // The device owner user will still be affiliated after changing its affiliation ids,
+            // but as a result of that other users might become affiliated or un-affiliated.
+            maybePauseDeviceWideLoggingLocked();
+            maybeResumeDeviceWideLoggingLocked();
         }
     }
 
@@ -9461,84 +9467,78 @@
 
     @Override
     public boolean isAffiliatedUser() {
-        return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId());
+        if (!mHasFeature) {
+            return false;
+        }
+
+        synchronized (this) {
+            return isUserAffiliatedWithDeviceLocked(mInjector.userHandleGetCallingUserId());
+        }
     }
 
-    private boolean isUserAffiliatedWithDevice(int userId) {
-        synchronized (this) {
-            if (!mOwners.hasDeviceOwner()) {
-                return false;
-            }
-            if (userId == mOwners.getDeviceOwnerUserId()) {
-                // The user that the DO is installed on is always affiliated with the device.
+    private boolean isUserAffiliatedWithDeviceLocked(int userId) {
+        if (!mOwners.hasDeviceOwner()) {
+            return false;
+        }
+        if (userId == mOwners.getDeviceOwnerUserId()) {
+            // The user that the DO is installed on is always affiliated with the device.
+            return true;
+        }
+        if (userId == UserHandle.USER_SYSTEM) {
+            // The system user is always affiliated in a DO device, even if the DO is set on a
+            // different user. This could be the case if the DO is set in the primary user
+            // of a split user device.
+            return true;
+        }
+        final ComponentName profileOwner = getProfileOwner(userId);
+        if (profileOwner == null) {
+            return false;
+        }
+        final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
+        final Set<String> deviceAffiliationIds =
+                getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
+        for (String id : userAffiliationIds) {
+            if (deviceAffiliationIds.contains(id)) {
                 return true;
             }
-            if (userId == UserHandle.USER_SYSTEM) {
-                // The system user is always affiliated in a DO device, even if the DO is set on a
-                // different user. This could be the case if the DO is set in the primary user
-                // of a split user device.
-                return true;
-            }
-            final ComponentName profileOwner = getProfileOwner(userId);
-            if (profileOwner == null) {
-                return false;
-            }
-            final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
-            final Set<String> deviceAffiliationIds =
-                    getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
-            for (String id : userAffiliationIds) {
-                if (deviceAffiliationIds.contains(id)) {
-                    return true;
-                }
-            }
         }
         return false;
     }
 
-    private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() {
-        final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice();
-
-        // disable security logging if needed
-        if (!isSingleUserManagedDevice) {
-            mInjector.securityLogSetLoggingEnabledProperty(false);
-            Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed"
-                    + " device.");
-        }
-
-        // disable backup service if needed
-        // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by
-        // the device owner
-        if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) {
-            setBackupServiceEnabledInternal(false);
-            Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
-        }
-
-        // disable network logging if needed
-        if (!isSingleUserManagedDevice) {
-            setNetworkLoggingActiveInternal(false);
-            Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed"
-                    + " device.");
-            // if there still is a device owner, disable logging policy, otherwise the admin
-            // has been nuked
-            if (mOwners.hasDeviceOwner()) {
-                getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false;
-                saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+    private boolean areAllUsersAffiliatedWithDeviceLocked() {
+        final long ident = mInjector.binderClearCallingIdentity();
+        try {
+            final List<UserInfo> userInfos = mUserManager.getUsers();
+            for (int i = 0; i < userInfos.size(); i++) {
+                int userId = userInfos.get(i).id;
+                if (!isUserAffiliatedWithDeviceLocked(userId)) {
+                    Slog.d(LOG_TAG, "User id " + userId + " not affiliated.");
+                    return false;
+                }
             }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
         }
+
+        return true;
     }
 
     @Override
     public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) {
+        if (!mHasFeature) {
+            return;
+        }
         Preconditions.checkNotNull(admin);
-        ensureDeviceOwnerManagingSingleUser(admin);
 
         synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
             if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
                 return;
             }
             mInjector.securityLogSetLoggingEnabledProperty(enabled);
             if (enabled) {
                 mSecurityLogMonitor.start();
+                maybePauseDeviceWideLoggingLocked();
             } else {
                 mSecurityLogMonitor.stop();
             }
@@ -9547,6 +9547,10 @@
 
     @Override
     public boolean isSecurityLoggingEnabled(ComponentName admin) {
+        if (!mHasFeature) {
+            return false;
+        }
+
         Preconditions.checkNotNull(admin);
         synchronized (this) {
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
@@ -9565,10 +9569,15 @@
 
     @Override
     public ParceledListSlice<SecurityEvent> retrievePreRebootSecurityLogs(ComponentName admin) {
-        Preconditions.checkNotNull(admin);
-        ensureDeviceOwnerManagingSingleUser(admin);
+        if (!mHasFeature) {
+            return null;
+        }
 
-        if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs)) {
+        Preconditions.checkNotNull(admin);
+        ensureDeviceOwnerAndAllUsersAffiliated(admin);
+
+        if (!mContext.getResources().getBoolean(R.bool.config_supportPreRebootSecurityLogs)
+                || !mInjector.securityLogGetLoggingEnabledProperty()) {
             return null;
         }
 
@@ -9586,8 +9595,16 @@
 
     @Override
     public ParceledListSlice<SecurityEvent> retrieveSecurityLogs(ComponentName admin) {
+        if (!mHasFeature) {
+            return null;
+        }
+
         Preconditions.checkNotNull(admin);
-        ensureDeviceOwnerManagingSingleUser(admin);
+        ensureDeviceOwnerAndAllUsersAffiliated(admin);
+
+        if (!mInjector.securityLogGetLoggingEnabledProperty()) {
+            return null;
+        }
 
         recordSecurityLogRetrievalTime();
 
@@ -9794,18 +9811,21 @@
         }
     }
 
+    // TODO(b/22388012): When backup is available for secondary users and profiles, consider
+    // whether there are any privacy/security implications of enabling the backup service here
+    // if there are other users or profiles unmanaged or managed by a different entity (i.e. not
+    // affiliated).
     @Override
     public void setBackupServiceEnabled(ComponentName admin, boolean enabled) {
-        Preconditions.checkNotNull(admin);
         if (!mHasFeature) {
             return;
         }
-        ensureDeviceOwnerManagingSingleUser(admin);
-        setBackupServiceEnabledInternal(enabled);
-    }
+        Preconditions.checkNotNull(admin);
+        synchronized (this) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        }
 
-    private synchronized void setBackupServiceEnabledInternal(boolean enabled) {
-        long ident = mInjector.binderClearCallingIdentity();
+        final long ident = mInjector.binderClearCallingIdentity();
         try {
             IBackupManager ibm = mInjector.getIBackupManager();
             if (ibm != null) {
@@ -9906,7 +9926,7 @@
             final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner);
             final boolean isCallerManagedProfile = isManagedProfile(callingUserId);
             if ((!isCallerDeviceOwner && !isCallerManagedProfile)
-                    || !isUserAffiliatedWithDevice(callingUserId)) {
+                    || !isUserAffiliatedWithDeviceLocked(callingUserId)) {
                 return targetUsers;
             }
 
@@ -9926,7 +9946,7 @@
 
                         // Both must be the same package and be affiliated in order to bind.
                         if (callingOwnerPackage.equals(targetOwnerPackage)
-                               && isUserAffiliatedWithDevice(userId)) {
+                               && isUserAffiliatedWithDeviceLocked(userId)) {
                             targetUsers.add(UserHandle.of(userId));
                         }
                     }
@@ -10024,7 +10044,7 @@
             return;
         }
         Preconditions.checkNotNull(admin);
-        ensureDeviceOwnerManagingSingleUser(admin);
+        getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
 
         if (enabled == isNetworkLoggingEnabledInternalLocked()) {
             // already in the requested state
@@ -10051,10 +10071,10 @@
                     Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
                             + " service not being available yet.");
                 }
+                maybePauseDeviceWideLoggingLocked();
                 sendNetworkLoggingNotificationLocked();
             } else {
                 if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) {
-                    mNetworkLogger = null;
                     Slog.wtf(LOG_TAG, "Network logging could not be stopped due to the logging"
                             + " service not being available yet.");
                 }
@@ -10066,6 +10086,44 @@
         }
     }
 
+    /** Pauses security and network logging if there are unaffiliated users on the device */
+    private void maybePauseDeviceWideLoggingLocked() {
+        if (!areAllUsersAffiliatedWithDeviceLocked()) {
+            Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be "
+                    + "paused if enabled.");
+            mSecurityLogMonitor.pause();
+            if (mNetworkLogger != null) {
+                mNetworkLogger.pause();
+            }
+        }
+    }
+
+    /** Resumes security and network logging (if they are enabled) if all users are affiliated */
+    private void maybeResumeDeviceWideLoggingLocked() {
+        if (areAllUsersAffiliatedWithDeviceLocked()) {
+            final long ident = mInjector.binderClearCallingIdentity();
+            try {
+                mSecurityLogMonitor.resume();
+                if (mNetworkLogger != null) {
+                    mNetworkLogger.resume();
+                }
+            } finally {
+                mInjector.binderRestoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    /** Deletes any security and network logs that might have been collected so far */
+    private void discardDeviceWideLogsLocked() {
+        mSecurityLogMonitor.discardLogs();
+        if (mNetworkLogger != null) {
+            mNetworkLogger.discardLogs();
+        }
+        // TODO: We should discard pre-boot security logs here too, as otherwise those
+        // logs (which might contain data from the user just removed) will be
+        // available after next boot.
+    }
+
     @Override
     public boolean isNetworkLoggingEnabled(ComponentName admin) {
         if (!mHasFeature) {
@@ -10090,32 +10148,27 @@
      * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH
      */
     @Override
-    public synchronized List<NetworkEvent> retrieveNetworkLogs(ComponentName admin,
-            long batchToken) {
+    public List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, long batchToken) {
         if (!mHasFeature) {
             return null;
         }
         Preconditions.checkNotNull(admin);
-        ensureDeviceOwnerManagingSingleUser(admin);
+        ensureDeviceOwnerAndAllUsersAffiliated(admin);
 
-        if (mNetworkLogger == null) {
-            return null;
-        }
-
-        if (!isNetworkLoggingEnabledInternalLocked()) {
-            return null;
-        }
-
-        final long currentTime = System.currentTimeMillis();
         synchronized (this) {
+            if (mNetworkLogger == null
+                    || !isNetworkLoggingEnabledInternalLocked()) {
+                return null;
+            }
+
+            final long currentTime = System.currentTimeMillis();
             DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
             if (currentTime > policyData.mLastNetworkLogsRetrievalTime) {
                 policyData.mLastNetworkLogsRetrievalTime = currentTime;
                 saveSettingsLocked(UserHandle.USER_SYSTEM);
             }
+            return mNetworkLogger.retrieveLogs(batchToken);
         }
-
-        return mNetworkLogger.retrieveLogs(batchToken);
     }
 
     private void sendNetworkLoggingNotificationLocked() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index b82cb3c..0085931 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -31,7 +31,6 @@
 
 import com.android.server.ServiceThread;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -130,6 +129,8 @@
         Log.d(TAG, "Stopping network logging");
         // stop the logging regardless of whether we fail to unregister listener
         mIsLoggingEnabled.set(false);
+        discardLogs();
+
         try {
             if (!checkIpConnectivityMetricsService()) {
                 // the IIpConnectivityMetrics service should have been present at this point
@@ -140,9 +141,43 @@
             return mIpConnectivityMetrics.unregisterNetdEventCallback();
         } catch (RemoteException re) {
             Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
-        } finally {
-            mHandlerThread.quitSafely();
             return true;
+        } finally {
+            if (mHandlerThread != null) {
+                mHandlerThread.quitSafely();
+            }
+        }
+    }
+
+    /**
+     * If logs are being collected, keep collecting them but stop notifying the device owner that
+     * new logs are available (since they cannot be retrieved)
+     */
+    void pause() {
+        if (mNetworkLoggingHandler != null) {
+            mNetworkLoggingHandler.pause();
+        }
+    }
+
+    /**
+     * If logs are being collected, start notifying the device owner when logs are ready to be
+     * collected again (if it was paused).
+     * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
+     * to notify the device owner. Therefore calling identity should be cleared before calling it
+     * (in case the method is called from a user other than the DO's user).
+     */
+    void resume() {
+        if (mNetworkLoggingHandler != null) {
+            mNetworkLoggingHandler.resume();
+        }
+    }
+
+    /**
+     * Discard all collected logs.
+     */
+    void discardLogs() {
+        if (mNetworkLoggingHandler != null) {
+            mNetworkLoggingHandler.discardLogs();
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index baa4c13..7d68412 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -55,10 +55,16 @@
     @GuardedBy("this")
     private ArrayList<NetworkEvent> mFullBatch;
 
-    // each full batch is represented by its token, which the DPC has to provide back to revieve it
+    @GuardedBy("this")
+    private boolean mPaused = false;
+
+    // each full batch is represented by its token, which the DPC has to provide back to retrieve it
     @GuardedBy("this")
     private long mCurrentFullBatchToken;
 
+    @GuardedBy("this")
+    private long mLastRetrievedFullBatchToken;
+
     NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
         super(looper);
         mDpm = dpm;
@@ -70,15 +76,19 @@
             case LOG_NETWORK_EVENT_MSG: {
                 NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
                 if (networkEvent != null) {
-                    mNetworkEvents.add(networkEvent);
-                    if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
-                        finalizeBatchAndNotifyDeviceOwnerIfNotEmpty();
+                    synchronized (NetworkLoggingHandler.this) {
+                        mNetworkEvents.add(networkEvent);
+                        if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
+                            finalizeBatchAndNotifyDeviceOwnerLocked();
+                        }
                     }
                 }
                 break;
             }
             case FINALIZE_BATCH_MSG: {
-                finalizeBatchAndNotifyDeviceOwnerIfNotEmpty();
+                synchronized (NetworkLoggingHandler.this) {
+                    finalizeBatchAndNotifyDeviceOwnerLocked();
+                }
                 break;
             }
         }
@@ -91,22 +101,49 @@
                 + "ms from now.");
     }
 
-    private synchronized void finalizeBatchAndNotifyDeviceOwnerIfNotEmpty() {
+    synchronized void pause() {
+        Log.d(TAG, "Paused network logging");
+        mPaused = true;
+    }
+
+    synchronized void resume() {
+        if (!mPaused) {
+            Log.d(TAG, "Attempted to resume network logging, but logging is not paused.");
+            return;
+        }
+
+        Log.d(TAG, "Resumed network logging. Current batch="
+                + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken);
+        mPaused = false;
+
+        // If there is a full batch ready that the device owner hasn't been notified about, do it
+        // now.
+        if (mFullBatch != null && mFullBatch.size() > 0
+                && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) {
+            scheduleBatchFinalization();
+            notifyDeviceOwnerLocked();
+        }
+    }
+
+    synchronized void discardLogs() {
+        mFullBatch = null;
+        mNetworkEvents = new ArrayList<NetworkEvent>();
+        Log.d(TAG, "Discarded all network logs");
+    }
+
+    @GuardedBy("this")
+    private void finalizeBatchAndNotifyDeviceOwnerLocked() {
         if (mNetworkEvents.size() > 0) {
             // finalize the batch and start a new one from scratch
             mFullBatch = mNetworkEvents;
             mCurrentFullBatchToken++;
             mNetworkEvents = new ArrayList<NetworkEvent>();
-            // notify DO that there's a new non-empty batch waiting
-            Bundle extras = new Bundle();
-            extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
-            extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
-            Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
-                    + mCurrentFullBatchToken);
-            mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+            if (!mPaused) {
+                notifyDeviceOwnerLocked();
+            }
         } else {
             // don't notify the DO, since there are no events; DPC can still retrieve
-            // the last full batch
+            // the last full batch if not paused.
             Log.d(TAG, "Was about to finalize the batch, but there were no events to send to"
                     + " the DPC, the batchToken of last available batch: "
                     + mCurrentFullBatchToken);
@@ -115,10 +152,21 @@
         scheduleBatchFinalization();
     }
 
+    @GuardedBy("this")
+    private void notifyDeviceOwnerLocked() {
+        Bundle extras = new Bundle();
+        extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
+        extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
+        Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
+                + mCurrentFullBatchToken);
+        mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+    }
+
     synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) {
         if (batchToken != mCurrentFullBatchToken) {
             return null;
         }
+        mLastRetrievedFullBatchToken = mCurrentFullBatchToken;
         return mFullBatch;
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 79702a8..18f06be 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -19,6 +19,7 @@
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
 
@@ -50,7 +51,7 @@
         mService = service;
     }
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = false;  // STOPSHIP if true.
     private static final String TAG = "SecurityLogMonitor";
     /**
      * Each log entry can hold up to 4K bytes (but as of {@link android.os.Build.VERSION_CODES#N}
@@ -78,17 +79,25 @@
     private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
     @GuardedBy("mLock")
     private boolean mAllowedToRetrieve = false;
-    // When DO will be allowed to retrieves the log, in milliseconds.
+
+    /**
+     * When DO will be allowed to retrieve the log, in milliseconds since boot (as per
+     * {@link SystemClock#elapsedRealtime()})
+     */
     @GuardedBy("mLock")
-    private long mNextAllowedRetrivalTimeMillis = -1;
+    private long mNextAllowedRetrievalTimeMillis = -1;
+    @GuardedBy("mLock")
+    private boolean mPaused = false;
 
     void start() {
+        Slog.i(TAG, "Starting security logging.");
         mLock.lock();
         try {
             if (mMonitorThread == null) {
                 mPendingLogs = new ArrayList<SecurityEvent>();
                 mAllowedToRetrieve = false;
-                mNextAllowedRetrivalTimeMillis = -1;
+                mNextAllowedRetrievalTimeMillis = -1;
+                mPaused = false;
 
                 mMonitorThread = new Thread(this);
                 mMonitorThread.start();
@@ -99,6 +108,7 @@
     }
 
     void stop() {
+        Slog.i(TAG, "Stopping security logging.");
         mLock.lock();
         try {
             if (mMonitorThread != null) {
@@ -111,7 +121,8 @@
                 // Reset state and clear buffer
                 mPendingLogs = new ArrayList<SecurityEvent>();
                 mAllowedToRetrieve = false;
-                mNextAllowedRetrivalTimeMillis = -1;
+                mNextAllowedRetrievalTimeMillis = -1;
+                mPaused = false;
                 mMonitorThread = null;
             }
         } finally {
@@ -120,6 +131,58 @@
     }
 
     /**
+     * If logs are being collected, keep collecting them but stop notifying the device owner that
+     * new logs are available (since they cannot be retrieved).
+     */
+    void pause() {
+        Slog.i(TAG, "Paused.");
+
+        mLock.lock();
+        mPaused = true;
+        mAllowedToRetrieve = false;
+        mLock.unlock();
+    }
+
+    /**
+     * If logs are being collected, start notifying the device owner when logs are ready to be
+     * retrieved again (if it was paused).
+     * <p>If logging is enabled and there are logs ready to be retrieved, this method will attempt
+     * to notify the device owner. Therefore calling identity should be cleared before calling it
+     * (in case the method is called from a user other than the DO's user).
+     */
+    void resume() {
+        mLock.lock();
+        try {
+            if (!mPaused) {
+                Log.d(TAG, "Attempted to resume, but logging is not paused.");
+                return;
+            }
+            mPaused = false;
+            mAllowedToRetrieve = false;
+        } finally {
+            mLock.unlock();
+        }
+
+        Slog.i(TAG, "Resumed.");
+        try {
+            notifyDeviceOwnerIfNeeded();
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Thread interrupted.", e);
+        }
+    }
+
+    /**
+     * Discard all collected logs.
+     */
+    void discardLogs() {
+        mLock.lock();
+        mAllowedToRetrieve = false;
+        mPendingLogs = new ArrayList<SecurityEvent>();
+        mLock.unlock();
+        Slog.i(TAG, "Discarded all logs.");
+    }
+
+    /**
      * Returns the new batch of logs since the last call to this method. Returns null if
      * rate limit is exceeded.
      */
@@ -128,7 +191,7 @@
         try {
             if (mAllowedToRetrieve) {
                 mAllowedToRetrieve = false;
-                mNextAllowedRetrivalTimeMillis = System.currentTimeMillis()
+                mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
                         + RATE_LIMIT_INTERVAL_MILLISECONDS;
                 List<SecurityEvent> result = mPendingLogs;
                 mPendingLogs = new ArrayList<SecurityEvent>();
@@ -163,7 +226,7 @@
                     SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
                 }
                 if (!logs.isEmpty()) {
-                    if (DEBUG) Slog.d(TAG, "processing new logs");
+                    if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size());
                     mLock.lockInterruptibly();
                     try {
                         mPendingLogs.addAll(logs);
@@ -172,6 +235,7 @@
                             mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
                                     mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
                                     mPendingLogs.size()));
+                            Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
                         }
                     } finally {
                         mLock.unlock();
@@ -188,7 +252,7 @@
                 break;
             }
         }
-        if (DEBUG) Slog.d(TAG, "MonitorThread exit.");
+        Slog.i(TAG, "MonitorThread exit.");
     }
 
     private void notifyDeviceOwnerIfNeeded() throws InterruptedException {
@@ -196,15 +260,24 @@
         boolean allowToRetrieveNow = false;
         mLock.lockInterruptibly();
         try {
+            if (mPaused) {
+                return;
+            }
+
+            // STOPSHIP(b/34186771): If the previous notification didn't reach the DO and logs were
+            // not retrieved (e.g. the broadcast was sent before the user was unlocked), no more
+            // subsequent callbacks will be sent. We should make sure that the DO gets notified
+            // before logs are lost.
             int logSize = mPendingLogs.size();
             if (logSize >= BUFFER_ENTRIES_NOTIFICATION_LEVEL) {
                 // Allow DO to retrieve logs if too many pending logs
                 allowToRetrieveNow = true;
+                if (DEBUG) Slog.d(TAG, "Number of log entries over threshold: " + logSize);
             } else if (logSize > 0) {
-                if (mNextAllowedRetrivalTimeMillis == -1 ||
-                        System.currentTimeMillis() >= mNextAllowedRetrivalTimeMillis) {
+                if (SystemClock.elapsedRealtime() >= mNextAllowedRetrievalTimeMillis) {
                     // Rate limit reset
                     allowToRetrieveNow = true;
+                    if (DEBUG) Slog.d(TAG, "Timeout reached");
                 }
             }
             shouldNotifyDO = (!mAllowedToRetrieve) && allowToRetrieveNow;
@@ -213,7 +286,7 @@
             mLock.unlock();
         }
         if (shouldNotifyDO) {
-            if (DEBUG) Slog.d(TAG, "notify DO");
+            Slog.i(TAG, "notify DO");
             mService.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_SECURITY_LOGS_AVAILABLE,
                     null);
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 68cb0c5..08fb591 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -175,6 +175,8 @@
             "com.google.android.clockwork.bluetooth.WearBluetoothService";
     private static final String WEAR_WIFI_MEDIATOR_SERVICE_CLASS =
             "com.google.android.clockwork.wifi.WearWifiMediatorService";
+    private static final String WEAR_CELLULAR_MEDIATOR_SERVICE_CLASS =
+            "com.google.android.clockwork.cellular.WearCellularMediatorService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.google.android.clockwork.time.WearTimeService";
     private static final String ACCOUNT_SERVICE_CLASS =
@@ -1381,6 +1383,13 @@
             traceBeginAndSlog("StartWearWifiMediator");
             mSystemServiceManager.startService(WEAR_WIFI_MEDIATOR_SERVICE_CLASS);
             traceEnd();
+
+            if (SystemProperties.getBoolean("config.enable_cellmediator", false)) {
+                traceBeginAndSlog("StartWearCellularMediator");
+                mSystemServiceManager.startService(WEAR_CELLULAR_MEDIATOR_SERVICE_CLASS);
+                traceEnd();
+            }
+
             if (!disableNonCoreServices) {
                 traceBeginAndSlog("StartWearTimeService");
                 mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e68895e..c3eb09d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2845,7 +2845,10 @@
     public void testGetLastSecurityLogRetrievalTime() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
-        when(mContext.userManager.getUserCount()).thenReturn(1);
+
+        // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+        // feature is disabled because there are non-affiliated secondary users.
+        mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
         when(mContext.resources.getBoolean(R.bool.config_supportPreRebootSecurityLogs))
                 .thenReturn(true);
 
@@ -2854,6 +2857,10 @@
 
         // Enabling logging should not change the timestamp.
         dpm.setSecurityLoggingEnabled(admin1, true);
+        verify(mContext.settings)
+                .securityLogSetLoggingEnabledProperty(true);
+        when(mContext.settings.securityLogGetLoggingEnabledProperty())
+                .thenReturn(true);
         assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
 
         // Retrieving the logs should update the timestamp.
@@ -2906,7 +2913,7 @@
     public void testGetLastBugReportRequestTime() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
-        when(mContext.userManager.getUserCount()).thenReturn(1);
+
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
         when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject()))
@@ -2914,6 +2921,10 @@
         when(mContext.resources.getColor(eq(R.color.notification_material_background_color),
                 anyObject())).thenReturn(Color.WHITE);
 
+        // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+        // feature is disabled because there are non-affiliated secondary users.
+        mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
+
         // No bug reports were requested so far.
         assertEquals(-1, dpm.getLastBugReportRequestTime());
 
@@ -2951,7 +2962,16 @@
     public void testGetLastNetworkLogRetrievalTime() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
-        when(mContext.userManager.getUserCount()).thenReturn(1);
+        mContext.packageName = admin1.getPackageName();
+        mContext.applicationInfo = new ApplicationInfo();
+        when(mContext.resources.getColor(eq(R.color.notification_action_list), anyObject()))
+                .thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(eq(R.color.notification_material_background_color),
+                anyObject())).thenReturn(Color.WHITE);
+
+        // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+        // feature is disabled because there are non-affiliated secondary users.
+        mContext.removeUser(DpmMockContext.CALLER_USER_HANDLE);
         when(mContext.iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
                 .thenReturn(true);
 
@@ -3149,6 +3169,44 @@
         assertNull(dpm.getDeviceOwnerOrganizationName());
     }
 
+    public void testWipeDataManagedProfile() throws Exception {
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(mContext.iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        dpm.wipeData(0);
+        verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed(
+                MANAGED_PROFILE_USER_ID);
+    }
+
+    public void testWipeDataManagedProfileDisallowed() throws Exception {
+        final int MANAGED_PROFILE_USER_ID = 15;
+        final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+        addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+        // Even if the caller is the managed profile, the current user is the user 0
+        when(mContext.iactivityManager.getCurrentUser())
+                .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+        when(mContext.userManager.getUserRestrictionSource(
+                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                UserHandle.of(MANAGED_PROFILE_USER_ID)))
+                .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+        mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+        try {
+            // The PO is not allowed to remove the profile if the user restriction was set on the
+            // profile by the system
+            dpm.wipeData(0);
+            fail("SecurityException not thrown");
+        } catch (SecurityException expected) {
+        }
+    }
+
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
         when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                 userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index a1b6769..44bf547 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -393,6 +393,18 @@
         return dir;
     }
 
+    public void removeUser(int userId) {
+        for (int i = 0; i < mUserInfos.size(); i++) {
+            if (mUserInfos.get(i).id == userId) {
+                mUserInfos.remove(i);
+                break;
+            }
+        }
+        when(userManager.getUserInfo(eq(userId))).thenReturn(null);
+
+        when(userManager.isUserRunning(eq(new UserHandle(userId)))).thenReturn(false);
+    }
+
     private UserInfo getUserInfo(int userId) {
         for (UserInfo ui : mUserInfos) {
             if (ui.id == userId) {
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index ba770ef..d9f352c 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -188,7 +188,6 @@
                 String[] annotations = event.mContentAnnotations;
                 if (annotations != null) {
                     for (String annotation : annotations) {
-                        // TODO(kanlig): update with confidences of annotations.
                         stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
                     }
                 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 861a1eb..eb838fa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -936,8 +936,10 @@
      *
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
+     * @hide
      */
-    /** {@hide} */
+    @SystemApi
     public String getImei() {
         return getImei(getDefaultSim());
     }
@@ -949,8 +951,10 @@
      *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *
      * @param slotId of which deviceID is returned
+     *
+     * @hide
      */
-    /** {@hide} */
+    @SystemApi
     public String getImei(int slotId) {
         ITelephony telephony = getITelephony();
         if (telephony == null) return null;
diff --git a/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java b/tests/net/java/android/net/util/BlockingSocketReaderTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java
rename to tests/net/java/android/net/util/BlockingSocketReaderTest.java
diff --git a/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java
rename to tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
new file mode 100644
index 0000000..b2a9a49
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.connectivity.tethering;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UpstreamNetworkMonitorTest {
+    private static final int EVENT_UNM_UPDATE = 1;
+
+    @Mock private Context mContext;
+    @Mock private IConnectivityManager mCS;
+
+    private TestConnectivityManager mCM;
+    private UpstreamNetworkMonitor mUNM;
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        reset(mContext);
+        reset(mCS);
+
+        mCM = new TestConnectivityManager(mContext, mCS);
+        mUNM = new UpstreamNetworkMonitor(null, EVENT_UNM_UPDATE, (ConnectivityManager) mCM);
+    }
+
+    @Test
+    public void testDoesNothingBeforeStarted() {
+        UpstreamNetworkMonitor unm = new UpstreamNetworkMonitor(null, null, EVENT_UNM_UPDATE);
+        assertFalse(unm.mobileNetworkRequested());
+        // Given a null Context, and therefore a null ConnectivityManager,
+        // these would cause an exception, if they actually attempted anything.
+        unm.mobileUpstreamRequiresDun(true);
+        unm.mobileUpstreamRequiresDun(false);
+    }
+
+    @Test
+    public void testDefaultNetworkIsTracked() throws Exception {
+        assertEquals(0, mCM.trackingDefault.size());
+
+        mUNM.start();
+        assertEquals(1, mCM.trackingDefault.size());
+
+        mUNM.stop();
+        assertTrue(mCM.isEmpty());
+    }
+
+    @Test
+    public void testListensForDunNetworks() throws Exception {
+        assertTrue(mCM.listening.isEmpty());
+
+        mUNM.start();
+        assertFalse(mCM.listening.isEmpty());
+        assertTrue(mCM.isListeningForDun());
+
+        mUNM.stop();
+        assertTrue(mCM.isEmpty());
+    }
+
+    @Test
+    public void testCanRequestMobileNetwork() throws Exception {
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.start();
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.mobileUpstreamRequiresDun(false);
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.registerMobileNetworkRequest();
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertEquals(1, mCM.requested.size());
+        assertFalse(mCM.isDunRequested());
+
+        mUNM.stop();
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertTrue(mCM.isEmpty());
+    }
+
+    @Test
+    public void testCanRequestDunNetwork() throws Exception {
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.start();
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.mobileUpstreamRequiresDun(true);
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.registerMobileNetworkRequest();
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertEquals(1, mCM.requested.size());
+        assertTrue(mCM.isDunRequested());
+
+        mUNM.stop();
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertTrue(mCM.isEmpty());
+    }
+
+    private static class TestConnectivityManager extends ConnectivityManager {
+        public Set<NetworkCallback> trackingDefault = new HashSet<>();
+        public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
+        public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
+
+        public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
+            super(ctx, svc);
+        }
+
+        boolean isEmpty() {
+            return trackingDefault.isEmpty() &&
+                   listening.isEmpty() &&
+                   requested.isEmpty();
+        }
+
+        boolean isListeningForDun() {
+            for (NetworkRequest req : listening.values()) {
+                if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        boolean isDunRequested() {
+            for (NetworkRequest req : requested.values()) {
+                if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
+            assertFalse(requested.containsKey(cb));
+            requested.put(cb, req);
+        }
+
+        @Override
+        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
+            assertFalse(listening.containsKey(cb));
+            listening.put(cb, req);
+        }
+
+        @Override
+        public void registerDefaultNetworkCallback(NetworkCallback cb) {
+            assertFalse(trackingDefault.contains(cb));
+            trackingDefault.add(cb);
+        }
+
+        @Override
+        public void unregisterNetworkCallback(NetworkCallback cb) {
+            if (trackingDefault.contains(cb)) {
+                trackingDefault.remove(cb);
+            } else if (listening.containsKey(cb)) {
+                listening.remove(cb);
+            } else if (requested.containsKey(cb)) {
+                requested.remove(cb);
+            }
+
+            assertFalse(trackingDefault.contains(cb));
+            assertFalse(listening.containsKey(cb));
+            assertFalse(requested.containsKey(cb));
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index c3d4cef6..e0f8e1c 100644
--- a/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -21,6 +21,7 @@
 import com.android.ide.common.rendering.api.DensityBasedResourceValue;
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.PluralsResourceValue;
 import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.layoutlib.bridge.Bridge;
@@ -43,6 +44,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.content.res.Resources.Theme;
 import android.graphics.drawable.Drawable;
+import android.icu.text.PluralRules;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.LruCache;
@@ -405,9 +407,6 @@
             rv = resources.mContext.getRenderResources().resolveResValue(rv);
             if (rv != null) {
                 return rv.getValue();
-            } else {
-                Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
-                        "Unable to resolve resource " + ref, null);
             }
         }
         // Not a reference.
@@ -738,6 +737,48 @@
     }
 
     @LayoutlibDelegate
+    static String getQuantityString(Resources resources, int id, int quantity) throws
+            NotFoundException {
+        Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+        if (value != null) {
+            if (value.getSecond() instanceof PluralsResourceValue) {
+                PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond();
+                PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales()
+                        .get(0));
+                String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity));
+                if (strValue == null) {
+                    strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER);
+                }
+
+                return strValue;
+            }
+            else {
+                return value.getSecond().getValue();
+            }
+        }
+
+        // id was not found or not resolved. Throw a NotFoundException.
+        throwException(resources, id);
+
+        // this is not used since the method above always throws
+        return null;
+    }
+
+    @LayoutlibDelegate
+    static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)
+            throws NotFoundException {
+        String raw = getQuantityString(resources, id, quantity);
+        return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs);
+    }
+
+    @LayoutlibDelegate
+    static CharSequence getQuantityText(Resources resources, int id, int quantity) throws
+            NotFoundException {
+        return getQuantityString(resources, id, quantity);
+    }
+
+    @LayoutlibDelegate
     static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
             throws NotFoundException {
         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
index 21f36ce..c6827a3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java
@@ -30,6 +30,7 @@
 import java.awt.Toolkit;
 import java.awt.font.FontRenderContext;
 import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -41,6 +42,7 @@
  */
 @SuppressWarnings("deprecation")
 public class BidiRenderer {
+    private static String JAVA_VENDOR = System.getProperty("java.vendor");
 
     private static class ScriptRun {
         int start;
@@ -221,9 +223,16 @@
             frc = mGraphics.getFontRenderContext();
         } else {
             frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
+
             // Metrics obtained this way don't have anti-aliasing set. So,
             // we create a new FontRenderContext with anti-aliasing set.
-            frc = new FontRenderContext(font.getTransform(), mPaint.isAntiAliased(), frc.usesFractionalMetrics());
+            AffineTransform transform = font.getTransform();
+            if (mPaint.isAntiAliased() &&
+                    // Workaround for http://b.android.com/211659
+                    (transform.getScaleX() <= 9.9 ||
+                    !"JetBrains s.r.o".equals(JAVA_VENDOR))) {
+                frc = new FontRenderContext(transform, true, frc.usesFractionalMetrics());
+            }
         }
         GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag);
         int ng = gv.getNumGlyphs();
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 43a0ff5..c599e9d 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -20,24 +20,14 @@
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.layoutlib.bridge.impl.PorterDuffUtility;
-import com.android.ninepatch.NinePatchChunk;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.annotation.Nullable;
 import android.graphics.Bitmap.Config;
-import android.text.TextUtils;
 
-import java.awt.Color;
-import java.awt.Composite;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
-import java.awt.RenderingHints;
-import java.awt.Shape;
 import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
 
 import libcore.util.NativeAllocationRegistry_Delegate;
 
@@ -401,23 +391,6 @@
     }
 
     @LayoutlibDelegate
-    public static boolean nClipRegion(long nativeCanvas,
-                                                    long nativeRegion,
-                                                    int regionOp) {
-        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            return true;
-        }
-
-        Region_Delegate region = Region_Delegate.getDelegate(nativeRegion);
-        if (region == null) {
-            return true;
-        }
-
-        return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp);
-    }
-
-    @LayoutlibDelegate
     public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
         Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
diff --git a/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java
new file mode 100644
index 0000000..4a5ea9b
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/PointerIcon_Delegate.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+public class PointerIcon_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static void loadResource(PointerIcon icon, Context context, Resources resources,
+            int resourceId) {
+        // HACK: This bypasses the problem of having an enum resolved as a resourceId.
+        // PointerIcon would not be displayed by layoutlib anyway, so we always return the null
+        // icon.
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 1b3b563..663e56d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -66,7 +66,6 @@
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
-import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -104,6 +103,7 @@
 import java.util.List;
 import java.util.Map;
 
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
 
 /**
@@ -113,6 +113,28 @@
 public final class BridgeContext extends Context {
     private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
 
+    private static final Map<String, ResourceValue> FRAMEWORK_PATCHED_VALUES = new HashMap<>(2);
+    private static final Map<String, ResourceValue> FRAMEWORK_REPLACE_VALUES = new HashMap<>(3);
+
+    static {
+        FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue(
+                ResourceType.BOOL, "animateFirstView", "false", false));
+        FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
+                new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false));
+
+
+        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
+                new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout",
+                        "text_edit_suggestion_item", true));
+        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
+                new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout",
+                        "text_edit_suggestion_container", true));
+        FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
+                new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle",
+                        "TextAppearance.Holo.SuggestionHighlight", true));
+
+    }
+
     /** The map adds cookies to each view so that IDE can link xml tags to views. */
     private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
     /**
@@ -312,7 +334,7 @@
      * Returns the current parser at the top the of the stack.
      * @return a parser or null.
      */
-    public BridgeXmlBlockParser getCurrentParser() {
+    private BridgeXmlBlockParser getCurrentParser() {
         return mParserStack.peek();
     }
 
@@ -406,7 +428,8 @@
     }
 
     public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
-            boolean attachToRoot, boolean skipCallbackParser) {
+            @SuppressWarnings("SameParameterValue") boolean attachToRoot,
+            boolean skipCallbackParser) {
         boolean isPlatformLayout = resource.isFramework();
 
         if (!isPlatformLayout && !skipCallbackParser) {
@@ -711,11 +734,7 @@
 
             Object key = parser.getViewCookie();
             if (key != null) {
-                defaultPropMap = mDefaultPropMaps.get(key);
-                if (defaultPropMap == null) {
-                    defaultPropMap = new PropertiesMap();
-                    mDefaultPropMaps.put(key, defaultPropMap);
-                }
+                defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new PropertiesMap());
             }
 
         } else if (set instanceof BridgeLayoutParamsMapAttributes) {
@@ -909,6 +928,16 @@
                 // if there's no direct value for this attribute in the XML, we look for default
                 // values in the widget defStyle, and then in the theme.
                 if (value == null) {
+                    if (frameworkAttr) {
+                        // For some framework values, layoutlib patches the actual value in the
+                        // theme when it helps to improve the final preview. In most cases
+                        // we just disable animations.
+                        ResourceValue patchedValue = FRAMEWORK_PATCHED_VALUES.get(attrName);
+                        if (patchedValue != null) {
+                            defaultValue = patchedValue;
+                        }
+                    }
+
                     // if we found a value, we make sure this doesn't reference another value.
                     // So we resolve it.
                     if (defaultValue != null) {
@@ -916,16 +945,21 @@
                         // exist, we should log a warning and omit it.
                         String val = defaultValue.getValue();
                         if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) {
-                            if (!attrName.equals(RTL_ATTRS.get(val)) ||
-                                    getApplicationInfo().targetSdkVersion <
-                                            VERSION_CODES.JELLY_BEAN_MR1) {
+                            // Because we always use the latest framework code, some resources might
+                            // fail to resolve when using old themes (they haven't been backported).
+                            // Since this is an artifact caused by us using always the latest
+                            // code, we check for some of those values and replace them here.
+                            defaultValue = FRAMEWORK_REPLACE_VALUES.get(attrName);
+
+                            if (defaultValue == null &&
+                                    (getApplicationInfo().targetSdkVersion < JELLY_BEAN_MR1 ||
+                                    !attrName.equals(RTL_ATTRS.get(val)))) {
                                 // Only log a warning if the referenced value isn't one of the RTL
                                 // attributes, or the app targets old API.
                                 Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
                                         String.format("Failed to find '%s' in current theme.", val),
                                         val);
                             }
-                            defaultValue = null;
                         }
                     }
 
@@ -1944,7 +1978,7 @@
                 Map<List<StyleResourceValue>,
                         Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache;
 
-        public TypedArrayCache() {
+        private TypedArrayCache() {
             mCache = new IdentityHashMap<>();
         }
 
@@ -1965,17 +1999,9 @@
         public void put(int[] attrs, List<StyleResourceValue> themes, int resId,
                 Pair<BridgeTypedArray, PropertiesMap> value) {
             Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
-                    cacheFromThemes = mCache.get(attrs);
-            if (cacheFromThemes == null) {
-                cacheFromThemes = new HashMap<>();
-                mCache.put(attrs, cacheFromThemes);
-            }
+                    cacheFromThemes = mCache.computeIfAbsent(attrs, k -> new HashMap<>());
             Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
-                    cacheFromThemes.get(themes);
-            if (cacheFromResId == null) {
-                cacheFromResId = new HashMap<>();
-                cacheFromThemes.put(themes, cacheFromResId);
-            }
+                    cacheFromThemes.computeIfAbsent(themes, k -> new HashMap<>());
             cacheFromResId.put(resId, value);
         }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java
index 09937bc..5386b17 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java
@@ -74,8 +74,8 @@
     }
 
     public static String getTime(int platformVersion) {
-        if (isGreaterOrEqual(platformVersion, M)) {
-            return "6:00";
+        if (isGreaterOrEqual(platformVersion, N)) {
+            return "7:00";
         }
         if (platformVersion < GINGERBREAD) {
             return "2:20";
@@ -98,6 +98,9 @@
         if (platformVersion < M) {
             return "5:10";
         }
+        if (platformVersion < N) {
+            return "6:00";
+        }
         // Should never happen.
         return "4:04";
     }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 14f783b..85fe2a4 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -385,13 +385,10 @@
     }
 
     /**
-     * Renders the given view hierarchy to the passed canvas and returns the result of the render
-     * operation.
-     * @param canvas an optional canvas to render the views to. If null, only the measure and
-     * layout steps will be executed.
+     * Runs a layout pass for the given view root
      */
-    private static Result renderAndBuildResult(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
-            @Nullable Canvas canvas, int width, int height) {
+    private static void doLayout(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
+            int width, int height) {
         // measure again with the size we need
         // This must always be done before the call to layout
         measureView(viewRoot, null /*measuredView*/,
@@ -401,7 +398,16 @@
         // now do the layout.
         viewRoot.layout(0, 0, width, height);
         handleScrolling(context, viewRoot);
+    }
 
+    /**
+     * Renders the given view hierarchy to the passed canvas and returns the result of the render
+     * operation.
+     * @param canvas an optional canvas to render the views to. If null, only the measure and
+     * layout steps will be executed.
+     */
+    private static Result renderAndBuildResult(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
+            @Nullable Canvas canvas, int width, int height) {
         if (canvas == null) {
             return SUCCESS.createResult();
         }
@@ -479,6 +485,7 @@
                 // delete the canvas and image to reset them on the next full rendering
                 mImage = null;
                 mCanvas = null;
+                doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
             } else {
                 // draw the views
                 // create the BufferedImage into which the layout will be rendered.
@@ -539,6 +546,7 @@
                     gc.dispose();
                 }
 
+                doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
                 if (mElapsedFrameTimeNanos >= 0) {
                     long initialTime = System_Delegate.nanoTime();
                     if (!mFirstFrameExecuted) {
diff --git a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
index 6246ec1b8..04fabc2 100644
--- a/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
@@ -52,9 +52,15 @@
 
     @LayoutlibDelegate
     /*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
-        NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
-        if (delegate != null) {
-            delegate.mFinalizer.free(nativePtr);
+        // This method MIGHT run in the context of the finalizer thread. If the delegate method
+        // crashes, it could bring down the VM. That's why we catch all the exceptions and ignore
+        // them.
+        try {
+            NativeAllocationRegistry_Delegate delegate = sManager.getDelegate(freeFunction);
+            if (delegate != null) {
+                delegate.mFinalizer.free(nativePtr);
+            }
+        } catch (Throwable ignore) {
         }
     }
 
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 9ee416a..33d55de 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -30,7 +30,8 @@
 			layoutlib_api-prebuilt \
 			tools-common-prebuilt \
 			sdk-common \
-			junit-host
+			junit-host \
+			guavalib
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
index f274dbf..199ea60 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/activity.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index ef95f83..89ff5db 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
index 6eeb82c..1f4405d 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png
index 336f9d8..c3bd708 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/array_check.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
new file mode 100644
index 0000000..eb431b0
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
index 290018b..b756719 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
index adb58a3..05a3665 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/allwidgets.xml
@@ -28,7 +28,8 @@
         android:layout_alignParentStart="true"
         android:layout_below="@id/frameLayout"
         android:text="Large Text"
-        android:textAppearance="?android:attr/textAppearanceLarge" />
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:pointerIcon="hand" />
 
     <TextView
         android:id="@id/textView3"
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
index f6e14d2..5f58d39 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
@@ -17,6 +17,7 @@
         <!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> -->
         <item>?android:attr/candidatesTextStyleSpans</item>
         <item>@android:string/unknownName</item>  <!-- value = Unknown -->
+        <item>?EC</item>
     </string-array>
 
     <!-- resources that the above array can refer to -->
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
index c8a5fec..debe33b 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
@@ -3,7 +3,6 @@
     <!-- Base application theme. -->
     <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
         <item name="myattr">@integer/ten</item>
-        <item name="android:animateFirstView">false</item>
     </style>
 
 </resources>
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index cdcae89..ded52a7 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,815 +16,23 @@
 
 package com.android.layoutlib.bridge.intensive;
 
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
-import com.android.ide.common.rendering.api.Result;
-import com.android.ide.common.rendering.api.SessionParams;
-import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.ide.common.resources.FrameworkResources;
-import com.android.ide.common.resources.ResourceItem;
-import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.common.resources.ResourceResolver;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
-import com.android.io.FolderWrapper;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.layoutlib.bridge.android.BridgeContext;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.RenderAction;
-import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
-import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
-import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
-import com.android.resources.Density;
-import com.android.resources.Navigation;
-import com.android.resources.ResourceType;
-import com.android.resources.ScreenOrientation;
-import com.android.tools.layoutlib.java.System_Delegate;
-import com.android.utils.ILogger;
+import com.android.layoutlib.bridge.TestDelegates;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParserTest;
+import com.android.layoutlib.bridge.impl.LayoutParserWrapperTest;
 
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.AssetManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.DisplayMetrics;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Field;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
-import com.google.android.collect.Lists;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import android.graphics.Matrix_DelegateTest;
 
 /**
- * This is a set of tests that loads all the framework resources and a project checked in this
- * test's resources. The main dependencies
- * are:
- * 1. Fonts directory.
- * 2. Framework Resources.
- * 3. App resources.
- * 4. build.prop file
- *
- * These are configured by two variables set in the system properties.
- *
- * 1. platform.dir: This is the directory for the current platform in the built SDK
- *     (.../sdk/platforms/android-<version>).
- *
- *     The fonts are platform.dir/data/fonts.
- *     The Framework resources are platform.dir/data/res.
- *     build.prop is at platform.dir/build.prop.
- *
- * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
- *     falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
- *
- *     The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
+ * Suite used by the layoutlib build system
  */
+@RunWith(Suite.class)
+@SuiteClasses({
+        RenderTests.class, LayoutParserWrapperTest.class, BridgeXmlBlockParserTest.class,
+        Matrix_DelegateTest.class, TestDelegates.class
+})
 public class Main {
-
-    private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
-    private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
-
-    private static final String PLATFORM_DIR;
-    private static final String TEST_RES_DIR;
-    /** Location of the app to test inside {@link #TEST_RES_DIR}*/
-    private static final String APP_TEST_DIR = "testApp/MyApplication";
-    /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
-    private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
-    private static final String APP_CLASSES_LOCATION =
-            APP_TEST_DIR + "/build/intermediates/classes/debug/";
-
-    private static LayoutLog sLayoutLibLog;
-    private static FrameworkResources sFrameworkRepo;
-    private static ResourceRepository sProjectResources;
-    private static  ILogger sLogger;
-    private static Bridge sBridge;
-
-    /** List of log messages generated by a render call. It can be used to find specific errors */
-    private static ArrayList<String> sRenderMessages = Lists.newArrayList();
-
-    // Default class loader with access to the app classes
-    private ClassLoader mDefaultClassLoader =
-            new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
-
-    @Rule
-    public TestWatcher sRenderMessageWatcher = new TestWatcher() {
-        @Override
-        protected void succeeded(Description description) {
-            // We only check error messages if the rest of the test case was successful.
-            if (!sRenderMessages.isEmpty()) {
-                fail(description.getMethodName() + " render error message: " + sRenderMessages.get
-                        (0));
-            }
-        }
-    };
-
-    static {
-        // Test that System Properties are properly set.
-        PLATFORM_DIR = getPlatformDir();
-        if (PLATFORM_DIR == null) {
-            fail(String.format("System Property %1$s not properly set. The value is %2$s",
-                    PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
-        }
-
-        TEST_RES_DIR = getTestResDir();
-        if (TEST_RES_DIR == null) {
-            fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
-                    RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
-        }
-    }
-
-    private static String getPlatformDir() {
-        String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
-        if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
-            return platformDir;
-        }
-        // System Property not set. Try to find the directory in the build directory.
-        String androidHostOut = System.getenv("ANDROID_HOST_OUT");
-        if (androidHostOut != null) {
-            platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
-            if (platformDir != null) {
-                return platformDir;
-            }
-        }
-        String workingDirString = System.getProperty("user.dir");
-        File workingDir = new File(workingDirString);
-        // Test if workingDir is android checkout root.
-        platformDir = getPlatformDirFromRoot(workingDir);
-        if (platformDir != null) {
-            return platformDir;
-        }
-
-        // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
-        File currentDir = workingDir;
-        if (currentDir.getName().equalsIgnoreCase("bridge")) {
-            currentDir = currentDir.getParentFile();
-        }
-        // Test if currentDir is  platform/frameworks/base/tools/layoutlib. That is, root should be
-        // workingDir/../../../../  (4 levels up)
-        for (int i = 0; i < 4; i++) {
-            if (currentDir != null) {
-                currentDir = currentDir.getParentFile();
-            }
-        }
-        return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
-    }
-
-    private static String getPlatformDirFromRoot(File root) {
-        if (!root.isDirectory()) {
-            return null;
-        }
-        File out = new File(root, "out");
-        if (!out.isDirectory()) {
-            return null;
-        }
-        File host = new File(out, "host");
-        if (!host.isDirectory()) {
-            return null;
-        }
-        File[] hosts = host.listFiles(path -> path.isDirectory() &&
-                (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
-        assert hosts != null;
-        for (File hostOut : hosts) {
-            String platformDir = getPlatformDirFromHostOut(hostOut);
-            if (platformDir != null) {
-                return platformDir;
-            }
-        }
-        return null;
-    }
-
-    private static String getPlatformDirFromHostOut(File out) {
-        if (!out.isDirectory()) {
-            return null;
-        }
-        File sdkDir = new File(out, "sdk");
-        if (!sdkDir.isDirectory()) {
-            return null;
-        }
-        File[] sdkDirs = sdkDir.listFiles(path -> {
-            // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
-            return path.isDirectory() && path.getName().startsWith("sdk");
-        });
-        assert sdkDirs != null;
-        for (File dir : sdkDirs) {
-            String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
-            if (platformDir != null) {
-                return platformDir;
-            }
-        }
-        return null;
-    }
-
-    private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
-        File[] possibleSdks = sdkDir.listFiles(
-                path -> path.isDirectory() && path.getName().contains("android-sdk"));
-        assert possibleSdks != null;
-        for (File possibleSdk : possibleSdks) {
-            File platformsDir = new File(possibleSdk, "platforms");
-            File[] platforms = platformsDir.listFiles(
-                    path -> path.isDirectory() && path.getName().startsWith("android-"));
-            if (platforms == null || platforms.length == 0) {
-                continue;
-            }
-            Arrays.sort(platforms, (o1, o2) -> {
-                final int MAX_VALUE = 1000;
-                String suffix1 = o1.getName().substring("android-".length());
-                String suffix2 = o2.getName().substring("android-".length());
-                int suff1, suff2;
-                try {
-                    suff1 = Integer.parseInt(suffix1);
-                } catch (NumberFormatException e) {
-                    suff1 = MAX_VALUE;
-                }
-                try {
-                    suff2 = Integer.parseInt(suffix2);
-                } catch (NumberFormatException e) {
-                    suff2 = MAX_VALUE;
-                }
-                if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
-                    return suff2 - suff1;
-                }
-                return suffix2.compareTo(suffix1);
-            });
-            return platforms[0].getAbsolutePath();
-        }
-        return null;
-    }
-
-    private static String getTestResDir() {
-        String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
-        if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
-            return resourceDir;
-        }
-        // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
-        try {
-            URL location = Main.class.getProtectionDomain().getCodeSource().getLocation();
-            return new File(location.getPath()).exists() ? location.getPath() : null;
-        } catch (NullPointerException e) {
-            // Prevent a lot of null checks by just catching the exception.
-            return null;
-        }
-    }
-
-    /**
-     * Initialize the bridge and the resource maps.
-     */
-    @BeforeClass
-    public static void setUp() {
-        File data_dir = new File(PLATFORM_DIR, "data");
-        File res = new File(data_dir, "res");
-        sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
-        sFrameworkRepo.loadResources();
-        sFrameworkRepo.loadPublicResources(getLogger());
-
-        sProjectResources =
-                new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
-                        false) {
-            @NonNull
-            @Override
-            protected ResourceItem createResourceItem(@NonNull String name) {
-                return new ResourceItem(name);
-            }
-        };
-        sProjectResources.loadResources();
-
-        File fontLocation = new File(data_dir, "fonts");
-        File buildProp = new File(PLATFORM_DIR, "build.prop");
-        File attrs = new File(res, "values" + File.separator + "attrs.xml");
-        sBridge = new Bridge();
-        sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
-                ConfigGenerator.getEnumMap(attrs), getLayoutLog());
-    }
-
-    @Before
-    public void beforeTestCase() {
-        sRenderMessages.clear();
-    }
-
-    /** Test activity.xml */
-    @Test
-    public void testActivity() throws ClassNotFoundException {
-        renderAndVerify("activity.xml", "activity.png");
-    }
-
-    @Test
-    public void testTranslucentBars() throws ClassNotFoundException {
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
-                RenderingMode.NORMAL, 22);
-        renderAndVerify(params, "four_corners_translucent.png");
-
-        parser = createLayoutPullParser("four_corners.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
-                RenderingMode.NORMAL, 22);
-        renderAndVerify(params, "four_corners_translucent_land.png");
-
-        parser = createLayoutPullParser("four_corners.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.NORMAL, 22);
-        renderAndVerify(params, "four_corners.png");
-    }
-
-    private static void gc() {
-        // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
-        Object obj = new Object();
-        WeakReference ref = new WeakReference<>(obj);
-        //noinspection UnusedAssignment
-        obj = null;
-        while(ref.get() != null) {
-            System.gc();
-            System.runFinalization();
-        }
-
-        System.gc();
-        System.runFinalization();
-    }
-
-    /** Test allwidgets.xml */
-    @Test
-    public void testAllWidgets() throws ClassNotFoundException {
-        renderAndVerify("allwidgets.xml", "allwidgets.png");
-
-        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
-        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
-    }
-
-    @Test
-    public void testArrayCheck() throws ClassNotFoundException {
-        renderAndVerify("array_check.xml", "array_check.png");
-    }
-
-    @Test
-    public void testAllWidgetsTablet() throws ClassNotFoundException {
-        renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
-
-        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
-        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
-    }
-
-    @Test
-    public void testActivityActionBar() throws ClassNotFoundException {
-        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "simple_activity_noactionbar.png");
-
-        parser = createLayoutPullParser("simple_activity.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "simple_activity.png");
-
-        // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
-        // displaying menus.
-        parser = createLayoutPullParser("simple_activity.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.V_SCROLL, 22);
-        params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
-        renderAndVerify(params, "simple_activity.png");
-    }
-
-    @Test
-    public void testOnApplyInsetsCall()
-            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
-        // We get the widget via reflection to avoid IntelliJ complaining about the class being
-        // located in the wrong package. (From the Bridge tests point of view, it is)
-        Class insetsWidgetClass = Class.forName("com.android.layoutlib.test.myapplication.widgets" +
-                ".InsetsWidget");
-        Field field = insetsWidgetClass.getDeclaredField("sApplyInsetsCalled");
-        assertFalse((Boolean)field.get(null));
-
-        LayoutPullParser parser = createLayoutPullParser("insets.xml");
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
-                RenderingMode.NORMAL, 22);
-
-        render(params, -1);
-
-        assertTrue((Boolean)field.get(null));
-        field.set(null, false);
-    }
-
-    @AfterClass
-    public static void tearDown() {
-        sLayoutLibLog = null;
-        sFrameworkRepo = null;
-        sProjectResources = null;
-        sLogger = null;
-        sBridge = null;
-
-        gc();
-
-        System.out.println("Objects still linked from the DelegateManager:");
-        DelegateManager.dump(System.out);
-    }
-
-    /** Test expand_layout.xml */
-    @Test
-    public void testExpand() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        ConfigGenerator customConfigGenerator = new ConfigGenerator()
-                .setScreenWidth(300)
-                .setScreenHeight(20)
-                .setDensity(Density.XHIGH)
-                .setNavigation(Navigation.NONAV);
-
-        SessionParams params = getSessionParams(parser, customConfigGenerator,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "expand_vert_layout.png");
-
-        customConfigGenerator = new ConfigGenerator()
-                .setScreenWidth(20)
-                .setScreenHeight(300)
-                .setDensity(Density.XHIGH)
-                .setNavigation(Navigation.NONAV);
-        parser = createLayoutPullParser("expand_horz_layout.xml");
-        params = getSessionParams(parser, customConfigGenerator,
-                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
-                RenderingMode.H_SCROLL, 22);
-
-        renderAndVerify(params, "expand_horz_layout.png");
-    }
-
-    /** Test indeterminate_progressbar.xml */
-    @Test
-    public void testVectorAnimation() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
-
-        parser = createLayoutPullParser("indeterminate_progressbar.xml");
-        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-        renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
-    }
-
-    /**
-     * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
-     * for vector drawables (lines, moves and cubic and quadratic curves).
-     */
-    @Test
-    public void testVectorDrawable() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
-    }
-
-    /**
-     * Regression test for http://b.android.com/91383 and http://b.android.com/203797
-     */
-    @Test
-    public void testVectorDrawable91383() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-
-        renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
-    }
-
-    /** Test activity.xml */
-    @Test
-    public void testScrolling() throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
-                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
-                RenderingMode.V_SCROLL, 22);
-        params.setForceNoDecor();
-        params.setExtendedViewInfoMode(true);
-
-        RenderResult result = renderAndVerify(params, "scrolled.png");
-        assertNotNull(result);
-        assertNotNull(result.getResult());
-        assertTrue(result.getResult().isSuccess());
-
-        ViewInfo rootLayout = result.getRootViews().get(0);
-        // Check the first box in the main LinearLayout
-        assertEquals(-90, rootLayout.getChildren().get(0).getTop());
-        assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
-        assertEquals(90, rootLayout.getChildren().get(0).getBottom());
-        assertEquals(150, rootLayout.getChildren().get(0).getRight());
-
-        // Check the first box within the nested LinearLayout
-        assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
-        assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
-        assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
-        assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
-    }
-
-    @Test
-    public void testGetResourceNameVariants() throws Exception {
-        // Setup
-        // Create the layout pull parser for our resources (empty.xml can not be part of the test
-        // app as it won't compile).
-        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
-        AssetManager assetManager = AssetManager.getSystem();
-        DisplayMetrics metrics = new DisplayMetrics();
-        Configuration configuration = RenderAction.getConfiguration(params);
-        Resources resources = new Resources(assetManager, metrics, configuration);
-        resources.mLayoutlibCallback = params.getLayoutlibCallback();
-        resources.mContext =
-                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
-                        params.getAssets(), params.getLayoutlibCallback(), configuration,
-                        params.getTargetSdkVersion(), params.isRtlSupported());
-        // Test
-        assertEquals("android:style/ButtonBar",
-                resources.getResourceName(android.R.style.ButtonBar));
-        assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
-        assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
-        assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
-        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
-        assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
-                resources.getResourceName(id));
-        assertEquals("com.android.layoutlib.test.myapplication",
-                resources.getResourcePackageName(id));
-        assertEquals("string", resources.getResourceTypeName(id));
-        assertEquals("app_name", resources.getResourceEntryName(id));
-    }
-
-    @NonNull
-    private LayoutPullParser createLayoutPullParser(String layoutPath) {
-        return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
-    }
-
-    @NonNull
-    private static RenderResult render(SessionParams params, long frameTimeNanos) {
-        // TODO: Set up action bar handler properly to test menu rendering.
-        // Create session params.
-        System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
-        System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
-        RenderSession session = sBridge.createSession(params);
-
-        try {
-
-            if (frameTimeNanos != -1) {
-                session.setElapsedFrameTimeNanos(frameTimeNanos);
-            }
-
-            if (!session.getResult().isSuccess()) {
-                getLogger().error(session.getResult().getException(),
-                        session.getResult().getErrorMessage());
-            }
-            // Render the session with a timeout of 50s.
-            Result renderResult = session.render(50000);
-            if (!renderResult.isSuccess()) {
-                getLogger().error(session.getResult().getException(),
-                        session.getResult().getErrorMessage());
-            }
-
-            return RenderResult.getFromSession(session);
-        } finally {
-            session.dispose();
-        }
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout doesn't throw any
-     * exceptions and matches the provided image.
-     * <p>
-     * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
-     * how far in the future is.
-     */
-    @Nullable
-    private static RenderResult renderAndVerify(SessionParams params, String goldenFileName, long
-            frameTimeNanos)
-            throws ClassNotFoundException {
-        RenderResult result = Main.render(params, frameTimeNanos);
-        try {
-            String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
-            assertNotNull(result.getImage());
-            ImageUtils.requireSimilar(goldenImagePath, result.getImage());
-        } catch (IOException e) {
-            getLogger().error(e, e.getMessage());
-        }
-
-        return result;
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout doesn't throw any
-     * exceptions and matches the provided image.
-     */
-    @Nullable
-    private static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
-            throws ClassNotFoundException {
-        return Main.renderAndVerify(params, goldenFileName, -1);
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout on nexus 5
-     * doesn't throw any exceptions and matches the provided image.
-     */
-    @Nullable
-    private RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
-            throws ClassNotFoundException {
-        return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
-    }
-
-    /**
-     * Create a new rendering session and test that rendering the given layout on given device
-     * doesn't throw any exceptions and matches the provided image.
-     */
-    @Nullable
-    private RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
-            ConfigGenerator deviceConfig)
-            throws ClassNotFoundException {
-        SessionParams params = createSessionParams(layoutFileName, deviceConfig);
-        return renderAndVerify(params, goldenFileName);
-    }
-
-    private SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
-            throws ClassNotFoundException {
-        // Create the layout pull parser.
-        LayoutPullParser parser = createLayoutPullParser(layoutFileName);
-        // Create LayoutLibCallback.
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        // TODO: Set up action bar handler properly to test menu rendering.
-        // Create session params.
-        return getSessionParams(parser, deviceConfig,
-                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
-    }
-
-    /**
-     * Uses Theme.Material and Target sdk version as 22.
-     */
-    private SessionParams getSessionParams(LayoutPullParser layoutParser,
-            ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
-            String themeName, boolean isProjectTheme, RenderingMode renderingMode, int targetSdk) {
-        FolderConfiguration config = configGenerator.getFolderConfig();
-        ResourceResolver resourceResolver =
-                ResourceResolver.create(sProjectResources.getConfiguredResources(config),
-                        sFrameworkRepo.getConfiguredResources(config),
-                        themeName, isProjectTheme);
-
-        SessionParams sessionParams = new SessionParams(
-                layoutParser,
-                renderingMode,
-                null /*used for caching*/,
-                configGenerator.getHardwareConfig(),
-                resourceResolver,
-                layoutLibCallback,
-                0,
-                targetSdk,
-                getLayoutLog());
-        sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
-        return sessionParams;
-    }
-
-    private static LayoutLog getLayoutLog() {
-        if (sLayoutLibLog == null) {
-            sLayoutLibLog = new LayoutLog() {
-                @Override
-                public void warning(String tag, String message, Object data) {
-                    System.out.println("Warning " + tag + ": " + message);
-                    failWithMsg(message);
-                }
-
-                @Override
-                public void fidelityWarning(@Nullable String tag, String message,
-                        Throwable throwable, Object data) {
-
-                    System.out.println("FidelityWarning " + tag + ": " + message);
-                    if (throwable != null) {
-                        throwable.printStackTrace();
-                    }
-                    failWithMsg(message == null ? "" : message);
-                }
-
-                @Override
-                public void error(String tag, String message, Object data) {
-                    System.out.println("Error " + tag + ": " + message);
-                    failWithMsg(message);
-                }
-
-                @Override
-                public void error(String tag, String message, Throwable throwable, Object data) {
-                    System.out.println("Error " + tag + ": " + message);
-                    if (throwable != null) {
-                        throwable.printStackTrace();
-                    }
-                    failWithMsg(message);
-                }
-            };
-        }
-        return sLayoutLibLog;
-    }
-
-    private static ILogger getLogger() {
-        if (sLogger == null) {
-            sLogger = new ILogger() {
-                @Override
-                public void error(Throwable t, @Nullable String msgFormat, Object... args) {
-                    if (t != null) {
-                        t.printStackTrace();
-                    }
-                    failWithMsg(msgFormat == null ? "" : msgFormat, args);
-                }
-
-                @Override
-                public void warning(@NonNull String msgFormat, Object... args) {
-                    failWithMsg(msgFormat, args);
-                }
-
-                @Override
-                public void info(@NonNull String msgFormat, Object... args) {
-                    // pass.
-                }
-
-                @Override
-                public void verbose(@NonNull String msgFormat, Object... args) {
-                    // pass.
-                }
-            };
-        }
-        return sLogger;
-    }
-
-    private static void failWithMsg(@NonNull String msgFormat, Object... args) {
-        sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
-    }
 }
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
new file mode 100644
index 0000000..c90c26a
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.util.perf.PerformanceRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.annotation.NonNull;
+
+/**
+ * Set of render tests
+ */
+@RunWith(PerformanceRunner.class)
+public class PerformanceTests extends RenderTestBase {
+
+    @Before
+    public void setUp() {
+        ignoreAllLogging();
+    }
+
+
+    private void render(@NonNull String layoutFileName) throws ClassNotFoundException {
+        SessionParams params = createSessionParams(layoutFileName, ConfigGenerator.NEXUS_5);
+        render(params, 250);
+    }
+
+    @Test
+    public void testActivity() throws ClassNotFoundException {
+        render("activity.xml");
+    }
+
+    @Test
+    public void testAllWidgets() throws ClassNotFoundException {
+        render("allwidgets.xml");
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
new file mode 100644
index 0000000..3e5f9e0
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.resources.FrameworkResources;
+import com.android.ide.common.resources.ResourceItem;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.io.FolderWrapper;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.layoutlib.bridge.intensive.util.ImageUtils;
+import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
+import com.android.layoutlib.bridge.intensive.util.TestUtils;
+import com.android.tools.layoutlib.java.System_Delegate;
+import com.android.utils.ILogger;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+import com.google.android.collect.Lists;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Base class for render tests. The render tests load all the framework resources and a project
+ * checked in this test's resources. The main dependencies
+ * are:
+ * 1. Fonts directory.
+ * 2. Framework Resources.
+ * 3. App resources.
+ * 4. build.prop file
+ * <p>
+ * These are configured by two variables set in the system properties.
+ * <p>
+ * 1. platform.dir: This is the directory for the current platform in the built SDK
+ * (.../sdk/platforms/android-<version>).
+ * <p>
+ * The fonts are platform.dir/data/fonts.
+ * The Framework resources are platform.dir/data/res.
+ * build.prop is at platform.dir/build.prop.
+ * <p>
+ * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this
+ * falls back to getClass().getProtectionDomain().getCodeSource().getLocation()
+ * <p>
+ * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res
+ */
+public class RenderTestBase {
+
+    private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
+    private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
+
+    private static final String PLATFORM_DIR;
+    private static final String TEST_RES_DIR;
+    /** Location of the app to test inside {@link #TEST_RES_DIR} */
+    private static final String APP_TEST_DIR = "testApp/MyApplication";
+    /** Location of the app's res dir inside {@link #TEST_RES_DIR} */
+    private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
+    private static final String APP_CLASSES_LOCATION =
+            APP_TEST_DIR + "/build/intermediates/classes/debug/";
+    protected static Bridge sBridge;
+    /** List of log messages generated by a render call. It can be used to find specific errors */
+    protected static ArrayList<String> sRenderMessages = Lists.newArrayList();
+    private static LayoutLog sLayoutLibLog;
+    private static FrameworkResources sFrameworkRepo;
+    private static ResourceRepository sProjectResources;
+    private static ILogger sLogger;
+
+    static {
+        // Test that System Properties are properly set.
+        PLATFORM_DIR = getPlatformDir();
+        if (PLATFORM_DIR == null) {
+            fail(String.format("System Property %1$s not properly set. The value is %2$s",
+                    PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY)));
+        }
+
+        TEST_RES_DIR = getTestResDir();
+        if (TEST_RES_DIR == null) {
+            fail(String.format("System property %1$s.dir not properly set. The value is %2$s",
+                    RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY)));
+        }
+    }
+
+    @Rule
+    public TestWatcher sRenderMessageWatcher = new TestWatcher() {
+        @Override
+        protected void succeeded(Description description) {
+            // We only check error messages if the rest of the test case was successful.
+            if (!sRenderMessages.isEmpty()) {
+                fail(description.getMethodName() + " render error message: " +
+                        sRenderMessages.get(0));
+            }
+        }
+    };
+    // Default class loader with access to the app classes
+    protected ClassLoader mDefaultClassLoader =
+            new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
+
+    private static String getPlatformDir() {
+        String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
+        if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) {
+            return platformDir;
+        }
+        // System Property not set. Try to find the directory in the build directory.
+        String androidHostOut = System.getenv("ANDROID_HOST_OUT");
+        if (androidHostOut != null) {
+            platformDir = getPlatformDirFromHostOut(new File(androidHostOut));
+            if (platformDir != null) {
+                return platformDir;
+            }
+        }
+        String workingDirString = System.getProperty("user.dir");
+        File workingDir = new File(workingDirString);
+        // Test if workingDir is android checkout root.
+        platformDir = getPlatformDirFromRoot(workingDir);
+        if (platformDir != null) {
+            return platformDir;
+        }
+
+        // Test if workingDir is platform/frameworks/base/tools/layoutlib/bridge.
+        File currentDir = workingDir;
+        if (currentDir.getName().equalsIgnoreCase("bridge")) {
+            currentDir = currentDir.getParentFile();
+        }
+        // Test if currentDir is  platform/frameworks/base/tools/layoutlib. That is, root should be
+        // workingDir/../../../../  (4 levels up)
+        for (int i = 0; i < 4; i++) {
+            if (currentDir != null) {
+                currentDir = currentDir.getParentFile();
+            }
+        }
+        return currentDir == null ? null : getPlatformDirFromRoot(currentDir);
+    }
+
+    private static String getPlatformDirFromRoot(File root) {
+        if (!root.isDirectory()) {
+            return null;
+        }
+        File out = new File(root, "out");
+        if (!out.isDirectory()) {
+            return null;
+        }
+        File host = new File(out, "host");
+        if (!host.isDirectory()) {
+            return null;
+        }
+        File[] hosts = host.listFiles(path -> path.isDirectory() &&
+                (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
+        assert hosts != null;
+        for (File hostOut : hosts) {
+            String platformDir = getPlatformDirFromHostOut(hostOut);
+            if (platformDir != null) {
+                return platformDir;
+            }
+        }
+        return null;
+    }
+
+    private static String getPlatformDirFromHostOut(File out) {
+        if (!out.isDirectory()) {
+            return null;
+        }
+        File sdkDir = new File(out, "sdk");
+        if (!sdkDir.isDirectory()) {
+            return null;
+        }
+        File[] sdkDirs = sdkDir.listFiles(path -> {
+            // We need to search for $TARGET_PRODUCT (usually, sdk_phone_armv7)
+            return path.isDirectory() && path.getName().startsWith("sdk");
+        });
+        assert sdkDirs != null;
+        for (File dir : sdkDirs) {
+            String platformDir = getPlatformDirFromHostOutSdkSdk(dir);
+            if (platformDir != null) {
+                return platformDir;
+            }
+        }
+        return null;
+    }
+
+    private static String getPlatformDirFromHostOutSdkSdk(File sdkDir) {
+        File[] possibleSdks = sdkDir.listFiles(
+                path -> path.isDirectory() && path.getName().contains("android-sdk"));
+        assert possibleSdks != null;
+        for (File possibleSdk : possibleSdks) {
+            File platformsDir = new File(possibleSdk, "platforms");
+            File[] platforms = platformsDir.listFiles(
+                    path -> path.isDirectory() && path.getName().startsWith("android-"));
+            if (platforms == null || platforms.length == 0) {
+                continue;
+            }
+            Arrays.sort(platforms, (o1, o2) -> {
+                final int MAX_VALUE = 1000;
+                String suffix1 = o1.getName().substring("android-".length());
+                String suffix2 = o2.getName().substring("android-".length());
+                int suff1, suff2;
+                try {
+                    suff1 = Integer.parseInt(suffix1);
+                } catch (NumberFormatException e) {
+                    suff1 = MAX_VALUE;
+                }
+                try {
+                    suff2 = Integer.parseInt(suffix2);
+                } catch (NumberFormatException e) {
+                    suff2 = MAX_VALUE;
+                }
+                if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) {
+                    return suff2 - suff1;
+                }
+                return suffix2.compareTo(suffix1);
+            });
+            return platforms[0].getAbsolutePath();
+        }
+        return null;
+    }
+
+    private static String getTestResDir() {
+        String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY);
+        if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) {
+            return resourceDir;
+        }
+        // TEST_RES_DIR not explicitly set. Fallback to the class's source location.
+        try {
+            URL location = RenderTestBase.class.getProtectionDomain().getCodeSource().getLocation();
+            return new File(location.getPath()).exists() ? location.getPath() : null;
+        } catch (NullPointerException e) {
+            // Prevent a lot of null checks by just catching the exception.
+            return null;
+        }
+    }
+
+    /**
+     * Initialize the bridge and the resource maps.
+     */
+    @BeforeClass
+    public static void beforeClass() {
+        File data_dir = new File(PLATFORM_DIR, "data");
+        File res = new File(data_dir, "res");
+        sFrameworkRepo = new FrameworkResources(new FolderWrapper(res));
+        sFrameworkRepo.loadResources();
+        sFrameworkRepo.loadPublicResources(getLogger());
+
+        sProjectResources =
+                new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
+                        false) {
+                    @NonNull
+                    @Override
+                    protected ResourceItem createResourceItem(@NonNull String name) {
+                        return new ResourceItem(name);
+                    }
+                };
+        sProjectResources.loadResources();
+
+        File fontLocation = new File(data_dir, "fonts");
+        File buildProp = new File(PLATFORM_DIR, "build.prop");
+        File attrs = new File(res, "values" + File.separator + "attrs.xml");
+        sBridge = new Bridge();
+        sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+                ConfigGenerator.getEnumMap(attrs), getLayoutLog());
+        Bridge.getLock().lock();
+        try {
+            Bridge.setLog(getLayoutLog());
+        } finally {
+            Bridge.getLock().unlock();
+        }
+    }
+
+    @AfterClass
+    public static void tearDown() {
+        sLayoutLibLog = null;
+        sFrameworkRepo = null;
+        sProjectResources = null;
+        sLogger = null;
+        sBridge = null;
+
+        TestUtils.gc();
+
+        System.out.println("Objects still linked from the DelegateManager:");
+        DelegateManager.dump(System.out);
+    }
+
+    @NonNull
+    protected static RenderResult render(SessionParams params, long frameTimeNanos) {
+        // TODO: Set up action bar handler properly to test menu rendering.
+        // Create session params.
+        System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
+        System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
+        RenderSession session = sBridge.createSession(params);
+
+        try {
+
+            if (frameTimeNanos != -1) {
+                session.setElapsedFrameTimeNanos(frameTimeNanos);
+            }
+
+            if (!session.getResult().isSuccess()) {
+                getLogger().error(session.getResult().getException(),
+                        session.getResult().getErrorMessage());
+            }
+            // Render the session with a timeout of 50s.
+            Result renderResult = session.render(50000);
+            if (!renderResult.isSuccess()) {
+                getLogger().error(session.getResult().getException(),
+                        session.getResult().getErrorMessage());
+            }
+
+            return RenderResult.getFromSession(session);
+        } finally {
+            session.dispose();
+        }
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout doesn't throw any
+     * exceptions and matches the provided image.
+     * <p>
+     * If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time indicates
+     * how far in the future is.
+     */
+    @Nullable
+    protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName,
+            long frameTimeNanos) throws ClassNotFoundException {
+        RenderResult result = RenderTestBase.render(params, frameTimeNanos);
+        try {
+            String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
+            assertNotNull(result.getImage());
+            ImageUtils.requireSimilar(goldenImagePath, result.getImage());
+        } catch (IOException e) {
+            getLogger().error(e, e.getMessage());
+        }
+
+        return result;
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout doesn't throw any
+     * exceptions and matches the provided image.
+     */
+    @Nullable
+    protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName)
+            throws ClassNotFoundException {
+        return RenderTestBase.renderAndVerify(params, goldenFileName, -1);
+    }
+
+    private static LayoutLog getLayoutLog() {
+        if (sLayoutLibLog == null) {
+            sLayoutLibLog = new LayoutLog() {
+                @Override
+                public void warning(String tag, String message, Object data) {
+                    System.out.println("Warning " + tag + ": " + message);
+                    failWithMsg(message);
+                }
+
+                @Override
+                public void fidelityWarning(@Nullable String tag, String message,
+                        Throwable throwable, Object data) {
+
+                    System.out.println("FidelityWarning " + tag + ": " + message);
+                    if (throwable != null) {
+                        throwable.printStackTrace();
+                    }
+                    failWithMsg(message == null ? "" : message);
+                }
+
+                @Override
+                public void error(String tag, String message, Object data) {
+                    System.out.println("Error " + tag + ": " + message);
+                    failWithMsg(message);
+                }
+
+                @Override
+                public void error(String tag, String message, Throwable throwable, Object data) {
+                    System.out.println("Error " + tag + ": " + message);
+                    if (throwable != null) {
+                        throwable.printStackTrace();
+                    }
+                    failWithMsg(message);
+                }
+            };
+        }
+        return sLayoutLibLog;
+    }
+
+    protected static void ignoreAllLogging() {
+        sLayoutLibLog = new LayoutLog();
+        sLogger = new ILogger() {
+            @Override
+            public void error(Throwable t, String msgFormat, Object... args) {
+            }
+
+            @Override
+            public void warning(String msgFormat, Object... args) {
+            }
+
+            @Override
+            public void info(String msgFormat, Object... args) {
+            }
+
+            @Override
+            public void verbose(String msgFormat, Object... args) {
+            }
+        };
+    }
+
+    protected static ILogger getLogger() {
+        if (sLogger == null) {
+            sLogger = new ILogger() {
+                @Override
+                public void error(Throwable t, @Nullable String msgFormat, Object... args) {
+                    if (t != null) {
+                        t.printStackTrace();
+                    }
+                    failWithMsg(msgFormat == null ? "" : msgFormat, args);
+                }
+
+                @Override
+                public void warning(@NonNull String msgFormat, Object... args) {
+                    failWithMsg(msgFormat, args);
+                }
+
+                @Override
+                public void info(@NonNull String msgFormat, Object... args) {
+                    // pass.
+                }
+
+                @Override
+                public void verbose(@NonNull String msgFormat, Object... args) {
+                    // pass.
+                }
+            };
+        }
+        return sLogger;
+    }
+
+    private static void failWithMsg(@NonNull String msgFormat, Object... args) {
+        sRenderMessages.add(args == null ? msgFormat : String.format(msgFormat, args));
+    }
+
+    @Before
+    public void beforeTestCase() {
+        sRenderMessages.clear();
+    }
+
+    @NonNull
+    protected LayoutPullParser createLayoutPullParser(String layoutPath) {
+        return new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutPath);
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout on nexus 5
+     * doesn't throw any exceptions and matches the provided image.
+     */
+    @Nullable
+    protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
+            throws ClassNotFoundException {
+        return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
+    }
+
+    /**
+     * Create a new rendering session and test that rendering the given layout on given device
+     * doesn't throw any exceptions and matches the provided image.
+     */
+    @Nullable
+    protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
+            ConfigGenerator deviceConfig) throws ClassNotFoundException {
+        SessionParams params = createSessionParams(layoutFileName, deviceConfig);
+        return renderAndVerify(params, goldenFileName);
+    }
+
+    protected SessionParams createSessionParams(String layoutFileName, ConfigGenerator deviceConfig)
+            throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser(layoutFileName);
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        // TODO: Set up action bar handler properly to test menu rendering.
+        // Create session params.
+        return getSessionParams(parser, deviceConfig, layoutLibCallback, "AppTheme", true,
+                RenderingMode.NORMAL, 22);
+    }
+
+    /**
+     * Uses Theme.Material and Target sdk version as 22.
+     */
+    protected SessionParams getSessionParams(LayoutPullParser layoutParser,
+            ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
+            String themeName, boolean isProjectTheme, RenderingMode renderingMode,
+            @SuppressWarnings("SameParameterValue") int targetSdk) {
+        FolderConfiguration config = configGenerator.getFolderConfig();
+        ResourceResolver resourceResolver =
+                ResourceResolver.create(sProjectResources.getConfiguredResources(config),
+                        sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme);
+
+        SessionParams sessionParams =
+                new SessionParams(layoutParser, renderingMode, null /*used for caching*/,
+                        configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
+                        targetSdk, getLayoutLog());
+        sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
+        return sessionParams;
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
new file mode 100644
index 0000000..73e51ec
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.resources.Density;
+import com.android.resources.Navigation;
+import com.android.resources.ResourceType;
+
+import org.junit.Test;
+
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.DisplayMetrics;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Set of render tests
+ */
+public class RenderTests extends RenderTestBase {
+
+    @Test
+    public void testActivity() throws ClassNotFoundException {
+        renderAndVerify("activity.xml", "activity.png");
+    }
+
+    @Test
+    public void testActivityOnOldTheme() throws ClassNotFoundException {
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.NoTitleBar", false,
+                RenderingMode.NORMAL, 22);
+
+        renderAndVerify(params, "simple_activity-old-theme.png");
+    }
+
+    @Test
+    public void testTranslucentBars() throws ClassNotFoundException {
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        LayoutPullParser parser = createLayoutPullParser("four_corners.xml");
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
+                RenderingMode.NORMAL, 22);
+        renderAndVerify(params, "four_corners_translucent.png");
+
+        parser = createLayoutPullParser("four_corners.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
+                RenderingMode.NORMAL, 22);
+        renderAndVerify(params, "four_corners_translucent_land.png");
+
+        parser = createLayoutPullParser("four_corners.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.NORMAL, 22);
+        renderAndVerify(params, "four_corners.png");
+    }
+
+    @Test
+    public void testAllWidgets() throws ClassNotFoundException {
+        renderAndVerify("allwidgets.xml", "allwidgets.png");
+
+        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+    }
+
+    @Test
+    public void testArrayCheck() throws ClassNotFoundException {
+        renderAndVerify("array_check.xml", "array_check.png");
+    }
+
+    @Test
+    public void testAllWidgetsTablet() throws ClassNotFoundException {
+        renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
+
+        // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+        sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+    }
+
+    @Test
+    public void testActivityActionBar() throws ClassNotFoundException {
+        LayoutPullParser parser = createLayoutPullParser("simple_activity.xml");
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "simple_activity_noactionbar.png");
+
+        parser = createLayoutPullParser("simple_activity.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "simple_activity.png");
+
+        // This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
+        // displaying menus.
+        parser = createLayoutPullParser("simple_activity.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.V_SCROLL, 22);
+        params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
+        renderAndVerify(params, "simple_activity.png");
+    }
+
+    @Test
+    public void testOnApplyInsetsCall()
+            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
+        // We get the widget via reflection to avoid IntelliJ complaining about the class being
+        // located in the wrong package. (From the Bridge tests point of view, it is)
+        Class insetsWidgetClass = Class.forName("com.android.layoutlib.test.myapplication.widgets" +
+                ".InsetsWidget");
+        Field field = insetsWidgetClass.getDeclaredField("sApplyInsetsCalled");
+        assertFalse((Boolean)field.get(null));
+
+        LayoutPullParser parser = createLayoutPullParser("insets.xml");
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
+                RenderingMode.NORMAL, 22);
+
+        render(params, -1);
+
+        assertTrue((Boolean)field.get(null));
+        field.set(null, false);
+    }
+
+    /** Test expand_layout.xml */
+    @Test
+    public void testExpand() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("expand_vert_layout.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        ConfigGenerator customConfigGenerator = new ConfigGenerator()
+                .setScreenWidth(300)
+                .setScreenHeight(20)
+                .setDensity(Density.XHIGH)
+                .setNavigation(Navigation.NONAV);
+
+        SessionParams params = getSessionParams(parser, customConfigGenerator,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "expand_vert_layout.png");
+
+        customConfigGenerator = new ConfigGenerator()
+                .setScreenWidth(20)
+                .setScreenHeight(300)
+                .setDensity(Density.XHIGH)
+                .setNavigation(Navigation.NONAV);
+        parser = createLayoutPullParser("expand_horz_layout.xml");
+        params = getSessionParams(parser, customConfigGenerator,
+                layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
+                RenderingMode.H_SCROLL, 22);
+
+        renderAndVerify(params, "expand_horz_layout.png");
+    }
+
+    /** Test indeterminate_progressbar.xml */
+    @Test
+    public void testVectorAnimation() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("indeterminate_progressbar.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
+
+        parser = createLayoutPullParser("indeterminate_progressbar.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
+    }
+
+    /**
+     * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
+     * for vector drawables (lines, moves and cubic and quadratic curves).
+     */
+    @Test
+    public void testVectorDrawable() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("vector_drawable.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
+    }
+
+    /**
+     * Regression test for http://b.android.com/91383 and http://b.android.com/203797
+     */
+    @Test
+    public void testVectorDrawable91383() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("vector_drawable_android.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
+    }
+
+    /** Test activity.xml */
+    @Test
+    public void testScrollingAndMeasure() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = createLayoutPullParser("scrolled.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        params.setForceNoDecor();
+        params.setExtendedViewInfoMode(true);
+
+        // Do an only-measure pass
+        RenderSession session = sBridge.createSession(params);
+        session.measure();
+        RenderResult result = RenderResult.getFromSession(session);
+        assertNotNull(result);
+        assertNotNull(result.getResult());
+        assertTrue(result.getResult().isSuccess());
+
+        ViewInfo rootLayout = result.getRootViews().get(0);
+        // Check the first box in the main LinearLayout
+        assertEquals(-90, rootLayout.getChildren().get(0).getTop());
+        assertEquals(-30, rootLayout.getChildren().get(0).getLeft());
+        assertEquals(90, rootLayout.getChildren().get(0).getBottom());
+        assertEquals(150, rootLayout.getChildren().get(0).getRight());
+
+        // Check the first box within the nested LinearLayout
+        assertEquals(-450, rootLayout.getChildren().get(5).getChildren().get(0).getTop());
+        assertEquals(90, rootLayout.getChildren().get(5).getChildren().get(0).getLeft());
+        assertEquals(-270, rootLayout.getChildren().get(5).getChildren().get(0).getBottom());
+        assertEquals(690, rootLayout.getChildren().get(5).getChildren().get(0).getRight());
+
+        // Do a full render pass
+        parser = createLayoutPullParser("scrolled.xml");
+
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        params.setForceNoDecor();
+        params.setExtendedViewInfoMode(true);
+
+        result = renderAndVerify(params, "scrolled.png");
+        assertNotNull(result);
+        assertNotNull(result.getResult());
+        assertTrue(result.getResult().isSuccess());
+    }
+
+    @Test
+    public void testGetResourceNameVariants() throws Exception {
+        // Setup
+        // Create the layout pull parser for our resources (empty.xml can not be part of the test
+        // app as it won't compile).
+        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        AssetManager assetManager = AssetManager.getSystem();
+        DisplayMetrics metrics = new DisplayMetrics();
+        Configuration configuration = RenderAction.getConfiguration(params);
+        Resources resources = new Resources(assetManager, metrics, configuration);
+        resources.mLayoutlibCallback = params.getLayoutlibCallback();
+        resources.mContext =
+                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+                        params.getAssets(), params.getLayoutlibCallback(), configuration,
+                        params.getTargetSdkVersion(), params.isRtlSupported());
+        // Test
+        assertEquals("android:style/ButtonBar",
+                resources.getResourceName(android.R.style.ButtonBar));
+        assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
+        assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
+        assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
+        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name");
+        assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
+                resources.getResourceName(id));
+        assertEquals("com.android.layoutlib.test.myapplication",
+                resources.getResourcePackageName(id));
+        assertEquals("string", resources.getResourceTypeName(id));
+        assertEquals("app_name", resources.getResourceEntryName(id));
+    }
+
+    @Test
+    public void testStringEscaping() throws Exception {
+        // Setup
+        // Create the layout pull parser for our resources (empty.xml can not be part of the test
+        // app as it won't compile).
+        LayoutPullParser parser = new LayoutPullParser("/empty.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
+                layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+        AssetManager assetManager = AssetManager.getSystem();
+        DisplayMetrics metrics = new DisplayMetrics();
+        Configuration configuration = RenderAction.getConfiguration(params);
+        Resources resources = new Resources(assetManager, metrics, configuration);
+        resources.mLayoutlibCallback = params.getLayoutlibCallback();
+        resources.mContext =
+                new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+                        params.getAssets(), params.getLayoutlibCallback(), configuration,
+                        params.getTargetSdkVersion(), params.isRtlSupported());
+
+        int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array");
+        String[] strings = resources.getStringArray(id);
+        assertArrayEquals(
+                new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
+                strings);
+        assertTrue(sRenderMessages.isEmpty());
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
similarity index 99%
rename from tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
rename to tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
index d3f0f89..18c6629 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge.intensive;
+package com.android.layoutlib.bridge.intensive.util;
 
 import android.annotation.NonNull;
 
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java
similarity index 97%
rename from tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java
rename to tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java
index 3fac778..da360f3 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ModuleClassLoader.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge.intensive;
+package com.android.layoutlib.bridge.intensive.util;
 
 import java.io.IOException;
 import java.util.HashMap;
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java
new file mode 100644
index 0000000..1df8e79
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util;
+
+import java.lang.ref.WeakReference;
+
+public class TestUtils {
+    public static void gc() {
+        // See RuntimeUtil#gc in jlibs (http://jlibs.in/)
+        Object obj = new Object();
+        WeakReference ref = new WeakReference<>(obj);
+        //noinspection UnusedAssignment
+        obj = null;
+        while (ref.get() != null) {
+            System.gc();
+            System.runFinalization();
+        }
+
+        System.gc();
+        System.runFinalization();
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java
new file mode 100644
index 0000000..ee98b4b
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/LongStatsCollector.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import android.annotation.NonNull;
+import android.util.LongArray;
+
+import java.util.Arrays;
+import java.util.function.LongConsumer;
+
+/**
+ * Class that collect a series of longs and produces the median, min and max values.
+ */
+public class LongStatsCollector implements LongConsumer {
+    private final LongArray mAllValues;
+    private long mMin = Long.MAX_VALUE;
+    private long mMax = Long.MIN_VALUE;
+    public LongStatsCollector(int estimatedRuns) {
+        mAllValues = new LongArray(estimatedRuns);
+    }
+
+    public int size() {
+        return mAllValues.size();
+    }
+
+    @NonNull
+    public Stats getStats() {
+        if (mAllValues.size() == 0) {
+            throw new IndexOutOfBoundsException("No data");
+        }
+
+        double median;
+        int size = mAllValues.size();
+        long[] buffer = new long[size];
+        for (int i = 0; i < size; i++) {
+            buffer[i] = mAllValues.get(i);
+        }
+
+        Arrays.sort(buffer);
+
+        int midPoint = size / 2;
+        median = (size % 2 == 0) ? (buffer[midPoint - 1] + buffer[midPoint]) / 2 : buffer[midPoint];
+
+        return new Stats(mAllValues.size(), mMin, mMax, median);
+    }
+
+    @Override
+    public void accept(long value) {
+        mMin = Math.min(mMin, value);
+        mMax = Math.max(mMax, value);
+        mAllValues.add(value);
+    }
+
+    public static class Stats {
+        private final int mSamples;
+        private final long mMin;
+        private final long mMax;
+        private final double mMedian;
+
+        private Stats(int samples, long min, long max, double median) {
+            mSamples = samples;
+            mMin = min;
+            mMax = max;
+            mMedian = median;
+        }
+
+        public int getSampleCount() {
+            return mSamples;
+        }
+
+        public long getMin() {
+            return mMin;
+        }
+
+        public long getMax() {
+            return mMax;
+        }
+
+        public double getMedian() {
+            return mMedian;
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java
new file mode 100644
index 0000000..7225a10
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/PerformanceRunner.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * JUnit {@link Runner} that times the test execution and produces some stats.
+ */
+public class PerformanceRunner extends BlockJUnit4ClassRunner {
+    private static final int DEFAULT_WARMUP_ITERATIONS = 50;
+    private static final int DEFAULT_RUNS = 100;
+
+    private final int mWarmUpIterations;
+    private final int mRuns;
+
+    public PerformanceRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+
+        Configuration classConfig = testClass.getAnnotation(Configuration.class);
+        mWarmUpIterations = classConfig != null && classConfig.warmUpIterations() != -1 ?
+                classConfig.warmUpIterations() :
+                DEFAULT_WARMUP_ITERATIONS;
+        mRuns = classConfig != null && classConfig.runs() != -1 ?
+                classConfig.runs() :
+                DEFAULT_RUNS;
+    }
+
+    @Override
+    protected Statement methodInvoker(FrameworkMethod method, Object test) {
+        int warmUpIterations;
+        int runs;
+
+        Configuration methodConfig = method.getAnnotation(Configuration.class);
+        warmUpIterations = methodConfig != null && methodConfig.warmUpIterations() != -1 ?
+                methodConfig.warmUpIterations() :
+                mWarmUpIterations;
+        runs = methodConfig != null && methodConfig.runs() != -1 ?
+                methodConfig.runs() :
+                mRuns;
+        return new TimedStatement(super.methodInvoker(method, test), warmUpIterations, runs,
+                (result) -> System.out.println(result.toString()));
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        super.run(notifier);
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Inherited
+    public @interface Configuration {
+        int warmUpIterations() default -1;
+
+        int runs() default -1;
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java
new file mode 100644
index 0000000..77a2b0e
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatement.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import com.android.layoutlib.bridge.intensive.util.TestUtils;
+
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+
+/**
+ * JUnit {@link Statement} used to measure some statistics about the test method.
+ */
+public class TimedStatement extends Statement {
+    private static final int CALIBRATION_WARMUP_ITERATIONS = 50;
+    private static final int CALIBRATION_RUNS = 100;
+
+    private static boolean sIsCalibrated;
+    private static double sCalibrated;
+
+    private final Statement mStatement;
+    private final int mWarmUpIterations;
+    private final int mRuns;
+    private final Runtime mRuntime = Runtime.getRuntime();
+    private final Consumer<TimedStatementResult> mCallback;
+
+    TimedStatement(Statement statement, int warmUpIterations, int runs,
+            Consumer<TimedStatementResult> finishedCallback) {
+        mStatement = statement;
+        mWarmUpIterations = warmUpIterations;
+        mRuns = runs;
+        mCallback = finishedCallback;
+    }
+
+    /**
+     * The calibrate method tries to do some work that involves IO, memory allocations and some
+     * operations on the randomly generated data to calibrate the speed of the machine with
+     * something that resembles the execution of a test case.
+     */
+    private static void calibrateMethod() throws IOException {
+        File tmpFile = File.createTempFile("test", "file");
+        Random rnd = new Random();
+        HashFunction hashFunction = Hashing.sha512();
+        for (int i = 0; i < 5 + rnd.nextInt(5); i++) {
+            FileOutputStream stream = new FileOutputStream(tmpFile);
+            int bytes = 30000 + rnd.nextInt(60000);
+            byte[] buffer = new byte[bytes];
+
+            rnd.nextBytes(buffer);
+            byte acc = 0;
+            for (int j = 0; j < bytes; j++) {
+                acc += buffer[i];
+            }
+            buffer[0] = acc;
+            stream.write(buffer);
+            System.gc();
+            stream.close();
+            FileInputStream input = new FileInputStream(tmpFile);
+            byte[] readBuffer = new byte[bytes];
+            //noinspection ResultOfMethodCallIgnored
+            input.read(readBuffer);
+            buffer = readBuffer;
+            HashCode code1 = hashFunction.hashBytes(buffer);
+            Arrays.sort(buffer);
+            HashCode code2 = hashFunction.hashBytes(buffer);
+            input.close();
+
+            FileOutputStream hashStream = new FileOutputStream(tmpFile);
+            hashStream.write(code1.asBytes());
+            hashStream.write(code2.asBytes());
+            hashStream.close();
+        }
+    }
+
+    /**
+     * Runs the calibration process and sets the calibration measure in {@link #sCalibrated}
+     */
+    private static void doCalibration() throws IOException {
+        System.out.println("Calibrating ...");
+        TestUtils.gc();
+        for (int i = 0; i < CALIBRATION_WARMUP_ITERATIONS; i++) {
+            calibrateMethod();
+        }
+
+        LongStatsCollector stats = new LongStatsCollector(CALIBRATION_RUNS);
+        for (int i = 0; i < CALIBRATION_RUNS; i++) {
+            TestUtils.gc();
+            long start = System.currentTimeMillis();
+            calibrateMethod();
+            stats.accept(System.currentTimeMillis() - start);
+        }
+
+        sCalibrated = stats.getStats().getMedian();
+        sIsCalibrated = true;
+        System.out.printf("  DONE %fms\n", sCalibrated);
+    }
+
+    private long getUsedMemory() {
+        return mRuntime.totalMemory() - mRuntime.freeMemory();
+    }
+
+
+    @Override
+    public void evaluate() throws Throwable {
+        if (!sIsCalibrated) {
+            doCalibration();
+        }
+
+        for (int i = 0; i < mWarmUpIterations; i++) {
+            mStatement.evaluate();
+        }
+
+        LongStatsCollector timeStats = new LongStatsCollector(mRuns);
+        LongStatsCollector memoryUseStats = new LongStatsCollector(mRuns);
+        AtomicBoolean collectSamples = new AtomicBoolean(false);
+
+        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
+        TestUtils.gc();
+        executorService.scheduleAtFixedRate(() -> {
+            if (!collectSamples.get()) {
+                return;
+            }
+            memoryUseStats.accept(getUsedMemory());
+        }, 0, 200, TimeUnit.MILLISECONDS);
+
+        try {
+            for (int i = 0; i < mRuns; i++) {
+                TestUtils.gc();
+                collectSamples.set(true);
+                long startTimeMs = System.currentTimeMillis();
+                mStatement.evaluate();
+                long stopTimeMs = System.currentTimeMillis();
+                collectSamples.set(true);
+                timeStats.accept(stopTimeMs - startTimeMs);
+
+            }
+        } finally {
+            executorService.shutdownNow();
+        }
+
+        TimedStatementResult result = new TimedStatementResult(
+                mWarmUpIterations,
+                mRuns,
+                sCalibrated,
+                timeStats.getStats(),
+                memoryUseStats.getStats());
+        mCallback.accept(result);
+    }
+
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java
new file mode 100644
index 0000000..59f90d2
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/perf/TimedStatementResult.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive.util.perf;
+
+import com.android.layoutlib.bridge.intensive.util.perf.LongStatsCollector.Stats;
+
+import java.text.DecimalFormat;
+
+/**
+ * Result value of a {@link TimedStatement}
+ */
+public class TimedStatementResult {
+    private static final DecimalFormat UNITS_FORMAT = new DecimalFormat("#.##");
+
+    private final int mWarmUpIterations;
+    private final int mRuns;
+    private final double mCalibrationTimeMs;
+    private final Stats mTimeStats;
+    private final Stats mMemoryStats;
+
+    TimedStatementResult(int warmUpIterations, int runs,
+            double calibrationTimeMs,
+            Stats timeStats,
+            Stats memoryStats) {
+        mWarmUpIterations = warmUpIterations;
+        mRuns = runs;
+        mCalibrationTimeMs = calibrationTimeMs;
+        mTimeStats = timeStats;
+        mMemoryStats = memoryStats;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "Warm up %d. Runs %d\n" + "Time:             %s ms (min: %s, max %s)\n" +
+                        "Calibration Time: %f ms\n" +
+                        "Calibrated Time:  %s units (min: %s, max %s)\n" +
+                        "Sampled %d times\n" +
+                        "   Memory used:  %d bytes (max %d)\n\n",
+                mWarmUpIterations, mRuns,
+                mTimeStats.getMedian(), mTimeStats.getMin(), mTimeStats.getMax(),
+                mCalibrationTimeMs,
+                UNITS_FORMAT.format((mTimeStats.getMedian() / mCalibrationTimeMs) * 100000),
+                UNITS_FORMAT.format((mTimeStats.getMin() / mCalibrationTimeMs) * 100000),
+                UNITS_FORMAT.format((mTimeStats.getMax() / mCalibrationTimeMs) * 100000),
+                mMemoryStats.getSampleCount(),
+                (long)mMemoryStats.getMedian() - mMemoryStats.getMin(),
+                mMemoryStats.getMax() - mMemoryStats.getMin());
+    }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index b6aaac8..741eb27 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -154,6 +154,8 @@
         "android.content.res.Resources#getIntArray",
         "android.content.res.Resources#getInteger",
         "android.content.res.Resources#getLayout",
+        "android.content.res.Resources#getQuantityString",
+        "android.content.res.Resources#getQuantityText",
         "android.content.res.Resources#getResourceEntryName",
         "android.content.res.Resources#getResourceName",
         "android.content.res.Resources#getResourcePackageName",
@@ -232,6 +234,7 @@
         "android.view.RenderNode#nSetScaleY",
         "android.view.RenderNode#nGetScaleY",
         "android.view.RenderNode#nIsPivotExplicitlySet",
+        "android.view.PointerIcon#loadResource",
         "android.view.ViewGroup#drawChild",
         "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
         "com.android.internal.util.XmlUtils#convertValueToInt",