Merge "Add SilkFX demo app"
diff --git a/apct-tests/perftests/textclassifier/Android.bp b/apct-tests/perftests/textclassifier/Android.bp
index 9f795a7..c40e025 100644
--- a/apct-tests/perftests/textclassifier/Android.bp
+++ b/apct-tests/perftests/textclassifier/Android.bp
@@ -19,7 +19,7 @@
         "androidx.test.rules",
         "androidx.annotation_annotation",
         "apct-perftests-utils",
-        "collector-device-lib-platform",
+        "collector-device-lib",
     ],
     data: [":perfetto_artifacts"],
     platform_apis: true,
diff --git a/api/system-current.txt b/api/system-current.txt
index 56059dd..88fe40a 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12732,6 +12732,9 @@
   public interface PacProcessor {
     method @Nullable public String findProxyForUrl(@NonNull String);
     method @NonNull public static android.webkit.PacProcessor getInstance();
+    method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(long);
+    method public default long getNetworkHandle();
+    method public default void releasePacProcessor();
     method public boolean setProxyScript(@NonNull String);
   }
 
@@ -12871,6 +12874,7 @@
     method public android.webkit.CookieManager getCookieManager();
     method public android.webkit.GeolocationPermissions getGeolocationPermissions();
     method @NonNull public default android.webkit.PacProcessor getPacProcessor();
+    method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(long);
     method public android.webkit.ServiceWorkerController getServiceWorkerController();
     method public android.webkit.WebViewFactoryProvider.Statics getStatics();
     method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index ed717c4..4b7eda0 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.UserIdInt;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupManager.OperationType;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupProgress;
 import android.app.backup.BackupTransport;
@@ -666,7 +667,7 @@
 
         // The rest of the 'list' options work with a restore session on the current transport
         try {
-            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
+            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP);
             if (mRestore == null) {
                 System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
@@ -821,7 +822,7 @@
 
         try {
             boolean didRestore = false;
-            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null);
+            mRestore = mBmgr.beginRestoreSessionForUser(userId, null, null, OperationType.BACKUP);
             if (mRestore == null) {
                 System.err.println(BMGR_ERR_NO_RESTORESESSION_FOR_USER + userId);
                 return;
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index b1a62bf..9b67587 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -355,7 +355,36 @@
             try {
                 // All packages, current transport
                 IRestoreSession binder =
-                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null);
+                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null,
+                                OperationType.BACKUP);
+                if (binder != null) {
+                    session = new RestoreSession(mContext, binder);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "beginRestoreSession() couldn't connect");
+            }
+        }
+        return session;
+    }
+
+    /**
+     * Begin the process of restoring data from backup.  See the
+     * {@link android.app.backup.RestoreSession} class for documentation on that process.
+     *
+     * @param operationType Type of the operation, see {@link OperationType}
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BACKUP)
+    public RestoreSession beginRestoreSession(@OperationType int operationType) {
+        RestoreSession session = null;
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                // All packages, current transport
+                IRestoreSession binder =
+                        sService.beginRestoreSessionForUser(mContext.getUserId(), null, null,
+                                operationType);
                 if (binder != null) {
                     session = new RestoreSession(mContext, binder);
                 }
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 96b5dd5..e177a74 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -547,9 +547,11 @@
      *        set can be restored.
      * @param transportID The name of the transport to use for the restore operation.
      *        May be null, in which case the current active transport is used.
+     * @param operationType Type of the operation, see {@link BackupManager#OperationType}
      * @return An interface to the restore session, or null on error.
      */
-    IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID);
+    IRestoreSession beginRestoreSessionForUser(int userId, String packageName, String transportID,
+            int operationType);
 
     /**
      * Notify the backup manager that a BackupAgent has completed the operation
diff --git a/core/java/android/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java
new file mode 100644
index 0000000..39c5c85
--- /dev/null
+++ b/core/java/android/app/people/ConversationChannel.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.app.people;
+
+import android.app.NotificationChannel;
+import android.content.pm.ShortcutInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The non-customized notification channel of a conversation. It contains the information to render
+ * the conversation and allows the user to open and customize the conversation setting.
+ *
+ * @hide
+ */
+public final class ConversationChannel implements Parcelable {
+
+    private ShortcutInfo mShortcutInfo;
+    private NotificationChannel mParentNotificationChannel;
+    private long mLastEventTimestamp;
+    private boolean mHasActiveNotifications;
+
+    public static final Creator<ConversationChannel> CREATOR = new Creator<ConversationChannel>() {
+        @Override
+        public ConversationChannel createFromParcel(Parcel in) {
+            return new ConversationChannel(in);
+        }
+
+        @Override
+        public ConversationChannel[] newArray(int size) {
+            return new ConversationChannel[size];
+        }
+    };
+
+    public ConversationChannel(ShortcutInfo shortcutInfo,
+            NotificationChannel parentNotificationChannel, long lastEventTimestamp,
+            boolean hasActiveNotifications) {
+        mShortcutInfo = shortcutInfo;
+        mParentNotificationChannel = parentNotificationChannel;
+        mLastEventTimestamp = lastEventTimestamp;
+        mHasActiveNotifications = hasActiveNotifications;
+    }
+
+    public ConversationChannel(Parcel in) {
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mParentNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mLastEventTimestamp = in.readLong();
+        mHasActiveNotifications = in.readBoolean();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mShortcutInfo, flags);
+        dest.writeParcelable(mParentNotificationChannel, flags);
+        dest.writeLong(mLastEventTimestamp);
+        dest.writeBoolean(mHasActiveNotifications);
+    }
+
+    public ShortcutInfo getShortcutInfo() {
+        return mShortcutInfo;
+    }
+
+    public NotificationChannel getParentNotificationChannel() {
+        return mParentNotificationChannel;
+    }
+
+    public long getLastEventTimestamp() {
+        return mLastEventTimestamp;
+    }
+
+    /**
+     * Whether this conversation has any active notifications. If it's true, the shortcut for this
+     * conversation can't be uncached until all its active notifications are dismissed.
+     */
+    public boolean hasActiveNotifications() {
+        return mHasActiveNotifications;
+    }
+}
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
new file mode 100644
index 0000000..61dac0d
--- /dev/null
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.app.people;
+
+import android.content.pm.ParceledListSlice;
+import android.net.Uri;
+import android.os.IBinder;
+
+/**
+ * System private API for talking with the people service.
+ * {@hide}
+ */
+interface IPeopleManager {
+    /**
+     * Returns the recent conversations. The conversations that have customized notification
+     * settings are excluded from the returned list.
+     */
+    ParceledListSlice getRecentConversations();
+
+    /**
+     * Removes the specified conversation from the recent conversations list and uncaches the
+     * shortcut associated with the conversation.
+     */
+    void removeRecentConversation(in String packageName, int userId, in String shortcutId);
+
+    /** Removes all the recent conversations and uncaches their cached shortcuts. */
+    void removeAllRecentConversations();
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 16cdf23..52b0467 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3496,6 +3496,7 @@
             //@hide: TIME_ZONE_DETECTOR_SERVICE,
             PERMISSION_SERVICE,
             LIGHTS_SERVICE,
+            //@hide: PEOPLE_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -5189,6 +5190,14 @@
     public static final String SMS_SERVICE = "sms";
 
     /**
+     * Use with {@link #getSystemService(String)} to access people service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String PEOPLE_SERVICE = "people";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c383bc7..7f45c04 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -618,16 +618,20 @@
             return false;
         }
         if (DEBUG) Log.d(TAG, "onStateChanged: " + state);
-        updateState(state);
-
-        boolean localStateChanged = !mState.equals(mLastDispatchedState,
-                true /* excludingCaptionInsets */, true /* excludeInvisibleIme */);
         mLastDispatchedState.set(state, true /* copySources */);
 
+        final InsetsState lastState = new InsetsState(mState, true /* copySources */);
+        updateState(state);
         applyLocalVisibilityOverride();
-        if (localStateChanged) {
-            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged, send state to WM: " + mState);
+
+        if (!mState.equals(lastState, true /* excludingCaptionInsets */,
+                true /* excludeInvisibleIme */)) {
+            if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
             mHost.notifyInsetsChanged();
+        }
+        if (!mState.equals(state, true /* excludingCaptionInsets */,
+                true /* excludeInvisibleIme */)) {
+            if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState);
             updateRequestedState();
         }
         return true;
diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
index eb67191..e814ec6 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -29,7 +29,7 @@
 oneway interface IWindowMagnificationConnection {
 
     /**
-     * Enables window magnification on specified display with given center and scale and animation.
+     * Enables window magnification on specifed display with specified center and scale.
      *
      * @param displayId The logical display id.
      * @param scale magnification scale.
@@ -41,7 +41,7 @@
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY);
 
     /**
-     * Sets the scale of the window magnifier on specified display.
+     * Sets the scale of the window magnifier on specifed display.
      *
      * @param displayId The logical display id.
      * @param scale magnification scale.
@@ -49,14 +49,14 @@
     void setScale(int displayId, float scale);
 
      /**
-     * Disables window magnification on specified display with animation.
+     * Disables window magnification on specifed display.
      *
      * @param displayId The logical display id.
      */
     void disableWindowMagnification(int displayId);
 
     /**
-     * Moves the window magnifier on the specified display. It has no effect while animating.
+     * Moves the window magnifier on the specifed display.
      *
      * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
      *                current screen pixels.
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
index 5ef450f..7e7b987 100644
--- a/core/java/android/webkit/PacProcessor.java
+++ b/core/java/android/webkit/PacProcessor.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-
+import android.net.Network;
 
 /**
  * Class to evaluate PAC scripts.
@@ -40,6 +40,20 @@
     }
 
     /**
+     * Returns PacProcessor instance associated with the {@link Network}.
+     * The host resolution is done on this {@link Network}.
+     *
+     * @param networkHandle a handle representing {@link Network} handle.
+     * @return PacProcessor instance for the specified network.
+     * @see Network#getNetworkHandle
+     * @see Network#fromNetworkHandle
+     */
+    @NonNull
+    static PacProcessor getInstanceForNetwork(long networkHandle) {
+        return WebViewFactory.getProvider().getPacProcessorForNetwork(networkHandle);
+    }
+
+    /**
      * Set PAC script to use.
      *
      * @param script PAC script.
@@ -55,4 +69,23 @@
      */
     @Nullable
     String findProxyForUrl(@NonNull String url);
+
+    /**
+     * Stops support for this {@link PacProcessor} and release its resources.
+     * No methods of this class must be called after calling this method.
+     */
+    default void releasePacProcessor() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
+     * Returns a network handle associated with this {@link PacProcessor}.
+     *
+     * @return a network handle or 0 if a network is unspecified.
+     * @see Network#getNetworkHandle
+     * @see Network#fromNetworkHandle
+     */
+    default long getNetworkHandle() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
 }
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index f7c3ec0..f1863e3 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -20,6 +20,7 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Network;
 import android.net.Uri;
 
 import java.util.List;
@@ -175,7 +176,7 @@
     WebViewDatabase getWebViewDatabase(Context context);
 
     /**
-     * Gets the singleton PacProcessor instance.
+     * Gets the default PacProcessor instance.
      * @return the PacProcessor instance
      */
     @NonNull
@@ -184,6 +185,20 @@
     }
 
     /**
+     * Returns PacProcessor instance associated with the {@link Network}.
+     * The host resolution is done on this {@link Network}.
+     *
+     * @param networkHandle a network handle representing the {@link Network}.
+     * @return the {@link PacProcessor} instance associated with {@link Network}.
+     * @see Network#getNetworkHandle
+     * @see Network#fromNetworkHandle
+     */
+    @NonNull
+    default PacProcessor getPacProcessorForNetwork(long networkHandle) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    /**
      * Gets the classloader used to load internal WebView implementation classes. This interface
      * should only be used by the WebView Support Library.
      */
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 801cd4d..af02b7b 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -27,6 +27,7 @@
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
@@ -40,8 +41,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -124,7 +128,7 @@
             }
             mTestClock = new OffsettableClock();
             mTestHandler = new TestHandler(null, mTestClock);
-            mTestHost = new TestHost(mViewRoot);
+            mTestHost = spy(new TestHost(mViewRoot));
             mController = new InsetsController(mTestHost, (controller, type) -> {
                 if (type == ITYPE_IME) {
                     return new InsetsSourceConsumer(type, controller.getState(),
@@ -745,6 +749,99 @@
         });
     }
 
+    @Test
+    public void testInsetsChangedCount_controlSystemBars() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            prepareControls();
+
+            // Hiding visible system bars should only causes insets change once for each bar.
+            clearInvocations(mTestHost);
+            mController.hide(statusBars() | navigationBars());
+            verify(mTestHost, times(2)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after hiding system bars.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+
+            // Showing invisible system bars should only causes insets change once for each bar.
+            clearInvocations(mTestHost);
+            mController.show(statusBars() | navigationBars());
+            verify(mTestHost, times(2)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after showing system bars.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+        });
+    }
+
+    @Test
+    public void testInsetsChangedCount_controlIme() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            prepareControls();
+
+            // Showing invisible ime should only causes insets change once.
+            clearInvocations(mTestHost);
+            mController.show(ime(), true /* fromIme */);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after showing ime.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+
+            // Hiding visible ime should only causes insets change once.
+            clearInvocations(mTestHost);
+            mController.hide(ime());
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Sending the same insets state should not cause insets change.
+            // This simulates the callback from server after hiding ime.
+            clearInvocations(mTestHost);
+            mController.onStateChanged(mController.getState());
+            verify(mTestHost, never()).notifyInsetsChanged();
+        });
+    }
+
+    @Test
+    public void testInsetsChangedCount_onStateChanged() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            final InsetsState localState = mController.getState();
+
+            // Changing status bar frame should cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            InsetsState newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_STATUS_BAR).getFrame().bottom++;
+            mController.onStateChanged(newState);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Changing status bar visibility should cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+            mController.onStateChanged(newState);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+
+            // Changing invisible IME frame should not cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_IME).getFrame().top--;
+            mController.onStateChanged(newState);
+            verify(mTestHost, never()).notifyInsetsChanged();
+
+            // Changing IME visibility should cause notifyInsetsChanged.
+            clearInvocations(mTestHost);
+            newState = new InsetsState(localState, true /* copySources */);
+            newState.getSource(ITYPE_IME).setVisible(true);
+            mController.onStateChanged(newState);
+            verify(mTestHost, times(1)).notifyInsetsChanged();
+        });
+    }
+
     private void waitUntilNextFrame() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
@@ -777,7 +874,7 @@
         return controls;
     }
 
-    private static class TestHost extends ViewRootInsetsControllerHost {
+    public static class TestHost extends ViewRootInsetsControllerHost {
 
         private InsetsState mModifiedState = new InsetsState();
 
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 844e929..10887fa 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -11575,6 +11575,9 @@
   public interface PacProcessor {
     method @Nullable public String findProxyForUrl(@NonNull String);
     method @NonNull public static android.webkit.PacProcessor getInstance();
+    method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(long);
+    method public default long getNetworkHandle();
+    method public default void releasePacProcessor();
     method public boolean setProxyScript(@NonNull String);
   }
 
@@ -11714,6 +11717,7 @@
     method public android.webkit.CookieManager getCookieManager();
     method public android.webkit.GeolocationPermissions getGeolocationPermissions();
     method @NonNull public default android.webkit.PacProcessor getPacProcessor();
+    method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(long);
     method public android.webkit.ServiceWorkerController getServiceWorkerController();
     method public android.webkit.WebViewFactoryProvider.Statics getStatics();
     method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d5f74a8..816bcf8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -53,8 +53,8 @@
             ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION;
 
     @VisibleForTesting
-    protected WindowMagnificationAnimationController mWindowMagnificationAnimationController;
-    private final ModeSwitchesController mModeSwitchesController;
+    protected WindowMagnificationController mWindowMagnificationController;
+    protected final ModeSwitchesController mModeSwitchesController;
     private final Handler mHandler;
     private final AccessibilityManager mAccessibilityManager;
     private final CommandQueue mCommandQueue;
@@ -72,11 +72,6 @@
                 Context.ACCESSIBILITY_SERVICE);
         mCommandQueue = commandQueue;
         mModeSwitchesController = modeSwitchesController;
-        final WindowMagnificationController controller = new WindowMagnificationController(mContext,
-                mHandler, new SfVsyncFrameCallbackProvider(), null,
-                new SurfaceControl.Transaction(), this);
-        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext, controller);
     }
 
     @Override
@@ -86,7 +81,9 @@
             return;
         }
         mLastConfiguration.setTo(newConfig);
-        mWindowMagnificationAnimationController.onConfigurationChanged(configDiff);
+        if (mWindowMagnificationController != null) {
+            mWindowMagnificationController.onConfigurationChanged(configDiff);
+        }
         if (mModeSwitchesController != null) {
             mModeSwitchesController.onConfigurationChanged(configDiff);
         }
@@ -100,25 +97,39 @@
     @MainThread
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
         //TODO: b/144080869 support multi-display.
-        mWindowMagnificationAnimationController.enableWindowMagnification(scale, centerX, centerY);
+        if (mWindowMagnificationController == null) {
+            mWindowMagnificationController = new WindowMagnificationController(mContext,
+                    mHandler,
+                    new SfVsyncFrameCallbackProvider(),
+                    null, new SurfaceControl.Transaction(),
+                    this);
+        }
+        mWindowMagnificationController.enableWindowMagnification(scale, centerX, centerY);
     }
 
     @MainThread
     void setScale(int displayId, float scale) {
         //TODO: b/144080869 support multi-display.
-        mWindowMagnificationAnimationController.setScale(scale);
+        if (mWindowMagnificationController != null) {
+            mWindowMagnificationController.setScale(scale);
+        }
     }
 
     @MainThread
     void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
         //TODO: b/144080869 support multi-display.
-        mWindowMagnificationAnimationController.moveWindowMagnifier(offsetX, offsetY);
+        if (mWindowMagnificationController != null) {
+            mWindowMagnificationController.moveWindowMagnifier(offsetX, offsetY);
+        }
     }
 
     @MainThread
     void disableWindowMagnification(int displayId) {
         //TODO: b/144080869 support multi-display.
-        mWindowMagnificationAnimationController.deleteWindowMagnification();
+        if (mWindowMagnificationController != null) {
+            mWindowMagnificationController.deleteWindowMagnification();
+        }
+        mWindowMagnificationController = null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
deleted file mode 100644
index ae51623..0000000
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility;
-
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.Log;
-import android.view.animation.AccelerateInterpolator;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Provides same functionality of {@link WindowMagnificationController}. Some methods run with
- * the animation.
- */
-class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUpdateListener,
-        Animator.AnimatorListener {
-
-    private static final String TAG = "WindowMagnificationBridge";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_DISABLING, STATE_ENABLING})
-    @interface MagnificationState {}
-
-    //The window magnification is disabled.
-    private static final int STATE_DISABLED = 0;
-    //The window magnification is enabled.
-    private static final int STATE_ENABLED = 1;
-    //The window magnification is going to be disabled when the animation is end.
-    private  static final int STATE_DISABLING = 2;
-    //The animation is running for enabling the window magnification.
-    private static final int STATE_ENABLING = 3;
-
-    private final WindowMagnificationController mController;
-    private final ValueAnimator mValueAnimator;
-    private final AnimationSpec mStartSpec = new AnimationSpec();
-    private final AnimationSpec mEndSpec = new AnimationSpec();
-    private final Context mContext;
-
-    @MagnificationState
-    private int mState = STATE_DISABLED;
-
-    WindowMagnificationAnimationController(
-            Context context, WindowMagnificationController controller) {
-        this(context, controller, newValueAnimator(context.getResources()));
-    }
-
-    @VisibleForTesting
-    WindowMagnificationAnimationController(Context context,
-            WindowMagnificationController controller, ValueAnimator valueAnimator) {
-        mContext = context;
-        mController = controller;
-        mValueAnimator = valueAnimator;
-        mValueAnimator.addUpdateListener(this);
-        mValueAnimator.addListener(this);
-    }
-
-    /**
-     * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)}
-     * with transition animation. If the window magnification is not enabled, the scale will start
-     * from 1.0 and the center won't be changed during the animation. If {@link #mState} is
-     * {@code STATE_DISABLING}, the animation runs in reverse.
-     *
-     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged.
-     * @param centerX the screen-relative X coordinate around which to center,
-     *                or {@link Float#NaN} to leave unchanged.
-     * @param centerY the screen-relative Y coordinate around which to center,
-     *                or {@link Float#NaN} to leave unchanged.
-     *
-     * @see #onAnimationUpdate(ValueAnimator)
-     */
-    void enableWindowMagnification(float scale, float centerX, float centerY) {
-        if (mState == STATE_ENABLING) {
-            mValueAnimator.cancel();
-        }
-        setupEnableAnimationSpecs(scale, centerX, centerY);
-
-        if (mEndSpec.equals(mStartSpec)) {
-            setState(STATE_ENABLED);
-        } else {
-            if (mState == STATE_DISABLING) {
-                mValueAnimator.reverse();
-            } else {
-                mValueAnimator.start();
-            }
-            setState(STATE_ENABLING);
-        }
-    }
-
-    private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) {
-        final float currentScale = mController.getScale();
-        final float currentCenterX = mController.getCenterX();
-        final float currentCenterY = mController.getCenterY();
-
-        if (mState == STATE_DISABLED) {
-            //We don't need to offset the center during the animation.
-            mStartSpec.set(/* scale*/ 1.0f, centerX, centerY);
-            mEndSpec.set(Float.isNaN(scale) ? mContext.getResources().getInteger(
-                    R.integer.magnification_default_scale) : scale, centerX, centerY);
-        } else {
-            mStartSpec.set(currentScale, currentCenterX, currentCenterY);
-            mEndSpec.set(Float.isNaN(scale) ? currentScale : scale,
-                    Float.isNaN(centerX) ? currentCenterX : centerX,
-                    Float.isNaN(centerY) ? currentCenterY : centerY);
-        }
-        if (DEBUG) {
-            Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = "
-                    + mEndSpec);
-        }
-    }
-
-    /**
-     * Wraps {@link WindowMagnificationController#setScale(float)}. If the animation is
-     * running, it has no effect.
-     */
-    void setScale(float scale) {
-        if (mValueAnimator.isRunning()) {
-            return;
-        }
-        mController.setScale(scale);
-    }
-
-    /**
-     * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
-     * animation. If the window magnification is enabling, it runs the animation in reverse.
-     */
-    void deleteWindowMagnification() {
-        if (mState == STATE_DISABLED || mState == STATE_DISABLING) {
-            return;
-        }
-        mStartSpec.set(/* scale*/ 1.0f, Float.NaN, Float.NaN);
-        mEndSpec.set(/* scale*/ mController.getScale(), Float.NaN, Float.NaN);
-
-        mValueAnimator.reverse();
-        setState(STATE_DISABLING);
-    }
-
-    /**
-     * Wraps {@link WindowMagnificationController#moveWindowMagnifier(float, float)}. If the
-     * animation is running, it has no effect.
-     * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in
-     *                current screen pixels.
-     * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in
-     *                current screen pixels.
-     */
-    void moveWindowMagnifier(float offsetX, float offsetY) {
-        if (mValueAnimator.isRunning()) {
-            return;
-        }
-        mController.moveWindowMagnifier(offsetX, offsetY);
-    }
-
-    void onConfigurationChanged(int configDiff) {
-        mController.onConfigurationChanged(configDiff);
-    }
-
-    private void setState(@MagnificationState int state) {
-        if (DEBUG) {
-            Log.d(TAG, "setState from " + mState + " to " + state);
-        }
-        mState = state;
-    }
-
-    @Override
-    public void onAnimationStart(Animator animation) {
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        if (mState == STATE_DISABLING) {
-            mController.deleteWindowMagnification();
-            setState(STATE_DISABLED);
-        } else if (mState == STATE_ENABLING) {
-            setState(STATE_ENABLED);
-        } else {
-            Log.w(TAG, "onAnimationEnd unexpected state:" + mState);
-        }
-    }
-
-    @Override
-    public void onAnimationCancel(Animator animation) {
-    }
-
-    @Override
-    public void onAnimationRepeat(Animator animation) {
-    }
-
-    @Override
-    public void onAnimationUpdate(ValueAnimator animation) {
-        final float fract = animation.getAnimatedFraction();
-        final float sentScale = mStartSpec.mScale + (mEndSpec.mScale - mStartSpec.mScale) * fract;
-        final float centerX =
-                mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract;
-        final float centerY =
-                mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract;
-        mController.enableWindowMagnification(sentScale, centerX, centerY);
-    }
-
-    private static ValueAnimator newValueAnimator(Resources resources) {
-        final ValueAnimator valueAnimator = new ValueAnimator();
-        valueAnimator.setDuration(
-                resources.getInteger(com.android.internal.R.integer.config_longAnimTime));
-        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
-        valueAnimator.setFloatValues(0.0f, 1.0f);
-        return valueAnimator;
-    }
-
-    private static class AnimationSpec {
-        private float mScale = Float.NaN;
-        private float mCenterX = Float.NaN;
-        private float mCenterY = Float.NaN;
-
-        @Override
-        public boolean equals(Object other) {
-            if (this == other) {
-                return true;
-            }
-
-            if (other == null || getClass() != other.getClass()) {
-                return false;
-            }
-
-            final AnimationSpec s = (AnimationSpec) other;
-            return mScale == s.mScale && mCenterX == s.mCenterX && mCenterY == s.mCenterY;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = (mScale != +0.0f ? Float.floatToIntBits(mScale) : 0);
-            result = 31 * result + (mCenterX != +0.0f ? Float.floatToIntBits(mCenterX) : 0);
-            result = 31 * result + (mCenterY != +0.0f ? Float.floatToIntBits(mCenterY) : 0);
-            return result;
-        }
-
-        void set(float scale, float centerX, float centerY) {
-            mScale = scale;
-            mCenterX = centerX;
-            mCenterY = centerY;
-        }
-
-        @Override
-        public String toString() {
-            return "AnimationSpec{"
-                    + "mScale=" + mScale
-                    + ", mCenterX=" + mCenterX
-                    + ", mCenterY=" + mCenterY
-                    + '}';
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 6d3e8ba..798b751 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -150,7 +150,7 @@
 
         mMirrorViewGeometryVsyncCallback =
                 l -> {
-                    if (isWindowVisible() && mMirrorSurface != null) {
+                    if (mMirrorView != null && mMirrorSurface != null) {
                         calculateSourceBounds(mMagnificationFrame, mScale);
                         // The final destination for the magnification surface should be at 0,0
                         // since the ViewRootImpl's position will change
@@ -502,7 +502,7 @@
     /**
      * Enables window magnification with specified parameters.
      *
-     * @param scale   the target scale, or {@link Float#NaN} to leave unchanged
+     * @param scale   the target scale
      * @param centerX the screen-relative X coordinate around which to center,
      *                or {@link Float#NaN} to leave unchanged.
      * @param centerY the screen-relative Y coordinate around which to center,
@@ -513,10 +513,10 @@
                 : centerX - mMagnificationFrame.exactCenterX();
         final float offsetY = Float.isNaN(centerY) ? 0
                 : centerY - mMagnificationFrame.exactCenterY();
-        mScale = Float.isNaN(scale) ? mScale : scale;
+        mScale = scale;
         setMagnificationFrameBoundary();
         updateMagnificationFramePosition((int) offsetX, (int) offsetY);
-        if (!isWindowVisible()) {
+        if (mMirrorView == null) {
             createMirrorWindow();
             showControls();
         } else {
@@ -527,10 +527,10 @@
     /**
      * Sets the scale of the magnified region if it's visible.
      *
-     * @param scale the target scale, or {@link Float#NaN} to leave unchanged
+     * @param scale the target scale
      */
     void setScale(float scale) {
-        if (!isWindowVisible() || mScale == scale) {
+        if (mMirrorView == null || mScale == scale) {
             return;
         }
         enableWindowMagnification(scale, Float.NaN, Float.NaN);
@@ -552,35 +552,4 @@
             modifyWindowMagnification(mTransaction);
         }
     }
-
-    /**
-     * Gets the scale.
-     * @return {@link Float#NaN} if the window is invisible.
-     */
-    float getScale() {
-        return isWindowVisible() ? mScale : Float.NaN;
-    }
-
-    /**
-     * Returns the screen-relative X coordinate of the center of the magnified bounds.
-     *
-     * @return the X coordinate. {@link Float#NaN} if the window is invisible.
-     */
-    float getCenterX() {
-        return isWindowVisible() ? mMagnificationFrame.exactCenterX() : Float.NaN;
-    }
-
-    /**
-     * Returns the screen-relative Y coordinate of the center of the magnified bounds.
-     *
-     * @return the Y coordinate. {@link Float#NaN} if the window is invisible.
-     */
-    float getCenterY() {
-        return isWindowVisible() ? mMagnificationFrame.exactCenterY() : Float.NaN;
-    }
-
-    //The window is visible when it is existed.
-    private boolean isWindowVisible() {
-        return mMirrorView != null;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index d0f6181..fa0d2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -310,7 +310,6 @@
         // Set ActivityView's alpha value as zero, since there is no view content to be shown.
         setContentVisibility(false);
 
-        mActivityViewContainer.setBackgroundColor(Color.WHITE);
         mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
@@ -439,9 +438,11 @@
     }
 
     void applyThemeAttrs() {
-        final TypedArray ta = mContext.obtainStyledAttributes(
-                new int[] {android.R.attr.dialogCornerRadius});
+        final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+                android.R.attr.dialogCornerRadius,
+                android.R.attr.colorBackgroundFloating});
         mCornerRadius = ta.getDimensionPixelSize(0, 0);
+        mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
         ta.recycle();
 
         if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
index 53b369c..eb47645 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java
@@ -34,6 +34,7 @@
     private ContextButtonListener mListener;
     private ContextualButtonGroup mGroup;
 
+    protected final Context mLightContext;
     protected final @DrawableRes int mIconResId;
 
     /**
@@ -42,8 +43,10 @@
       * @param buttonResId the button view from xml layout
       * @param iconResId icon resource to be used
       */
-    public ContextualButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
+    public ContextualButton(@IdRes int buttonResId, Context lightContext,
+            @DrawableRes int iconResId) {
         super(buttonResId);
+        mLightContext = lightContext;
         mIconResId = iconResId;
     }
 
@@ -117,17 +120,8 @@
     }
 
     protected KeyButtonDrawable getNewDrawable(int lightIconColor, int darkIconColor) {
-        return KeyButtonDrawable.create(getContext().getApplicationContext(), lightIconColor,
-                darkIconColor, mIconResId, false /* shadow */, null /* ovalBackground */);
-    }
-
-    /**
-     * This context is from the view that could be stale after rotation or config change. To get
-     * correct resources use getApplicationContext() as well.
-     * @return current view context
-     */
-    protected Context getContext() {
-        return getCurrentView().getContext();
+        return KeyButtonDrawable.create(mLightContext, lightIconColor, darkIconColor, mIconResId,
+                false /* shadow */, null /* ovalBackground */);
     }
 
     public interface ContextButtonListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 62970525..8c5e2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -375,6 +375,34 @@
         }
     };
 
+    private static class NavBarViewAttachedListener implements View.OnAttachStateChangeListener {
+        private NavigationBarFragment mFragment;
+        private FragmentListener mListener;
+
+        NavBarViewAttachedListener(NavigationBarFragment fragment, FragmentListener listener) {
+            mFragment = fragment;
+            mListener = listener;
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
+            fragmentHost.getFragmentManager().beginTransaction()
+                    .replace(R.id.navigation_bar_frame, mFragment, TAG)
+                    .commit();
+            fragmentHost.addTagListener(TAG, mListener);
+            mFragment = null;
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
+            fragmentHost.removeTagListener(TAG, mListener);
+            FragmentHostManager.removeAndDestroy(v);
+            v.removeOnAttachStateChangeListener(this);
+        }
+    }
+
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             new DeviceConfig.OnPropertiesChangedListener() {
         @Override
@@ -1470,26 +1498,10 @@
         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
         if (navigationBarView == null) return null;
 
-        navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(View v) {
-                final NavigationBarFragment fragment =
-                        FragmentHostManager.get(v).create(NavigationBarFragment.class);
-                final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
-                fragmentHost.getFragmentManager().beginTransaction()
-                        .replace(R.id.navigation_bar_frame, fragment, TAG)
-                        .commit();
-                fragmentHost.addTagListener(TAG, listener);
-            }
-
-            @Override
-            public void onViewDetachedFromWindow(View v) {
-                final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
-                fragmentHost.removeTagListener(TAG, listener);
-                FragmentHostManager.removeAndDestroy(v);
-                navigationBarView.removeOnAttachStateChangeListener(this);
-            }
-        });
+        NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
+                .create(NavigationBarFragment.class);
+        navigationBarView.addOnAttachStateChangeListener(new NavBarViewAttachedListener(fragment,
+                listener));
         context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
         return navigationBarView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 84512ac..0b4a2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -295,11 +295,12 @@
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
-                R.drawable.ic_ime_switcher_default);
+                mLightContext, R.drawable.ic_ime_switcher_default);
         final RotationContextButton rotateSuggestionButton = new RotationContextButton(
-                R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
+                R.id.rotate_suggestion, mLightContext,
+                R.drawable.ic_sysbar_rotate_button_ccw_start_0);
         final ContextualButton accessibilityButton =
-                new ContextualButton(R.id.accessibility_button,
+                new ContextualButton(R.id.accessibility_button, mLightContext,
                         R.drawable.ic_sysbar_accessibility_button);
         mContextualButtonGroup.addButton(imeSwitcherButton);
         if (!isGesturalMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
index f83cdd4..b0630a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
@@ -283,7 +283,6 @@
             return;
         }
 
-        // TODO: Remove styles?
         // Prepare to show the navbar icon by updating the icon style to change anim params
         mLastRotationSuggestion = rotation; // Remember rotation for click
         final boolean rotationCCW = isRotationAnimationCCW(windowRotation, rotation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
index d7e95e4..08aeb04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java
@@ -18,6 +18,7 @@
 
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
+import android.content.Context;
 import android.view.View;
 
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
@@ -28,8 +29,12 @@
 
     private RotationButtonController mRotationButtonController;
 
-    public RotationContextButton(@IdRes int buttonResId, @DrawableRes int iconResId) {
-        super(buttonResId, iconResId);
+    /**
+     * @param lightContext the context to use to load the icon resource
+     */
+    public RotationContextButton(@IdRes int buttonResId, Context lightContext,
+            @DrawableRes int iconResId) {
+        super(buttonResId, lightContext, iconResId);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
index b5f98ad..89297fd 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
@@ -54,7 +54,7 @@
         String uriString = intent.getStringExtra("uri");
         mUri = (uriString == null ? null : Uri.parse(uriString));
 
-        // sanity check before displaying dialog
+        // Exception check before displaying dialog
         if (mUri == null) {
             Log.e(TAG, "could not parse Uri " + uriString);
             finish();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index ac567e0..fbc8e9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -25,7 +25,6 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -48,7 +47,6 @@
  */
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
 public class IWindowMagnificationConnectionTest extends SysuiTestCase {
 
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@@ -59,7 +57,7 @@
     @Mock
     private IWindowMagnificationConnectionCallback mConnectionCallback;
     @Mock
-    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
+    private WindowMagnificationController mWindowMagnificationController;
     @Mock
     private ModeSwitchesController mModeSwitchesController;
     private IWindowMagnificationConnection mIWindowMagnificationConnection;
@@ -76,8 +74,7 @@
                 any(IWindowMagnificationConnection.class));
         mWindowMagnification = new WindowMagnification(getContext(),
                 getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController);
-        mWindowMagnification.mWindowMagnificationAnimationController =
-                mWindowMagnificationAnimationController;
+        mWindowMagnification.mWindowMagnificationController = mWindowMagnificationController;
         mWindowMagnification.requestWindowMagnificationConnection(true);
         assertNotNull(mIWindowMagnificationConnection);
         mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback);
@@ -89,7 +86,7 @@
                 Float.NaN);
         waitForIdleSync();
 
-        verify(mWindowMagnificationAnimationController).enableWindowMagnification(3.0f, Float.NaN,
+        verify(mWindowMagnificationController).enableWindowMagnification(3.0f, Float.NaN,
                 Float.NaN);
     }
 
@@ -102,7 +99,7 @@
         mIWindowMagnificationConnection.disableWindowMagnification(TEST_DISPLAY);
         waitForIdleSync();
 
-        verify(mWindowMagnificationAnimationController).deleteWindowMagnification();
+        verify(mWindowMagnificationController).deleteWindowMagnification();
     }
 
     @Test
@@ -110,7 +107,7 @@
         mIWindowMagnificationConnection.setScale(TEST_DISPLAY, 3.0f);
         waitForIdleSync();
 
-        verify(mWindowMagnificationAnimationController).setScale(3.0f);
+        verify(mWindowMagnificationController).setScale(3.0f);
     }
 
     @Test
@@ -118,7 +115,7 @@
         mIWindowMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
         waitForIdleSync();
 
-        verify(mWindowMagnificationAnimationController).moveWindowMagnifier(100f, 200f);
+        verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
deleted file mode 100644
index add0843..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.animation.ValueAnimator;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.testing.AndroidTestingRunner;
-import android.view.SurfaceControl;
-import android.view.animation.AccelerateInterpolator;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-
-@MediumTest
-@RunWith(AndroidTestingRunner.class)
-public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
-
-    private static final float DEFAULT_SCALE = 3.0f;
-    private static final float DEFAULT_CENTER_X = 400.0f;
-    private static final float DEFAULT_CENTER_Y = 500.0f;
-    private static final long ANIMATION_DURATION_MS = 100;
-
-    private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
-    private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
-    private AtomicReference<Float> mCurrentCenterY = new AtomicReference<>((float) 0);
-    private ArgumentCaptor<Float> mScaleCaptor = ArgumentCaptor.forClass(Float.class);
-    private ArgumentCaptor<Float> mCenterXCaptor = ArgumentCaptor.forClass(Float.class);
-    private ArgumentCaptor<Float> mCenterYCaptor = ArgumentCaptor.forClass(Float.class);
-
-    @Mock
-    Handler mHandler;
-    @Mock
-    SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
-    @Mock
-    WindowMagnifierCallback mWindowMagnifierCallback;
-
-    private SpyWindowMagnificationController mController;
-    private WindowMagnificationController mSpyController;
-    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
-    private Instrumentation mInstrumentation;
-    private long mWaitingAnimationPeriod;
-    private long mWaitIntermediateAnimationPeriod;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mWaitingAnimationPeriod = ANIMATION_DURATION_MS + 50;
-        mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2;
-        mController = new SpyWindowMagnificationController(mContext, mHandler,
-                mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
-                mWindowMagnifierCallback);
-        mSpyController = mController.getSpyController();
-        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext, mController, newValueAnimator());
-    }
-
-    @Test
-    public void enableWindowMagnification_disabled_expectedStartAndEndValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        verify(mSpyController, atLeast(2)).enableWindowMagnification(
-                mScaleCaptor.capture(),
-                mCenterXCaptor.capture(), mCenterYCaptor.capture());
-        verifyStartValue(mScaleCaptor, 1.0f);
-        verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X);
-        verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y);
-        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
-    }
-
-    @Test
-    public void enableWindowMagnification_enabling_expectedStartAndEndValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
-        final float targetScale = DEFAULT_SCALE + 1.0f;
-        final float targetCenterX = DEFAULT_CENTER_X + 100;
-        final float targetCenterY = DEFAULT_CENTER_Y + 100;
-
-        mInstrumentation.runOnMainSync(() -> {
-            Mockito.reset(mSpyController);
-            mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                    targetCenterX, targetCenterY);
-            mCurrentScale.set(mController.getScale());
-            mCurrentCenterX.set(mController.getCenterX());
-            mCurrentCenterY.set(mController.getCenterY());
-        });
-
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
-        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
-                mCenterXCaptor.capture(), mCenterYCaptor.capture());
-        verifyStartValue(mScaleCaptor, mCurrentScale.get());
-        verifyStartValue(mCenterXCaptor, mCurrentCenterX.get());
-        verifyStartValue(mCenterYCaptor, mCurrentCenterY.get());
-        verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
-    }
-
-    @Test
-    public void enableWindowMagnification_disabling_expectedStartAndEndValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
-        final float targetScale = DEFAULT_SCALE + 1.0f;
-        final float targetCenterX = DEFAULT_CENTER_X + 100;
-        final float targetCenterY = DEFAULT_CENTER_Y + 100;
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
-                            targetCenterX, targetCenterY);
-                    mCurrentScale.set(mController.getScale());
-                    mCurrentCenterX.set(mController.getCenterX());
-                    mCurrentCenterY.set(mController.getCenterY());
-                });
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
-        verify(mSpyController, atLeast(2)).enableWindowMagnification(
-                mScaleCaptor.capture(),
-                mCenterXCaptor.capture(), mCenterYCaptor.capture());
-        //Animating in reverse, so we only check if the start values are greater than current.
-        assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get());
-        assertEquals(targetScale, mScaleCaptor.getValue(), 0f);
-        assertTrue(mCenterXCaptor.getAllValues().get(0) > mCurrentCenterX.get());
-        assertEquals(targetCenterX, mCenterXCaptor.getValue(), 0f);
-        assertTrue(mCenterYCaptor.getAllValues().get(0) > mCurrentCenterY.get());
-        assertEquals(targetCenterY, mCenterYCaptor.getValue(), 0f);
-        verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
-    }
-
-    @Test
-    public void enableWindowMagnificationWithSameScale_doNothing() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(),
-                anyFloat());
-    }
-
-    @Test
-    public void setScale_enabled_expectedScale() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationAnimationController.setScale(DEFAULT_SCALE + 1));
-
-        verify(mSpyController).setScale(DEFAULT_SCALE + 1);
-        verifyFinalSpec(DEFAULT_SCALE + 1, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
-    }
-
-    @Test
-    public void deleteWindowMagnification_enabled_expectedStartAndEndValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
-                mCenterXCaptor.capture(), mCenterYCaptor.capture());
-        verify(mSpyController).deleteWindowMagnification();
-        verifyStartValue(mScaleCaptor, DEFAULT_SCALE);
-        verifyStartValue(mCenterXCaptor, Float.NaN);
-        verifyStartValue(mCenterYCaptor, Float.NaN);
-        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
-    }
-
-    @Test
-    public void deleteWindowMagnification_disabled_doNothing() {
-        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        Mockito.verifyNoMoreInteractions(mSpyController);
-    }
-
-    @Test
-    public void deleteWindowMagnification_enabling_checkStartAndEndValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
-
-        //It just reverse the animation, so we don't need to wait the whole duration.
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.deleteWindowMagnification();
-                    mCurrentScale.set(mController.getScale());
-                    mCurrentCenterX.set(mController.getCenterX());
-                    mCurrentCenterY.set(mController.getCenterY());
-                });
-        SystemClock.sleep(mWaitingAnimationPeriod);
-
-        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
-                mCenterXCaptor.capture(), mCenterYCaptor.capture());
-        verify(mSpyController).deleteWindowMagnification();
-
-        //The animation is in verse, so we only check the start values should no be greater than
-        // the current one.
-        assertTrue(mScaleCaptor.getAllValues().get(0) <= mCurrentScale.get());
-        assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
-        verifyStartValue(mCenterXCaptor, Float.NaN);
-        verifyStartValue(mCenterYCaptor, Float.NaN);
-        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
-    }
-
-    @Test
-    public void deleteWindowMagnification_disabling_checkStartAndValues() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-        deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod);
-
-        deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(),
-                mCenterXCaptor.capture(), mCenterYCaptor.capture());
-        verify(mSpyController).deleteWindowMagnification();
-        assertEquals(1.0f, mScaleCaptor.getValue(), 0f);
-        verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
-    }
-
-    @Test
-    public void moveWindowMagnifier_enabled() {
-        enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod);
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationAnimationController.moveWindowMagnifier(100f, 200f));
-
-        verify(mSpyController).moveWindowMagnifier(100f, 200f);
-        verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 100f, DEFAULT_CENTER_Y + 100f);
-    }
-
-    @Test
-    public void onConfigurationChanged_passThrough() {
-        mWindowMagnificationAnimationController.onConfigurationChanged(100);
-
-        verify(mSpyController).onConfigurationChanged(100);
-    }
-    private void verifyFinalSpec(float expectedScale, float expectedCenterX,
-            float expectedCenterY) {
-        assertEquals(expectedScale, mController.getScale(), 0f);
-        assertEquals(expectedCenterX, mController.getCenterX(), 0f);
-        assertEquals(expectedCenterY, mController.getCenterY(), 0f);
-    }
-
-    private void enableWindowMagnificationAndWaitAnimating(long duration) {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    Mockito.reset(mSpyController);
-                    mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
-                            DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
-                });
-        SystemClock.sleep(duration);
-    }
-
-    private void deleteWindowMagnificationAndWaitAnimating(long duration) {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    resetMockObjects();
-                    mWindowMagnificationAnimationController.deleteWindowMagnification();
-                });
-        SystemClock.sleep(duration);
-    }
-
-    private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
-        assertEquals(startValue, captor.getAllValues().get(0), 0f);
-    }
-
-    private void resetMockObjects() {
-        Mockito.reset(mSpyController);
-    }
-
-    /**
-     * It observes the methods in {@link WindowMagnificationController} since we couldn't spy it
-     * directly.
-     */
-    private static class SpyWindowMagnificationController extends WindowMagnificationController {
-        private WindowMagnificationController mSpyController;
-
-        SpyWindowMagnificationController(Context context, Handler handler,
-                SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
-                MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction,
-                WindowMagnifierCallback callback) {
-            super(context, handler, sfVsyncFrameProvider, mirrorWindowControl, transaction,
-                    callback);
-            mSpyController = Mockito.mock(WindowMagnificationController.class);
-        }
-
-        WindowMagnificationController getSpyController() {
-            return mSpyController;
-        }
-
-        @Override
-        void enableWindowMagnification(float scale, float centerX, float centerY) {
-            super.enableWindowMagnification(scale, centerX, centerY);
-            mSpyController.enableWindowMagnification(scale, centerX, centerY);
-        }
-
-        @Override
-        void deleteWindowMagnification() {
-            super.deleteWindowMagnification();
-            mSpyController.deleteWindowMagnification();
-        }
-
-        @Override
-        void moveWindowMagnifier(float offsetX, float offsetY) {
-            super.moveWindowMagnifier(offsetX, offsetX);
-            mSpyController.moveWindowMagnifier(offsetX, offsetY);
-        }
-
-        @Override
-        void setScale(float scale) {
-            super.setScale(scale);
-            mSpyController.setScale(scale);
-        }
-
-        @Override
-        void onConfigurationChanged(int configDiff) {
-            super.onConfigurationChanged(configDiff);
-            mSpyController.onConfigurationChanged(configDiff);
-        }
-
-    }
-
-    private static ValueAnimator newValueAnimator() {
-        final ValueAnimator valueAnimator = new ValueAnimator();
-        valueAnimator.setDuration(ANIMATION_DURATION_MS);
-        valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
-        valueAnimator.setFloatValues(0.0f, 1.0f);
-        return valueAnimator;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 1515cec..2007fbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,7 +18,6 @@
 
 import static android.view.Choreographer.FrameCallback;
 
-import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
@@ -84,8 +83,9 @@
 
     @After
     public void tearDown() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.deleteWindowMagnification();
+        });
     }
 
     @Test
@@ -121,18 +121,4 @@
 
         verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
     }
-
-    @Test
-    public void setScale_expectedValue() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setScale(3.0f);
-        });
-
-        assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 936558b..4136013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -26,7 +26,6 @@
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IWindowMagnificationConnection;
@@ -46,7 +45,6 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
 public class WindowMagnificationTest extends SysuiTestCase {
 
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
index b5060ee..1fb28f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
@@ -65,9 +65,9 @@
         mDependency.injectMockDependency(AssistManager.class);
 
         mGroup = new ContextualButtonGroup(GROUP_ID);
-        mBtn0 = new ContextualButton(BUTTON_0_ID, ICON_RES_ID);
-        mBtn1 = new ContextualButton(BUTTON_1_ID, ICON_RES_ID);
-        mBtn2 = new ContextualButton(BUTTON_2_ID, ICON_RES_ID);
+        mBtn0 = new ContextualButton(BUTTON_0_ID, mContext, ICON_RES_ID);
+        mBtn1 = new ContextualButton(BUTTON_1_ID, mContext, ICON_RES_ID);
+        mBtn2 = new ContextualButton(BUTTON_2_ID, mContext, ICON_RES_ID);
 
         // Order of adding buttons to group determines the priority, ascending priority order
         mGroup.addButton(mBtn0);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 3ee5b28..bd25f2b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -25,12 +25,10 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.Point;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
-import android.view.Display;
 import android.view.MotionEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -92,8 +90,6 @@
 
     private MotionEventDispatcherDelegate mMotionEventDispatcherDelegate;
     private final int mDisplayId;
-    private final Context mContext;
-    private final Point mTempPoint = new Point();
 
     private final Queue<MotionEvent> mDebugOutputEventHistory;
 
@@ -111,7 +107,7 @@
             Slog.i(LOG_TAG,
                     "WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
         }
-        mContext = context;
+
         mWindowMagnificationMgr = windowMagnificationMgr;
         mDetectShortcutTrigger = detectShortcutTrigger;
         mDisplayId = displayId;
@@ -188,14 +184,7 @@
         if (!mDetectShortcutTrigger) {
             return;
         }
-        final Point screenSize = mTempPoint;
-        getScreenSize(mTempPoint);
-        toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f);
-    }
-
-    private  void getScreenSize(Point outSize) {
-        final Display display = mContext.getDisplay();
-        display.getRealSize(outSize);
+        toggleMagnification(Float.NaN, Float.NaN);
     }
 
     @Override
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 12e6e10..186812b 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1239,9 +1239,10 @@
 
     @Override
     public IRestoreSession beginRestoreSessionForUser(
-            int userId, String packageName, String transportID) throws RemoteException {
+            int userId, String packageName, String transportID,
+            @OperationType int operationType) throws RemoteException {
         return isUserReadyForBackup(userId)
-                ? beginRestoreSession(userId, packageName, transportID) : null;
+                ? beginRestoreSession(userId, packageName, transportID, operationType) : null;
     }
 
     /**
@@ -1250,13 +1251,15 @@
      */
     @Nullable
     public IRestoreSession beginRestoreSession(
-            @UserIdInt int userId, String packageName, String transportName) {
+            @UserIdInt int userId, String packageName, String transportName,
+            @OperationType int operationType) {
         UserBackupManagerService userBackupManagerService =
                 getServiceForUserIfCallerHasPermission(userId, "beginRestoreSession()");
 
         return userBackupManagerService == null
                 ? null
-                : userBackupManagerService.beginRestoreSession(packageName, transportName);
+                : userBackupManagerService.beginRestoreSession(packageName, transportName,
+                        operationType);
     }
 
     @Override
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 3ab81cb..27a280a 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -3973,7 +3973,8 @@
                                 restoreSet,
                                 packageName,
                                 token,
-                                listener);
+                                listener,
+                                mScheduledBackupEligibility);
                 mBackupHandler.sendMessage(msg);
             } catch (Exception e) {
                 // Calling into the transport broke; back off and proceed with the installation.
@@ -4002,13 +4003,15 @@
     }
 
     /** Hand off a restore session. */
-    public IRestoreSession beginRestoreSession(String packageName, String transport) {
+    public IRestoreSession beginRestoreSession(String packageName, String transport,
+            @OperationType int operationType) {
         if (DEBUG) {
             Slog.v(
                     TAG,
                     addUserIdToLogMessage(
                             mUserId,
-                            "beginRestoreSession: pkg=" + packageName + " transport=" + transport));
+                            "beginRestoreSession: pkg=" + packageName + " transport=" + transport
+                                + "operationType=" + operationType));
         }
 
         boolean needPermission = true;
@@ -4065,7 +4068,8 @@
                                 "Restore session requested but currently running backups"));
                 return null;
             }
-            mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport);
+            mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport,
+                    getEligibilityRulesForOperation(operationType));
             mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
                     mAgentTimeoutParameters.getRestoreAgentTimeoutMillis());
         }
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 1bb4349..100dbae 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -305,8 +305,7 @@
                                 params.isSystemRestore,
                                 params.filterSet,
                                 params.listener,
-                                backupManagerService.getEligibilityRulesForOperation(
-                                        OperationType.BACKUP));
+                                params.backupEligibilityRules);
 
                 synchronized (backupManagerService.getPendingRestores()) {
                     if (backupManagerService.isRestoreInProgress()) {
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index a6fea6c..a08a1f8 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -23,6 +23,7 @@
 
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.utils.BackupEligibilityRules;
 
 import java.util.Map;
 import java.util.Set;
@@ -37,6 +38,7 @@
     public final boolean isSystemRestore;
     @Nullable public final String[] filterSet;
     public final OnTaskFinishedListener listener;
+    public final BackupEligibilityRules backupEligibilityRules;
 
     /**
      * No kill after restore.
@@ -47,7 +49,8 @@
             IBackupManagerMonitor monitor,
             long token,
             PackageInfo packageInfo,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules eligibilityRules) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -57,7 +60,8 @@
                 /* pmToken */ 0,
                 /* isSystemRestore */ false,
                 /* filterSet */ null,
-                listener);
+                listener,
+                eligibilityRules);
     }
 
     /**
@@ -70,7 +74,8 @@
             long token,
             String packageName,
             int pmToken,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         String[] filterSet = {packageName};
         return new RestoreParams(
                 transportClient,
@@ -81,7 +86,8 @@
                 pmToken,
                 /* isSystemRestore */ false,
                 filterSet,
-                listener);
+                listener,
+                backupEligibilityRules);
     }
 
     /**
@@ -92,7 +98,8 @@
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
             long token,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -102,7 +109,8 @@
                 /* pmToken */ 0,
                 /* isSystemRestore */ true,
                 /* filterSet */ null,
-                listener);
+                listener,
+                backupEligibilityRules);
     }
 
     /**
@@ -115,7 +123,8 @@
             long token,
             String[] filterSet,
             boolean isSystemRestore,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         return new RestoreParams(
                 transportClient,
                 observer,
@@ -125,7 +134,8 @@
                 /* pmToken */ 0,
                 isSystemRestore,
                 filterSet,
-                listener);
+                listener,
+                backupEligibilityRules);
     }
 
     private RestoreParams(
@@ -137,7 +147,8 @@
             int pmToken,
             boolean isSystemRestore,
             @Nullable String[] filterSet,
-            OnTaskFinishedListener listener) {
+            OnTaskFinishedListener listener,
+            BackupEligibilityRules backupEligibilityRules) {
         this.transportClient = transportClient;
         this.observer = observer;
         this.monitor = monitor;
@@ -147,5 +158,6 @@
         this.isSystemRestore = isSystemRestore;
         this.filterSet = filterSet;
         this.listener = listener;
+        this.backupEligibilityRules = backupEligibilityRules;
     }
 }
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 5a57cdc..3102b5f 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -42,6 +42,7 @@
 import com.android.server.backup.params.RestoreGetSetsParams;
 import com.android.server.backup.params.RestoreParams;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.utils.BackupEligibilityRules;
 
 import java.util.function.BiFunction;
 
@@ -55,6 +56,7 @@
     private final String mTransportName;
     private final UserBackupManagerService mBackupManagerService;
     private final int mUserId;
+    private final BackupEligibilityRules mBackupEligibilityRules;
     @Nullable private final String mPackageName;
     public RestoreSet[] mRestoreSets = null;
     boolean mEnded = false;
@@ -63,12 +65,14 @@
     public ActiveRestoreSession(
             UserBackupManagerService backupManagerService,
             @Nullable String packageName,
-            String transportName) {
+            String transportName,
+            BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
         mPackageName = packageName;
         mTransportManager = backupManagerService.getTransportManager();
         mTransportName = transportName;
         mUserId = backupManagerService.getUserId();
+        mBackupEligibilityRules = backupEligibilityRules;
     }
 
     public void markTimedOut() {
@@ -178,7 +182,8 @@
                                                 observer,
                                                 monitor,
                                                 token,
-                                                listener),
+                                                listener,
+                                                mBackupEligibilityRules),
                                 "RestoreSession.restoreAll()");
                     } finally {
                         Binder.restoreCallingIdentity(oldId);
@@ -271,7 +276,8 @@
                                                 token,
                                                 packages,
                                                 /* isSystemRestore */ packages.length > 1,
-                                                listener),
+                                                listener,
+                                                mBackupEligibilityRules),
                                 "RestoreSession.restorePackages(" + packages.length + " packages)");
                     } finally {
                         Binder.restoreCallingIdentity(oldId);
@@ -363,7 +369,8 @@
                                     monitor,
                                     token,
                                     app,
-                                    listener),
+                                    listener,
+                                    mBackupEligibilityRules),
                     "RestoreSession.restorePackage(" + packageName + ")");
         } finally {
             Binder.restoreCallingIdentity(oldId);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1f85d10..1c93d4e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -48,6 +48,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
+import android.net.DnsResolver;
 import android.net.INetworkManagementEventObserver;
 import android.net.Ikev2VpnProfile;
 import android.net.IpPrefix;
@@ -79,6 +80,7 @@
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -123,6 +125,7 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
@@ -134,6 +137,8 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -190,6 +195,7 @@
     // automated reconnection
 
     private final Context mContext;
+    @VisibleForTesting final Dependencies mDeps;
     private final NetworkInfo mNetworkInfo;
     @VisibleForTesting protected String mPackage;
     private int mOwnerUID;
@@ -252,17 +258,143 @@
     // Handle of the user initiating VPN.
     private final int mUserHandle;
 
+    interface RetryScheduler {
+        void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException;
+    }
+
+    static class Dependencies {
+        public void startService(final String serviceName) {
+            SystemService.start(serviceName);
+        }
+
+        public void stopService(final String serviceName) {
+            SystemService.stop(serviceName);
+        }
+
+        public boolean isServiceRunning(final String serviceName) {
+            return SystemService.isRunning(serviceName);
+        }
+
+        public boolean isServiceStopped(final String serviceName) {
+            return SystemService.isStopped(serviceName);
+        }
+
+        public File getStateFile() {
+            return new File("/data/misc/vpn/state");
+        }
+
+        public void sendArgumentsToDaemon(
+                final String daemon, final LocalSocket socket, final String[] arguments,
+                final RetryScheduler retryScheduler) throws IOException, InterruptedException {
+            final LocalSocketAddress address = new LocalSocketAddress(
+                    daemon, LocalSocketAddress.Namespace.RESERVED);
+
+            // Wait for the socket to connect.
+            while (true) {
+                try {
+                    socket.connect(address);
+                    break;
+                } catch (Exception e) {
+                    // ignore
+                }
+                retryScheduler.checkInterruptAndDelay(true /* sleepLonger */);
+            }
+            socket.setSoTimeout(500);
+
+            final OutputStream out = socket.getOutputStream();
+            for (String argument : arguments) {
+                byte[] bytes = argument.getBytes(StandardCharsets.UTF_8);
+                if (bytes.length >= 0xFFFF) {
+                    throw new IllegalArgumentException("Argument is too large");
+                }
+                out.write(bytes.length >> 8);
+                out.write(bytes.length);
+                out.write(bytes);
+                retryScheduler.checkInterruptAndDelay(false /* sleepLonger */);
+            }
+            out.write(0xFF);
+            out.write(0xFF);
+
+            // Wait for End-of-File.
+            final InputStream in = socket.getInputStream();
+            while (true) {
+                try {
+                    if (in.read() == -1) {
+                        break;
+                    }
+                } catch (Exception e) {
+                    // ignore
+                }
+                retryScheduler.checkInterruptAndDelay(true /* sleepLonger */);
+            }
+        }
+
+        @NonNull
+        public InetAddress resolve(final String endpoint)
+                throws ExecutionException, InterruptedException {
+            try {
+                return InetAddress.parseNumericAddress(endpoint);
+            } catch (IllegalArgumentException e) {
+                // Endpoint is not numeric : fall through and resolve
+            }
+
+            final CancellationSignal cancellationSignal = new CancellationSignal();
+            try {
+                final DnsResolver resolver = DnsResolver.getInstance();
+                final CompletableFuture<InetAddress> result = new CompletableFuture();
+                final DnsResolver.Callback<List<InetAddress>> cb =
+                        new DnsResolver.Callback<List<InetAddress>>() {
+                            @Override
+                            public void onAnswer(@NonNull final List<InetAddress> answer,
+                                    final int rcode) {
+                                if (answer.size() > 0) {
+                                    result.complete(answer.get(0));
+                                } else {
+                                    result.completeExceptionally(
+                                            new UnknownHostException(endpoint));
+                                }
+                            }
+
+                            @Override
+                            public void onError(@Nullable final DnsResolver.DnsException error) {
+                                // Unfortunately UnknownHostException doesn't accept a cause, so
+                                // print a message here instead. Only show the summary, not the
+                                // full stack trace.
+                                Log.e(TAG, "Async dns resolver error : " + error);
+                                result.completeExceptionally(new UnknownHostException(endpoint));
+                            }
+                        };
+                resolver.query(null /* network, null for default */, endpoint,
+                        DnsResolver.FLAG_EMPTY, r -> r.run(), cancellationSignal, cb);
+                return result.get();
+            } catch (final ExecutionException e) {
+                Log.e(TAG, "Cannot resolve VPN endpoint : " + endpoint + ".", e);
+                throw e;
+            } catch (final InterruptedException e) {
+                Log.e(TAG, "Legacy VPN was interrupted while resolving the endpoint", e);
+                cancellationSignal.cancel();
+                throw e;
+            }
+        }
+
+        public boolean checkInterfacePresent(final Vpn vpn, final String iface) {
+            return vpn.jniCheck(iface) == 0;
+        }
+    }
+
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
             @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
-        this(looper, context, netService, userHandle, keyStore,
+        this(looper, context, new Dependencies(), netService, userHandle, keyStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
-    protected Vpn(Looper looper, Context context, INetworkManagementService netService,
+    protected Vpn(Looper looper, Context context, Dependencies deps,
+            INetworkManagementService netService,
             int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
+        mDeps = deps;
         mNetd = netService;
         mUserHandle = userHandle;
         mLooper = looper;
@@ -2129,7 +2261,8 @@
     }
 
     /** This class represents the common interface for all VPN runners. */
-    private abstract class VpnRunner extends Thread {
+    @VisibleForTesting
+    abstract class VpnRunner extends Thread {
 
         protected VpnRunner(String name) {
             super(name);
@@ -2638,7 +2771,7 @@
                     } catch (InterruptedException e) {
                     }
                     for (String daemon : mDaemons) {
-                        SystemService.stop(daemon);
+                        mDeps.stopService(daemon);
                     }
                 }
                 agentDisconnect();
@@ -2655,21 +2788,55 @@
             }
         }
 
+        private void checkAndFixupArguments(@NonNull final InetAddress endpointAddress) {
+            final String endpointAddressString = endpointAddress.getHostAddress();
+            // Perform some safety checks before inserting the address in place.
+            // Position 0 in mDaemons and mArguments must be racoon, and position 1 must be mtpd.
+            if (!"racoon".equals(mDaemons[0]) || !"mtpd".equals(mDaemons[1])) {
+                throw new IllegalStateException("Unexpected daemons order");
+            }
+
+            // Respectively, the positions at which racoon and mtpd take the server address
+            // argument are 1 and 2. Not all types of VPN require both daemons however, and
+            // in that case the corresponding argument array is null.
+            if (mArguments[0] != null) {
+                if (!mProfile.server.equals(mArguments[0][1])) {
+                    throw new IllegalStateException("Invalid server argument for racoon");
+                }
+                mArguments[0][1] = endpointAddressString;
+            }
+
+            if (mArguments[1] != null) {
+                if (!mProfile.server.equals(mArguments[1][2])) {
+                    throw new IllegalStateException("Invalid server argument for mtpd");
+                }
+                mArguments[1][2] = endpointAddressString;
+            }
+        }
+
         private void bringup() {
             // Catch all exceptions so we can clean up a few things.
             try {
+                // resolve never returns null. If it does because of some bug, it will be
+                // caught by the catch() block below and cleanup gracefully.
+                final InetAddress endpointAddress = mDeps.resolve(mProfile.server);
+
+                // Big hack : dynamically replace the address of the server in the arguments
+                // with the resolved address.
+                checkAndFixupArguments(endpointAddress);
+
                 // Initialize the timer.
                 mBringupStartTime = SystemClock.elapsedRealtime();
 
                 // Wait for the daemons to stop.
                 for (String daemon : mDaemons) {
-                    while (!SystemService.isStopped(daemon)) {
+                    while (!mDeps.isServiceStopped(daemon)) {
                         checkInterruptAndDelay(true);
                     }
                 }
 
                 // Clear the previous state.
-                File state = new File("/data/misc/vpn/state");
+                final File state = mDeps.getStateFile();
                 state.delete();
                 if (state.exists()) {
                     throw new IllegalStateException("Cannot delete the state");
@@ -2696,57 +2863,19 @@
 
                     // Start the daemon.
                     String daemon = mDaemons[i];
-                    SystemService.start(daemon);
+                    mDeps.startService(daemon);
 
                     // Wait for the daemon to start.
-                    while (!SystemService.isRunning(daemon)) {
+                    while (!mDeps.isServiceRunning(daemon)) {
                         checkInterruptAndDelay(true);
                     }
 
                     // Create the control socket.
                     mSockets[i] = new LocalSocket();
-                    LocalSocketAddress address = new LocalSocketAddress(
-                            daemon, LocalSocketAddress.Namespace.RESERVED);
 
-                    // Wait for the socket to connect.
-                    while (true) {
-                        try {
-                            mSockets[i].connect(address);
-                            break;
-                        } catch (Exception e) {
-                            // ignore
-                        }
-                        checkInterruptAndDelay(true);
-                    }
-                    mSockets[i].setSoTimeout(500);
-
-                    // Send over the arguments.
-                    OutputStream out = mSockets[i].getOutputStream();
-                    for (String argument : arguments) {
-                        byte[] bytes = argument.getBytes(StandardCharsets.UTF_8);
-                        if (bytes.length >= 0xFFFF) {
-                            throw new IllegalArgumentException("Argument is too large");
-                        }
-                        out.write(bytes.length >> 8);
-                        out.write(bytes.length);
-                        out.write(bytes);
-                        checkInterruptAndDelay(false);
-                    }
-                    out.write(0xFF);
-                    out.write(0xFF);
-
-                    // Wait for End-of-File.
-                    InputStream in = mSockets[i].getInputStream();
-                    while (true) {
-                        try {
-                            if (in.read() == -1) {
-                                break;
-                            }
-                        } catch (Exception e) {
-                            // ignore
-                        }
-                        checkInterruptAndDelay(true);
-                    }
+                    // Wait for the socket to connect and send over the arguments.
+                    mDeps.sendArgumentsToDaemon(daemon, mSockets[i], arguments,
+                            this::checkInterruptAndDelay);
                 }
 
                 // Wait for the daemons to create the new state.
@@ -2754,7 +2883,7 @@
                     // Check if a running daemon is dead.
                     for (int i = 0; i < mDaemons.length; ++i) {
                         String daemon = mDaemons[i];
-                        if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
+                        if (mArguments[i] != null && !mDeps.isServiceRunning(daemon)) {
                             throw new IllegalStateException(daemon + " is dead");
                         }
                     }
@@ -2764,7 +2893,8 @@
                 // Now we are connected. Read and parse the new state.
                 String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
                 if (parameters.length != 7) {
-                    throw new IllegalStateException("Cannot parse the state");
+                    throw new IllegalStateException("Cannot parse the state: '"
+                            + String.join("', '", parameters) + "'");
                 }
 
                 // Set the interface and the addresses in the config.
@@ -2793,20 +2923,15 @@
                 }
 
                 // Add a throw route for the VPN server endpoint, if one was specified.
-                String endpoint = parameters[5].isEmpty() ? mProfile.server : parameters[5];
-                if (!endpoint.isEmpty()) {
-                    try {
-                        InetAddress addr = InetAddress.parseNumericAddress(endpoint);
-                        if (addr instanceof Inet4Address) {
-                            mConfig.routes.add(new RouteInfo(new IpPrefix(addr, 32), RTN_THROW));
-                        } else if (addr instanceof Inet6Address) {
-                            mConfig.routes.add(new RouteInfo(new IpPrefix(addr, 128), RTN_THROW));
-                        } else {
-                            Log.e(TAG, "Unknown IP address family for VPN endpoint: " + endpoint);
-                        }
-                    } catch (IllegalArgumentException e) {
-                        Log.e(TAG, "Exception constructing throw route to " + endpoint + ": " + e);
-                    }
+                if (endpointAddress instanceof Inet4Address) {
+                    mConfig.routes.add(new RouteInfo(
+                            new IpPrefix(endpointAddress, 32), RTN_THROW));
+                } else if (endpointAddress instanceof Inet6Address) {
+                    mConfig.routes.add(new RouteInfo(
+                            new IpPrefix(endpointAddress, 128), RTN_THROW));
+                } else {
+                    Log.e(TAG, "Unknown IP address family for VPN endpoint: "
+                            + endpointAddress);
                 }
 
                 // Here is the last step and it must be done synchronously.
@@ -2818,7 +2943,7 @@
                     checkInterruptAndDelay(false);
 
                     // Check if the interface is gone while we are waiting.
-                    if (jniCheck(mConfig.interfaze) == 0) {
+                    if (mDeps.checkInterfacePresent(Vpn.this, mConfig.interfaze)) {
                         throw new IllegalStateException(mConfig.interfaze + " is gone");
                     }
 
@@ -2849,7 +2974,7 @@
             while (true) {
                 Thread.sleep(2000);
                 for (int i = 0; i < mDaemons.length; i++) {
-                    if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
+                    if (mArguments[i] != null && mDeps.isServiceStopped(mDaemons[i])) {
                         return;
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 0eaac41..9646b9c 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -52,6 +52,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import libcore.io.IoUtils;
 import libcore.util.HexEncoding;
@@ -112,6 +113,7 @@
     private static final String ATTR_GRANTED = "granted";
 
     private final PackageManagerService mService;
+    private final PermissionManagerServiceInternal mPermissionManager;
     private final CookiePersistence mCookiePersistence;
 
     /** State for uninstalled instant apps */
@@ -131,8 +133,10 @@
     @GuardedBy("mService.mLock")
     private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
 
-    public InstantAppRegistry(PackageManagerService service) {
+    public InstantAppRegistry(PackageManagerService service,
+            PermissionManagerServiceInternal permissionManager) {
         mService = service;
+        mPermissionManager = permissionManager;
         mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
     }
 
@@ -861,7 +865,8 @@
         String[] requestedPermissions = new String[pkg.getRequestedPermissions().size()];
         pkg.getRequestedPermissions().toArray(requestedPermissions);
 
-        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
+        Set<String> permissions = mPermissionManager.getGrantedPermissions(
+                pkg.getPackageName(), userId);
         String[] grantedPermissions = new String[permissions.size()];
         permissions.toArray(grantedPermissions);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 58a1648..997c7e3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2970,7 +2970,7 @@
             mHandler = new PackageHandler(mHandlerThread.getLooper());
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-            mInstantAppRegistry = new InstantAppRegistry(this);
+            mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager);
 
             ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
                     = systemConfig.getSharedLibraries();
@@ -4385,14 +4385,13 @@
         final PackageUserState state = ps.readUserState(userId);
         AndroidPackage p = ps.pkg;
         if (p != null) {
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
             // Compute GIDs only if requested
             final int[] gids = (flags & PackageManager.GET_GIDS) == 0
-                    ? EMPTY_INT_ARRAY : permissionsState.computeGids(userId);
+                    ? EMPTY_INT_ARRAY : mPermissionManager.getPackageGids(ps.name, userId);
             // Compute granted permissions only if package has requested permissions
             final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions())
-                    ? Collections.emptySet() : permissionsState.getPermissions(userId);
+                    ? Collections.emptySet()
+                    : mPermissionManager.getGrantedPermissions(ps.name, userId);
 
             PackageInfo packageInfo = PackageInfoUtils.generate(p, gids, flags,
                     ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId, ps);
@@ -4863,13 +4862,13 @@
                 }
                 // TODO: Shouldn't this be checking for package installed state for userId and
                 // return null?
-                return ps.getPermissionsState().computeGids(userId);
+                return mPermissionManager.getPackageGids(packageName, userId);
             }
             if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
                 final PackageSetting ps = mSettings.mPackages.get(packageName);
                 if (ps != null && ps.isMatch(flags)
                         && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    return ps.getPermissionsState().computeGids(userId);
+                    return mPermissionManager.getPackageGids(packageName, userId);
                 }
             }
         }
@@ -24948,9 +24947,7 @@
 
         @Override
         public int[] getPermissionGids(String permissionName, int userId) {
-            synchronized (mLock) {
-                return getPermissionGidsLocked(permissionName, userId);
-            }
+            return mPermissionManager.getPermissionGids(permissionName, userId);
         }
 
         @Override
@@ -25271,16 +25268,6 @@
         return null;
     }
 
-    @GuardedBy("mLock")
-    public int[] getPermissionGidsLocked(String permissionName, int userId) {
-        BasePermission perm
-                = mPermissionManager.getPermissionSettings().getPermission(permissionName);
-        if (perm != null) {
-            return perm.computeGids(userId);
-        }
-        return null;
-    }
-
     @Override
     public int getRuntimePermissionsVersion(@UserIdInt int userId) {
         Preconditions.checkArgumentNonnegative(userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index be93b8f..03771be 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2454,6 +2454,36 @@
         }
     }
 
+    @NonNull
+    private Set<String> getGrantedPermissions(@NonNull String packageName,
+            @UserIdInt int userId) {
+        final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName);
+        if (ps == null) {
+            return null;
+        }
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.getPermissions(userId);
+    }
+
+    @Nullable
+    private int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
+        BasePermission permission = mSettings.getPermission(permissionName);
+        if (permission == null) {
+            return null;
+        }
+        return permission.computeGids(userId);
+    }
+
+    @Nullable
+    private int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) {
+        final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName);
+        if (ps == null) {
+            return null;
+        }
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.computeGids(userId);
+    }
+
     /**
      * Restore the permission state for a package.
      *
@@ -4650,6 +4680,22 @@
         public void removeAllPermissions(AndroidPackage pkg, boolean chatty) {
             PermissionManagerService.this.removeAllPermissions(pkg, chatty);
         }
+        @NonNull
+        @Override
+        public Set<String> getGrantedPermissions(@NonNull String packageName,
+                @UserIdInt int userId) {
+            return PermissionManagerService.this.getGrantedPermissions(packageName, userId);
+        }
+        @Nullable
+        @Override
+        public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
+            return PermissionManagerService.this.getPermissionGids(permissionName, userId);
+        }
+        @Nullable
+        @Override
+        public int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) {
+            return PermissionManagerService.this.getPackageGids(packageName, userId);
+        }
         @Override
         public void grantRequestedRuntimePermissions(AndroidPackage pkg, int[] userIds,
                 String[] grantedPermissions, int callingUid) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 2e83b23..cfa371d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -28,6 +28,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -263,6 +264,25 @@
     public abstract void addAllPermissionGroups(@NonNull AndroidPackage pkg, boolean chatty);
     public abstract void removeAllPermissions(@NonNull AndroidPackage pkg, boolean chatty);
 
+    /**
+     * Get all the permissions granted to a package.
+     */
+    @NonNull
+    public abstract Set<String> getGrantedPermissions(@NonNull String packageName,
+            @UserIdInt int userId);
+
+    /**
+     * Get the GIDs of a permission.
+     */
+    @Nullable
+    public abstract int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId);
+
+    /**
+     * Get the GIDs computed from the permission state of a package.
+     */
+    @Nullable
+    public abstract int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId);
+
     /** Retrieve the packages that have requested the given app op permission */
     public abstract @Nullable String[] getAppOpPermissionPackages(
             @NonNull String permName, int callingUid);
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 37bf664..33317a3 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
 import android.app.prediction.AppPredictionContext;
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.AppTarget;
@@ -26,8 +28,11 @@
 import android.app.prediction.IPredictionCallback;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.os.Binder;
 import android.os.CancellationSignal;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
 
@@ -35,6 +40,7 @@
 import com.android.server.SystemService;
 import com.android.server.people.data.DataManager;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -68,6 +74,7 @@
 
     @Override
     public void onStart() {
+        publishBinderService(Context.PEOPLE_SERVICE, new BinderService());
         publishLocalService(PeopleServiceInternal.class, new LocalService());
     }
 
@@ -81,6 +88,38 @@
         mDataManager.onUserStopping(user.getUserIdentifier());
     }
 
+    /**
+     * Enforces that only the system or root UID can make certain calls.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller is not system or root
+     */
+    private static void enforceSystemOrRoot(String message) {
+        int uid = Binder.getCallingUid();
+        if (!UserHandle.isSameApp(uid, Process.SYSTEM_UID) && uid != Process.ROOT_UID) {
+            throw new SecurityException("Only system may " + message);
+        }
+    }
+
+    private final class BinderService extends IPeopleManager.Stub {
+
+        @Override
+        public ParceledListSlice<ConversationChannel> getRecentConversations() {
+            enforceSystemOrRoot("get recent conversations");
+            return new ParceledListSlice<>(new ArrayList<>());
+        }
+
+        @Override
+        public void removeRecentConversation(String packageName, int userId, String shortcutId) {
+            enforceSystemOrRoot("remove a recent conversation");
+        }
+
+        @Override
+        public void removeAllRecentConversations() {
+            enforceSystemOrRoot("remove all recent conversations");
+        }
+    }
+
     @VisibleForTesting
     final class LocalService extends PeopleServiceInternal {
 
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index cd9b6ac..cbebe69 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -1092,9 +1092,11 @@
         registerUser(backupManagerService, mUserOneId, mUserOneService);
         setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);
 
-        backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT);
+        backupManagerService.beginRestoreSession(mUserOneId, TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
 
-        verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT);
+        verify(mUserOneService).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
     }
 
     /** Test that the backup service does not route methods for non-registered users. */
@@ -1104,9 +1106,11 @@
         registerUser(backupManagerService, mUserOneId, mUserOneService);
         setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);
 
-        backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT);
+        backupManagerService.beginRestoreSession(mUserTwoId, TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
 
-        verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT);
+        verify(mUserOneService, never()).beginRestoreSession(TEST_PACKAGE, TEST_TRANSPORT,
+                OperationType.BACKUP);
     }
 
     /** Test that the backup service routes methods correctly to the user that requests it. */
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index 3fc421d..5883c1c 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -57,6 +57,7 @@
 import com.android.server.backup.testing.TransportData;
 import com.android.server.backup.testing.TransportTestUtils;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 import com.android.server.testing.shadows.ShadowEventLog;
 import com.android.server.testing.shadows.ShadowPerformUnifiedRestoreTask;
@@ -96,6 +97,7 @@
     @Mock private TransportManager mTransportManager;
     @Mock private IRestoreObserver mObserver;
     @Mock private IBackupManagerMonitor mMonitor;
+    @Mock private BackupEligibilityRules mBackupEligibilityRules;
     private ShadowLooper mShadowBackupLooper;
     private ShadowApplication mShadowApplication;
     private UserBackupManagerService.BackupWakeLock mWakeLock;
@@ -576,7 +578,8 @@
     private IRestoreSession createActiveRestoreSession(
             String packageName, TransportData transport) {
         return new ActiveRestoreSession(
-                mBackupManagerService, packageName, transport.transportName);
+                mBackupManagerService, packageName, transport.transportName,
+                mBackupEligibilityRules);
     }
 
     private IRestoreSession createActiveRestoreSessionWithRestoreSets(
@@ -584,7 +587,8 @@
             throws RemoteException {
         ActiveRestoreSession restoreSession =
                 new ActiveRestoreSession(
-                        mBackupManagerService, packageName, transport.transportName);
+                        mBackupManagerService, packageName, transport.transportName,
+                        mBackupEligibilityRules);
         restoreSession.setRestoreSets(restoreSets);
         return restoreSession;
     }
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
index bc478b0..207f10a 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SystemStubMultiUserDisableUninstallTest.kt
@@ -104,8 +104,11 @@
     }
 
     private val tempFolder = TemporaryFolder()
+
+    // TODO(b/160159215): Use START_STOP rather than FULL once it's fixed. This will drastically
+    //  improve pre/post-submit times.
     private val preparer: SystemPreparer = SystemPreparer(tempFolder,
-            SystemPreparer.RebootStrategy.START_STOP, deviceRebootRule) { this.device }
+            SystemPreparer.RebootStrategy.FULL, deviceRebootRule) { this.device }
 
     @get:Rule
     val rules = RuleChain.outerRule(tempFolder).let {
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 4ccf79a..de1c5759 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -30,6 +30,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -49,6 +50,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
@@ -65,6 +67,7 @@
 import android.net.IpPrefix;
 import android.net.IpSecManager;
 import android.net.LinkProperties;
+import android.net.LocalSocket;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
@@ -74,6 +77,7 @@
 import android.net.VpnService;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Process;
@@ -101,13 +105,20 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.net.Inet4Address;
+import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Stream;
 
 /**
@@ -133,7 +144,8 @@
         managedProfileA.profileGroupId = primaryUser.id;
     }
 
-    static final String TEST_VPN_PKG = "com.dummy.vpn";
+    static final String EGRESS_IFACE = "wlan0";
+    static final String TEST_VPN_PKG = "com.testvpn.vpn";
     private static final String TEST_VPN_SERVER = "1.2.3.4";
     private static final String TEST_VPN_IDENTITY = "identity";
     private static final byte[] TEST_VPN_PSK = "psk".getBytes();
@@ -1012,31 +1024,190 @@
         // a subsequent CL.
     }
 
-    @Test
-    public void testStartLegacyVpn() throws Exception {
+    public Vpn startLegacyVpn(final VpnProfile vpnProfile) throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
 
         // Dummy egress interface
-        final String egressIface = "DUMMY0";
         final LinkProperties lp = new LinkProperties();
-        lp.setInterfaceName(egressIface);
+        lp.setInterfaceName(EGRESS_IFACE);
 
         final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
-                        InetAddresses.parseNumericAddress("192.0.2.0"), egressIface);
+                        InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
         lp.addRoute(defaultRoute);
 
-        vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp);
+        vpn.startLegacyVpn(vpnProfile, mKeyStore, lp);
+        return vpn;
+    }
 
+    @Test
+    public void testStartPlatformVpn() throws Exception {
+        startLegacyVpn(mVpnProfile);
         // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
-        // a subsequent CL.
+        // a subsequent patch.
+    }
+
+    @Test
+    public void testStartRacoonNumericAddress() throws Exception {
+        startRacoon("1.2.3.4", "1.2.3.4");
+    }
+
+    @Test
+    public void testStartRacoonHostname() throws Exception {
+        startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
+    }
+
+    public void startRacoon(final String serverAddr, final String expectedAddr)
+            throws Exception {
+        final ConditionVariable legacyRunnerReady = new ConditionVariable();
+        final VpnProfile profile = new VpnProfile("testProfile" /* key */);
+        profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
+        profile.name = "testProfileName";
+        profile.username = "userName";
+        profile.password = "thePassword";
+        profile.server = serverAddr;
+        profile.ipsecIdentifier = "id";
+        profile.ipsecSecret = "secret";
+        profile.l2tpSecret = "l2tpsecret";
+        when(mConnectivityManager.getAllNetworks())
+            .thenReturn(new Network[] { new Network(101) });
+        when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
+                anyInt(), any(), anyInt())).thenAnswer(invocation -> {
+                    // The runner has registered an agent and is now ready.
+                    legacyRunnerReady.open();
+                    return new Network(102);
+                });
+        final Vpn vpn = startLegacyVpn(profile);
+        final TestDeps deps = (TestDeps) vpn.mDeps;
+        try {
+            // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK
+            assertArrayEquals(
+                    new String[] { EGRESS_IFACE, expectedAddr, "udppsk",
+                            profile.ipsecIdentifier, profile.ipsecSecret, "1701" },
+                    deps.racoonArgs.get(10, TimeUnit.SECONDS));
+            // literal values are hardcoded in Vpn.java for mtpd args
+            assertArrayEquals(
+                    new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
+                            "name", profile.username, "password", profile.password,
+                            "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
+                            "idle", "1800", "mtu", "1400", "mru", "1400" },
+                    deps.mtpdArgs.get(10, TimeUnit.SECONDS));
+            // Now wait for the runner to be ready before testing for the route.
+            legacyRunnerReady.block(10_000);
+            // In this test the expected address is always v4 so /32
+            final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"),
+                    RouteInfo.RTN_THROW);
+            assertTrue("Routes lack the expected throw route (" + expectedRoute + ") : "
+                    + vpn.mConfig.routes,
+                    vpn.mConfig.routes.contains(expectedRoute));
+        } finally {
+            // Now interrupt the thread, unblock the runner and clean up.
+            vpn.mVpnRunner.exitVpnRunner();
+            deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
+            vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
+        }
+    }
+
+    private static final class TestDeps extends Vpn.Dependencies {
+        public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
+        public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
+        public final File mStateFile;
+
+        private final HashMap<String, Boolean> mRunningServices = new HashMap<>();
+
+        TestDeps() {
+            try {
+                mStateFile = File.createTempFile("vpnTest", ".tmp");
+                mStateFile.deleteOnExit();
+            } catch (final IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public void startService(final String serviceName) {
+            mRunningServices.put(serviceName, true);
+        }
+
+        @Override
+        public void stopService(final String serviceName) {
+            mRunningServices.put(serviceName, false);
+        }
+
+        @Override
+        public boolean isServiceRunning(final String serviceName) {
+            return mRunningServices.getOrDefault(serviceName, false);
+        }
+
+        @Override
+        public boolean isServiceStopped(final String serviceName) {
+            return !isServiceRunning(serviceName);
+        }
+
+        @Override
+        public File getStateFile() {
+            return mStateFile;
+        }
+
+        @Override
+        public void sendArgumentsToDaemon(
+                final String daemon, final LocalSocket socket, final String[] arguments,
+                final Vpn.RetryScheduler interruptChecker) throws IOException {
+            if ("racoon".equals(daemon)) {
+                racoonArgs.complete(arguments);
+            } else if ("mtpd".equals(daemon)) {
+                writeStateFile(arguments);
+                mtpdArgs.complete(arguments);
+            } else {
+                throw new UnsupportedOperationException("Unsupported daemon : " + daemon);
+            }
+        }
+
+        private void writeStateFile(final String[] arguments) throws IOException {
+            mStateFile.delete();
+            mStateFile.createNewFile();
+            mStateFile.deleteOnExit();
+            final BufferedWriter writer = new BufferedWriter(
+                    new FileWriter(mStateFile, false /* append */));
+            writer.write(EGRESS_IFACE);
+            writer.write("\n");
+            // addresses
+            writer.write("10.0.0.1/24\n");
+            // routes
+            writer.write("192.168.6.0/24\n");
+            // dns servers
+            writer.write("192.168.6.1\n");
+            // search domains
+            writer.write("vpn.searchdomains.com\n");
+            // endpoint - intentionally empty
+            writer.write("\n");
+            writer.flush();
+            writer.close();
+        }
+
+        @Override
+        @NonNull
+        public InetAddress resolve(final String endpoint) {
+            try {
+                // If a numeric IP address, return it.
+                return InetAddress.parseNumericAddress(endpoint);
+            } catch (IllegalArgumentException e) {
+                // Otherwise, return some token IP to test for.
+                return InetAddress.parseNumericAddress("5.6.7.8");
+            }
+        }
+
+        @Override
+        public boolean checkInterfacePresent(final Vpn vpn, final String iface) {
+            return true;
+        }
     }
 
     /**
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
-        return new Vpn(Looper.myLooper(), mContext, mNetService,
+        return new Vpn(Looper.myLooper(), mContext, new TestDeps(), mNetService,
                 userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
     }