Merge "Fix NetworkPolicyManagerServiceTest."
diff --git a/Android.mk b/Android.mk
index 1da4783..a1e9ed9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -295,6 +295,10 @@
 	core/java/android/print/IWriteResultCallback.aidl \
 	core/java/android/printservice/IPrintService.aidl \
 	core/java/android/printservice/IPrintServiceClient.aidl \
+	core/java/android/companion/ICompanionDeviceManager.aidl \
+	core/java/android/companion/ICompanionDeviceManagerService.aidl \
+	core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl \
+	core/java/android/companion/IOnAssociateCallback.aidl \
 	core/java/android/service/dreams/IDreamManager.aidl \
 	core/java/android/service/dreams/IDreamService.aidl \
 	core/java/android/service/persistentdata/IPersistentDataBlockService.aidl \
diff --git a/api/current.txt b/api/current.txt
index e315958..d772181 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2688,11 +2688,25 @@
 
 package android.accessibilityservice {
 
+  public final class AccessibilityButtonController {
+    method public boolean isAccessibilityButtonAvailable();
+    method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+    method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback, android.os.Handler);
+    method public void unregisterAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+  }
+
+  public static abstract class AccessibilityButtonController.AccessibilityButtonCallback {
+    ctor public AccessibilityButtonController.AccessibilityButtonCallback();
+    method public void onAvailabilityChanged(android.accessibilityservice.AccessibilityButtonController, boolean);
+    method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
+  }
+
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public final void disableSelf();
     method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
     method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
     method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2805,6 +2819,7 @@
     field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
     field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
     field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+    field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
     field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
     field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
@@ -7950,6 +7965,65 @@
 
 }
 
+package android.companion {
+
+  public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
+  }
+
+  public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> {
+    method public android.companion.AssociationRequest<F> build();
+    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice();
+    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice();
+    method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F);
+    method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean);
+  }
+
+  public final class BluetoothDeviceFilter implements android.companion.DeviceFilter {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.BluetoothDeviceFilter> CREATOR;
+  }
+
+  public static final class BluetoothDeviceFilter.Builder {
+    ctor public BluetoothDeviceFilter.Builder();
+    method public android.companion.BluetoothDeviceFilter.Builder addServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid);
+    method public android.companion.BluetoothDeviceFilter build();
+    method public android.companion.BluetoothDeviceFilter.Builder setAddress(java.lang.String);
+    method public android.companion.BluetoothDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+  }
+
+  public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;
+  }
+
+  public static final class BluetoothLEDeviceFilter.Builder {
+    ctor public BluetoothLEDeviceFilter.Builder();
+    method public android.companion.BluetoothLEDeviceFilter build();
+    method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+    method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);
+  }
+
+  public final class CompanionDeviceManager {
+    method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
+    field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+  }
+
+  public static abstract class CompanionDeviceManager.Callback {
+    ctor public CompanionDeviceManager.Callback();
+    method public abstract void onDeviceFound(android.content.IntentSender);
+    method public abstract void onFailure(java.lang.CharSequence);
+  }
+
+  public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {
+  }
+
+}
+
 package android.content {
 
   public abstract class AbstractThreadedSyncAdapter {
@@ -8562,6 +8636,7 @@
     field public static final java.lang.String CAPTIONING_SERVICE = "captioning";
     field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config";
     field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard";
+    field public static final java.lang.String COMPANION_DEVICE_SERVICE = "companion_device";
     field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity";
     field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir";
     field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
@@ -50554,6 +50629,7 @@
     method public void setIs24HourView(java.lang.Boolean);
     method public void setMinute(int);
     method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
+    method public boolean validateInput();
   }
 
   public static abstract interface TimePicker.OnTimeChangedListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index ae145dc..6affa7f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2807,11 +2807,25 @@
 
 package android.accessibilityservice {
 
+  public final class AccessibilityButtonController {
+    method public boolean isAccessibilityButtonAvailable();
+    method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+    method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback, android.os.Handler);
+    method public void unregisterAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+  }
+
+  public static abstract class AccessibilityButtonController.AccessibilityButtonCallback {
+    ctor public AccessibilityButtonController.AccessibilityButtonCallback();
+    method public void onAvailabilityChanged(android.accessibilityservice.AccessibilityButtonController, boolean);
+    method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
+  }
+
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public final void disableSelf();
     method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
     method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
     method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2924,6 +2938,7 @@
     field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
     field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
     field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+    field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
     field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
     field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
@@ -8340,6 +8355,65 @@
 
 }
 
+package android.companion {
+
+  public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
+  }
+
+  public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> {
+    method public android.companion.AssociationRequest<F> build();
+    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice();
+    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice();
+    method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F);
+    method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean);
+  }
+
+  public final class BluetoothDeviceFilter implements android.companion.DeviceFilter {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.BluetoothDeviceFilter> CREATOR;
+  }
+
+  public static final class BluetoothDeviceFilter.Builder {
+    ctor public BluetoothDeviceFilter.Builder();
+    method public android.companion.BluetoothDeviceFilter.Builder addServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid);
+    method public android.companion.BluetoothDeviceFilter build();
+    method public android.companion.BluetoothDeviceFilter.Builder setAddress(java.lang.String);
+    method public android.companion.BluetoothDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+  }
+
+  public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;
+  }
+
+  public static final class BluetoothLEDeviceFilter.Builder {
+    ctor public BluetoothLEDeviceFilter.Builder();
+    method public android.companion.BluetoothLEDeviceFilter build();
+    method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+    method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);
+  }
+
+  public final class CompanionDeviceManager {
+    method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
+    field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+  }
+
+  public static abstract class CompanionDeviceManager.Callback {
+    ctor public CompanionDeviceManager.Callback();
+    method public abstract void onDeviceFound(android.content.IntentSender);
+    method public abstract void onFailure(java.lang.CharSequence);
+  }
+
+  public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {
+  }
+
+}
+
 package android.content {
 
   public abstract class AbstractThreadedSyncAdapter {
@@ -8959,6 +9033,7 @@
     field public static final java.lang.String CAPTIONING_SERVICE = "captioning";
     field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config";
     field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard";
+    field public static final java.lang.String COMPANION_DEVICE_SERVICE = "companion_device";
     field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity";
     field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir";
     field public static final java.lang.String CONTEXTHUB_SERVICE = "contexthub";
@@ -54325,6 +54400,7 @@
     method public void setIs24HourView(java.lang.Boolean);
     method public void setMinute(int);
     method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
+    method public boolean validateInput();
   }
 
   public static abstract interface TimePicker.OnTimeChangedListener {
diff --git a/api/test-current.txt b/api/test-current.txt
index 1e3b6db..bbb7f89 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2688,11 +2688,25 @@
 
 package android.accessibilityservice {
 
+  public final class AccessibilityButtonController {
+    method public boolean isAccessibilityButtonAvailable();
+    method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+    method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback, android.os.Handler);
+    method public void unregisterAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+  }
+
+  public static abstract class AccessibilityButtonController.AccessibilityButtonCallback {
+    ctor public AccessibilityButtonController.AccessibilityButtonCallback();
+    method public void onAvailabilityChanged(android.accessibilityservice.AccessibilityButtonController, boolean);
+    method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
+  }
+
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
     method public final void disableSelf();
     method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+    method public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
     method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
     method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2805,6 +2819,7 @@
     field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
     field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
     field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+    field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
     field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
     field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
@@ -7973,6 +7988,65 @@
 
 }
 
+package android.companion {
+
+  public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
+  }
+
+  public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> {
+    method public android.companion.AssociationRequest<F> build();
+    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice();
+    method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice();
+    method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F);
+    method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean);
+  }
+
+  public final class BluetoothDeviceFilter implements android.companion.DeviceFilter {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.BluetoothDeviceFilter> CREATOR;
+  }
+
+  public static final class BluetoothDeviceFilter.Builder {
+    ctor public BluetoothDeviceFilter.Builder();
+    method public android.companion.BluetoothDeviceFilter.Builder addServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid);
+    method public android.companion.BluetoothDeviceFilter build();
+    method public android.companion.BluetoothDeviceFilter.Builder setAddress(java.lang.String);
+    method public android.companion.BluetoothDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+  }
+
+  public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;
+  }
+
+  public static final class BluetoothLEDeviceFilter.Builder {
+    ctor public BluetoothLEDeviceFilter.Builder();
+    method public android.companion.BluetoothLEDeviceFilter build();
+    method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+    method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);
+  }
+
+  public final class CompanionDeviceManager {
+    method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
+    field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+  }
+
+  public static abstract class CompanionDeviceManager.Callback {
+    ctor public CompanionDeviceManager.Callback();
+    method public abstract void onDeviceFound(android.content.IntentSender);
+    method public abstract void onFailure(java.lang.CharSequence);
+  }
+
+  public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {
+  }
+
+}
+
 package android.content {
 
   public abstract class AbstractThreadedSyncAdapter {
@@ -8587,6 +8661,7 @@
     field public static final java.lang.String CAPTIONING_SERVICE = "captioning";
     field public static final java.lang.String CARRIER_CONFIG_SERVICE = "carrier_config";
     field public static final java.lang.String CLIPBOARD_SERVICE = "clipboard";
+    field public static final java.lang.String COMPANION_DEVICE_SERVICE = "companion_device";
     field public static final java.lang.String CONNECTIVITY_SERVICE = "connectivity";
     field public static final java.lang.String CONSUMER_IR_SERVICE = "consumer_ir";
     field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
@@ -50879,6 +50954,7 @@
     method public void setIs24HourView(java.lang.Boolean);
     method public void setMinute(int);
     method public void setOnTimeChangedListener(android.widget.TimePicker.OnTimeChangedListener);
+    method public boolean validateInput();
     field public static final int MODE_CLOCK = 2; // 0x2
     field public static final int MODE_SPINNER = 1; // 0x1
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityButtonController.java b/core/java/android/accessibilityservice/AccessibilityButtonController.java
new file mode 100644
index 0000000..c3a5dab
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityButtonController.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+/**
+ * Controller for the accessibility button within the system's navigation area
+ * <p>
+ * This class may be used to query the accessibility button's state and register
+ * callbacks for interactions with and state changes to the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class and
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as
+ * the sole means for offering functionality to users via an {@link AccessibilityService}.
+ * Some device implementations may choose not to provide a software-rendered system
+ * navigation area, making this affordance permanently unavailable.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> On device implementations where the accessibility button is
+ * supported, it may not be available at all times, such as when a foreground application uses
+ * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign
+ * this button to another accessibility service or feature. In each of these cases, a
+ * registered {@link AccessibilityButtonCallback}'s
+ * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)}
+ * method will be invoked to provide notifications of changes in the accessibility button's
+ * availability to the registering service.
+ * </p>
+ */
+public final class AccessibilityButtonController {
+    private static final String LOG_TAG = "A11yButtonController";
+
+    private final IAccessibilityServiceConnection mServiceConnection;
+    private final Object mLock;
+    private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks;
+
+    AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) {
+        mServiceConnection = serviceConnection;
+        mLock = new Object();
+    }
+
+    /**
+     * Retrieves whether the accessibility button in the system's navigation area is
+     * available to the calling service.
+     * <p>
+     * <strong>Note:</strong> If the service is not yet connected (e.g.
+     * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+     * service has been disconnected, this method will have no effect and return {@code false}.
+     * </p>
+     *
+     * @return {@code true} if the accessibility button in the system's navigation area is
+     * available to the calling service, {@code false} otherwise
+     */
+    public boolean isAccessibilityButtonAvailable() {
+        try {
+            return mServiceConnection.isAccessibilityButtonAvailable();
+        } catch (RemoteException re) {
+            Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re);
+            re.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
+    /**
+     * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+     * changes callbacks related to the accessibility button.
+     *
+     * @param callback the callback to add, must be non-null
+     */
+    public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) {
+        registerAccessibilityButtonCallback(callback, null);
+    }
+
+    /**
+     * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+     * change callbacks related to the accessibility button. The callback will occur on the
+     * specified {@link Handler}'s thread, or on the services's main thread if the handler is
+     * {@code null}.
+     *
+     * @param callback the callback to add, must be non-null
+     * @param handler the handler on which to callback should execute, or {@code null} to
+     *                execute on the service's main thread
+     */
+    public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback,
+            @Nullable Handler handler) {
+        synchronized (mLock) {
+            if (mCallbacks == null) {
+                mCallbacks = new ArrayMap<>();
+            }
+
+            mCallbacks.put(callback, handler);
+        }
+    }
+
+    /**
+     * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state
+     * change callbacks related to the accessibility button.
+     *
+     * @param callback the callback to remove, must be non-null
+     */
+    public void unregisterAccessibilityButtonCallback(
+            @NonNull AccessibilityButtonCallback callback) {
+        synchronized (mLock) {
+            if (mCallbacks == null) {
+                return;
+            }
+
+            final int keyIndex = mCallbacks.indexOfKey(callback);
+            final boolean hasKey = keyIndex >= 0;
+            if (hasKey) {
+                mCallbacks.removeAt(keyIndex);
+            }
+        }
+    }
+
+    /**
+     * Dispatches the accessibility button click to any registered callbacks. This should
+     * be called on the service's main thread.
+     */
+    void dispatchAccessibilityButtonClicked() {
+        final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+        synchronized (mLock) {
+            if (mCallbacks == null || mCallbacks.isEmpty()) {
+                Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!");
+                return;
+            }
+
+            // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+            // modification.
+            entries = new ArrayMap<>(mCallbacks);
+        }
+
+        for (int i = 0, count = entries.size(); i < count; i++) {
+            final AccessibilityButtonCallback callback = entries.keyAt(i);
+            final Handler handler = entries.valueAt(i);
+            if (handler != null) {
+                handler.post(() -> callback.onClicked(this));
+            } else {
+                // We're already on the main thread, just run the callback.
+                callback.onClicked(this);
+            }
+        }
+    }
+
+    /**
+     * Dispatches the accessibility button availability changes to any registered callbacks.
+     * This should be called on the service's main thread.
+     */
+    void dispatchAccessibilityButtonAvailabilityChanged(boolean available) {
+        final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+        synchronized (mLock) {
+            if (mCallbacks == null || mCallbacks.isEmpty()) {
+                Slog.w(LOG_TAG,
+                        "Received accessibility button availability change with no callbacks!");
+                return;
+            }
+
+            // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+            // modification.
+            entries = new ArrayMap<>(mCallbacks);
+        }
+
+        for (int i = 0, count = entries.size(); i < count; i++) {
+            final AccessibilityButtonCallback callback = entries.keyAt(i);
+            final Handler handler = entries.valueAt(i);
+            if (handler != null) {
+                handler.post(() -> callback.onAvailabilityChanged(this, available));
+            } else {
+                // We're already on the main thread, just run the callback.
+                callback.onAvailabilityChanged(this, available);
+            }
+        }
+    }
+
+    /**
+     * Callback for interaction with and changes to state of the accessibility button
+     * within the system's navigation area.
+     */
+    public static abstract class AccessibilityButtonCallback {
+
+        /**
+         * Called when the accessibility button in the system's navigation area is clicked.
+         *
+         * @param controller the controller used to register for this callback
+         */
+        public void onClicked(AccessibilityButtonController controller) {}
+
+        /**
+         * Called when the availability of the accessibility button in the system's
+         * navigation area has changed. The accessibility button may become unavailable
+         * because the device shopped showing the button, the button was assigned to another
+         * service, or for other reasons.
+         *
+         * @param controller the controller used to register for this callback
+         * @param available {@code true} if the accessibility button is available to this
+         *                  service, {@code false} otherwise
+         */
+        public void onAvailabilityChanged(AccessibilityButtonController controller,
+                boolean available) {
+        }
+    }
+}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a036b6a..9e486d5 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -378,6 +378,8 @@
         void onPerformGestureResult(int sequence, boolean completedSuccessfully);
         void onFingerprintCapturingGesturesChanged(boolean active);
         void onFingerprintGesture(int gesture);
+        void onAccessibilityButtonClicked();
+        void onAccessibilityButtonAvailabilityChanged(boolean available);
     }
 
     /**
@@ -400,6 +402,7 @@
 
     private MagnificationController mMagnificationController;
     private SoftKeyboardController mSoftKeyboardController;
+    private AccessibilityButtonController mAccessibilityButtonController;
 
     private int mGestureStatusCallbackSequence;
 
@@ -431,6 +434,9 @@
         if (mMagnificationController != null) {
             mMagnificationController.onServiceConnected();
         }
+        if (mSoftKeyboardController != null) {
+            mSoftKeyboardController.onServiceConnected();
+        }
 
         // The client gets to handle service connection last, after we've set
         // up any state upon which their code may rely.
@@ -809,12 +815,10 @@
         }
 
         /**
-         * Removes all instances of the specified change listener from the list
-         * of magnification change listeners.
+         * Removes the specified change listener from the list of magnification change listeners.
          *
          * @param listener the listener to remove, must be non-null
-         * @return {@code true} if at least one instance of the listener was
-         *         removed
+         * @return {@code true} if the listener was removed, {@code false} otherwise
          */
         public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
             if (mListeners == null) {
@@ -1203,11 +1207,11 @@
         }
 
         /**
-         * Removes all instances of the specified change listener from the list of magnification
-         * change listeners.
+         * Removes the specified change listener from the list of keyboard show mode change
+         * listeners.
          *
          * @param listener the listener to remove, must be non-null
-         * @return {@code true} if at least one instance of the listener was removed
+         * @return {@code true} if the listener was removed, {@code false} otherwise
          */
         public boolean removeOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
             if (mListeners == null) {
@@ -1252,7 +1256,7 @@
             final ArrayMap<OnShowModeChangedListener, Handler> entries;
             synchronized (mLock) {
                 if (mListeners == null || mListeners.isEmpty()) {
-                    Slog.d(LOG_TAG, "Received soft keyboard show mode changed callback"
+                    Slog.w(LOG_TAG, "Received soft keyboard show mode changed callback"
                             + " with no listeners registered!");
                     setSoftKeyboardCallbackEnabled(false);
                     return;
@@ -1308,9 +1312,9 @@
          * The lastto this method will be honored, regardless of any previous calls (including those
          * made by other AccessibilityServices).
          * <p>
-         * <strong>Note:</strong> If the service is not yet conected (e.g.
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
          * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
-         * service has been disconnected, this method will hav no effect and return {@code false}.
+         * service has been disconnected, this method will have no effect and return {@code false}.
          *
          * @param showMode the new show mode for the soft keyboard
          * @return {@code true} on success
@@ -1349,6 +1353,39 @@
     }
 
     /**
+     * Returns the controller for the accessibility button within the system's navigation area.
+     * This instance may be used to query the accessibility button's state and register listeners
+     * for interactions with and state changes for the accessibility button when
+     * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+     * <p>
+     * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+     * within a navigation area, and as such, use of this class should be considered only as an
+     * optional feature or shortcut on supported device implementations.
+     * </p>
+     *
+     * @return the accessibility button controller for this {@link AccessibilityService}
+     */
+    @NonNull
+    public final AccessibilityButtonController getAccessibilityButtonController() {
+        synchronized (mLock) {
+            if (mAccessibilityButtonController == null) {
+                mAccessibilityButtonController = new AccessibilityButtonController(
+                        AccessibilityInteractionClient.getInstance().getConnection(mConnectionId));
+            }
+            return mAccessibilityButtonController;
+        }
+    }
+
+    private void onAccessibilityButtonClicked() {
+        getAccessibilityButtonController().dispatchAccessibilityButtonClicked();
+    }
+
+    private void onAccessibilityButtonAvailabilityChanged(boolean available) {
+        getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
+                available);
+    }
+
+    /**
      * Performs a global action. Such an action can be performed
      * at any moment regardless of the current application or user
      * location in that application. For example going back, going
@@ -1543,6 +1580,16 @@
             public void onFingerprintGesture(int gesture) {
                 AccessibilityService.this.onFingerprintGesture(gesture);
             }
+
+            @Override
+            public void onAccessibilityButtonClicked() {
+                AccessibilityService.this.onAccessibilityButtonClicked();
+            }
+
+            @Override
+            public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+                AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
+            }
         });
     }
 
@@ -1565,6 +1612,8 @@
         private static final int DO_GESTURE_COMPLETE = 9;
         private static final int DO_ON_FINGERPRINT_ACTIVE_CHANGED = 10;
         private static final int DO_ON_FINGERPRINT_GESTURE = 11;
+        private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
+        private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
 
         private final HandlerCaller mCaller;
 
@@ -1645,6 +1694,17 @@
             mCaller.sendMessage(mCaller.obtainMessageI(DO_ON_FINGERPRINT_GESTURE, gesture));
         }
 
+        public void onAccessibilityButtonClicked() {
+            final Message message = mCaller.obtainMessage(DO_ACCESSIBILITY_BUTTON_CLICKED);
+            mCaller.sendMessage(message);
+        }
+
+        public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+            final Message message = mCaller.obtainMessageI(
+                    DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED, (available ? 1 : 0));
+            mCaller.sendMessage(message);
+        }
+
         @Override
         public void executeMessage(Message message) {
             switch (message.what) {
@@ -1750,6 +1810,15 @@
                     mCallback.onFingerprintGesture(message.arg1);
                 } return;
 
+                case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
+                    mCallback.onAccessibilityButtonClicked();
+                } return;
+
+                case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
+                    final boolean available = (message.arg1 != 0);
+                    mCallback.onAccessibilityButtonAvailabilityChanged(available);
+                } return;
+
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 18e57cb..e135ffd 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -306,6 +306,12 @@
      */
     public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 0x00000080;
 
+     /**
+     * This flag indicates to the system that the accessibility service requests that an
+     * accessibility button be shown within the system's navigation area, if available.
+     */
+    public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x00000100;
+
     /**
      * This flag requests that all fingerprint gestures be sent to the accessibility service.
      * It is handled in {@link FingerprintGestureController}
@@ -396,6 +402,8 @@
      * @see #FLAG_REQUEST_FILTER_KEY_EVENTS
      * @see #FLAG_REPORT_VIEW_IDS
      * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
+     * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
+     * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
      */
     public int flags;
 
@@ -631,7 +639,7 @@
      * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
      * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
      * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
-     * @see #CAPABILITY_FILTER_KEY_EVENTS
+     * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
      * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
      * @see #CAPABILITY_CAN_PERFORM_GESTURES
      */
@@ -648,7 +656,7 @@
      * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
      * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
      * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
-     * @see #CAPABILITY_FILTER_KEY_EVENTS
+     * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
      * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
      * @see #CAPABILITY_CAN_PERFORM_GESTURES
      *
@@ -936,6 +944,8 @@
                 return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS";
             case FLAG_ENABLE_ACCESSIBILITY_VOLUME:
                 return "FLAG_ENABLE_ACCESSIBILITY_VOLUME";
+            case FLAG_REQUEST_ACCESSIBILITY_BUTTON:
+                return "FLAG_REQUEST_ACCESSIBILITY_BUTTON";
             case FLAG_CAPTURE_FINGERPRINT_GESTURES:
                 return "FLAG_CAPTURE_FINGERPRINT_GESTURES";
             default:
@@ -960,7 +970,7 @@
             case CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
                 return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
             case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
-                return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+                return "CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS";
             case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
                 return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
             case CAPABILITY_CAN_PERFORM_GESTURES:
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 3f778ad..4e96b8f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -50,4 +50,8 @@
     void onFingerprintCapturingGesturesChanged(boolean capturing);
 
     void onFingerprintGesture(int gesture);
+
+    void onAccessibilityButtonClicked();
+
+    void onAccessibilityButtonAvailabilityChanged(boolean available);
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5499bd5..5bd37220 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -88,6 +88,8 @@
 
     void setSoftKeyboardCallbackEnabled(boolean enabled);
 
+    boolean isAccessibilityButtonAvailable();
+
     void sendGesture(int sequence, in ParceledListSlice gestureSteps);
 
     boolean isFingerprintGestureDetectionAvailable();
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 4f68ec7..b7c0737 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -2077,8 +2077,6 @@
             } else {
                 record.trackAddedFragmentsInPop(mTmpAddedFragments);
             }
-            final int bumpAmount = isPop ? -1 : 1;
-            record.bumpBackStackNesting(bumpAmount);
             addToBackStack = addToBackStack || record.mAddToBackStack;
         }
         mTmpAddedFragments.clear();
@@ -2281,8 +2279,10 @@
             final BackStackRecord record = records.get(i);
             final boolean isPop = isRecordPop.get(i);
             if (isPop) {
+                record.bumpBackStackNesting(-1);
                 record.executePopOps();
             } else {
+                record.bumpBackStackNesting(1);
                 record.executeOps();
             }
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fc1d613..44db326 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -30,6 +30,8 @@
 import android.app.usage.UsageStatsManager;
 import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothManager;
+import android.companion.CompanionDeviceManager;
+import android.companion.ICompanionDeviceManager;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.IRestrictionsManager;
@@ -634,6 +636,18 @@
                         UserHandle.getAppId(Process.myUid()));
             }});
 
+        registerService(Context.COMPANION_DEVICE_SERVICE, CompanionDeviceManager.class,
+                new CachedServiceFetcher<CompanionDeviceManager>() {
+                    @Override
+                    public CompanionDeviceManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder iBinder =
+                                ServiceManager.getServiceOrThrow(Context.COMPANION_DEVICE_SERVICE);
+                        ICompanionDeviceManager service =
+                                ICompanionDeviceManager.Stub.asInterface(iBinder);
+                        return new CompanionDeviceManager(service, ctx);
+                    }});
+
         registerService(Context.CONSUMER_IR_SERVICE, ConsumerIrManager.class,
                 new CachedServiceFetcher<ConsumerIrManager>() {
             @Override
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 3f467a0..b219f2a 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -145,14 +145,25 @@
     }
 
     @Override
+    public void show() {
+        super.show();
+        getButton(BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mTimePicker.validateInput()) {
+                    if (mTimeSetListener != null) {
+                        mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
+                                mTimePicker.getCurrentMinute());
+                    }
+                    dismiss();
+                }
+            }
+        });
+    }
+
+    @Override
     public void onClick(DialogInterface dialog, int which) {
         switch (which) {
-            case BUTTON_POSITIVE:
-                if (mTimeSetListener != null) {
-                    mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
-                            mTimePicker.getCurrentMinute());
-                }
-                break;
             case BUTTON_NEGATIVE:
                 cancel();
                 break;
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 6d1d1a3..1d6f42e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1118,6 +1118,16 @@
                 public void onFingerprintGesture(int gesture) {
                     /* do nothing */
                 }
+
+                @Override
+                public void onAccessibilityButtonClicked() {
+                    /* do nothing */
+                }
+
+                @Override
+                public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+                    /* do nothing */
+                }
             });
         }
     }
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 17a802d..b89c64a 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -67,7 +67,9 @@
     private final byte[] mManufacturerData;
     @Nullable
     private final byte[] mManufacturerDataMask;
-    private static final ScanFilter EMPTY = new ScanFilter.Builder().build() ;
+
+    /** @hide */
+    public static final ScanFilter EMPTY = new ScanFilter.Builder().build() ;
 
 
     private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
@@ -318,8 +320,12 @@
         return true;
     }
 
-    // Check if the uuid pattern is contained in a list of parcel uuids.
-    private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
+    /**
+     * Check if the uuid pattern is contained in a list of parcel uuids.
+     *
+     * @hide
+     */
+    public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
             List<ParcelUuid> uuids) {
         if (uuid == null) {
             return true;
@@ -338,7 +344,7 @@
     }
 
     // Check if the uuid pattern matches the particular service uuid.
-    private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
+    private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
         if (mask == null) {
             return uuid.equals(data);
         }
diff --git a/core/java/android/companion/AssociationRequest.aidl b/core/java/android/companion/AssociationRequest.aidl
new file mode 100644
index 0000000..6c91062
--- /dev/null
+++ b/core/java/android/companion/AssociationRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+parcelable AssociationRequest;
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
new file mode 100644
index 0000000..d477f43
--- /dev/null
+++ b/core/java/android/companion/AssociationRequest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A request for the user to select a companion device to associate with.
+ *
+ * You can optionally set a {@link Builder#setDeviceFilter filter} for which devices to show to the
+ * user to select from.
+ * The exact type and fields of the filter you can set depend on the
+ * medium type. See {@link Builder}'s static factory methods for specific protocols that are
+ * supported.
+ *
+ * You can also set {@link Builder#setSingleDevice single device} to request a popup with single
+ * device to be shown instead of a list to choose from
+ *
+ * @param <F> Device filter type
+ */
+public final class AssociationRequest<F extends DeviceFilter> implements Parcelable {
+
+    /** @hide */
+    public static final int MEDIUM_TYPE_BLUETOOTH = 0;
+    /** @hide */
+    public static final int MEDIUM_TYPE_BLUETOOTH_LE = 1;
+    /** @hide */
+    public static final int MEDIUM_TYPE_WIFI = 2;
+
+    /** @hide */
+    @IntDef({MEDIUM_TYPE_BLUETOOTH, MEDIUM_TYPE_BLUETOOTH_LE, MEDIUM_TYPE_WIFI})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MediumType {}
+
+    private final boolean mSingleDevice;
+    private final int mMediumType;
+    private final F mDeviceFilter;
+
+    private AssociationRequest(boolean singleDevice, int mMediumType, F deviceFilter) {
+        this.mSingleDevice = singleDevice;
+        this.mMediumType = mMediumType;
+        this.mDeviceFilter = deviceFilter;
+    }
+
+    private AssociationRequest(Parcel in) {
+        this(
+            in.readByte() != 0,
+            in.readInt(),
+            in.readParcelable(AssociationRequest.class.getClassLoader()));
+    }
+
+    /** @hide */
+    public boolean isSingleDevice() {
+        return mSingleDevice;
+    }
+
+    /** @hide */
+    @MediumType
+    public int getMediumType() {
+        return mMediumType;
+    }
+
+    /** @hide */
+    @Nullable
+    public F getDeviceFilter() {
+        return mDeviceFilter;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByte((byte) (mSingleDevice ? 1 : 0));
+        dest.writeInt(mMediumType);
+        dest.writeParcelable(mDeviceFilter, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<AssociationRequest> CREATOR = new Creator<AssociationRequest>() {
+        @Override
+        public AssociationRequest createFromParcel(Parcel in) {
+            return new AssociationRequest(in);
+        }
+
+        @Override
+        public AssociationRequest[] newArray(int size) {
+            return new AssociationRequest[size];
+        }
+    };
+
+    /**
+     * A builder for {@link AssociationRequest}
+     *
+     * @param <F> the type of filter for the request.
+     */
+    public static final class Builder<F extends DeviceFilter>
+            extends OneTimeUseBuilder<AssociationRequest<F>> {
+        private boolean mSingleDevice = false;
+        @MediumType private int mMediumType;
+        @Nullable private F mDeviceFilter = null;
+
+        private Builder() {}
+
+        /**
+         * Create a new builder for an association request with a Bluetooth LE device
+         */
+        @NonNull
+        public static Builder<BluetoothLEDeviceFilter> createForBluetoothLEDevice() {
+            return new Builder<BluetoothLEDeviceFilter>()
+                    .setMediumType(MEDIUM_TYPE_BLUETOOTH_LE);
+        }
+
+        /**
+         * Create a new builder for an association request with a Bluetooth(non-LE) device
+         */
+        @NonNull
+        public static Builder<BluetoothDeviceFilter> createForBluetoothDevice() {
+            return new Builder<BluetoothDeviceFilter>()
+                    .setMediumType(MEDIUM_TYPE_BLUETOOTH);
+        }
+
+        //TODO implement, once specific filter classes are available
+//        public static Builder<> createForWiFiDevice()
+//        public static Builder<> createForNanDevice()
+
+        /**
+         * @param singleDevice if true, scanning for a device will stop as soon as at least one
+         *                     fitting device is found
+         */
+        @NonNull
+        public Builder<F> setSingleDevice(boolean singleDevice) {
+            checkNotUsed();
+            this.mSingleDevice = singleDevice;
+            return this;
+        }
+
+        /**
+         * @param deviceFilter if set, only devices matching the given filter will be shown to the
+         *                     user
+         */
+        @NonNull
+        public Builder<F> setDeviceFilter(@Nullable F deviceFilter) {
+            checkNotUsed();
+            this.mDeviceFilter = deviceFilter;
+            return this;
+        }
+
+        /**
+         * @param deviceType A type of medium over which to discover devices
+         *
+         * @see MediumType
+         */
+        @NonNull
+        private Builder<F> setMediumType(@MediumType int deviceType) {
+            mMediumType = deviceType;
+            return this;
+        }
+
+        /** @inheritDoc */
+        @NonNull
+        @Override
+        public AssociationRequest<F> build() {
+            markUsed();
+            return new AssociationRequest<>(mSingleDevice, mMediumType, mDeviceFilter);
+        }
+    }
+}
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
new file mode 100644
index 0000000..5a69955
--- /dev/null
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import static android.companion.BluetoothDeviceFilterUtils.matchesAddress;
+import static android.companion.BluetoothDeviceFilterUtils.matchesName;
+import static android.companion.BluetoothDeviceFilterUtils.matchesServiceUuids;
+import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
+import static android.companion.BluetoothDeviceFilterUtils.patternToString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.provider.OneTimeUseBuilder;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * A filter for Bluetooth(non-LE) devices
+ */
+public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice> {
+
+    private static BluetoothDeviceFilter NO_OP;
+
+    private final Pattern mNamePattern;
+    private final String mAddress;
+    private final List<ParcelUuid> mServiceUuids;
+    private final List<ParcelUuid> mServiceUuidMasks;
+
+    private BluetoothDeviceFilter(
+            Pattern namePattern,
+            String address,
+            List<ParcelUuid> serviceUuids,
+            List<ParcelUuid> serviceUuidMasks) {
+        mNamePattern = namePattern;
+        mAddress = address;
+        mServiceUuids = ArrayUtils.emptyIfNull(serviceUuids);
+        mServiceUuidMasks = ArrayUtils.emptyIfNull(serviceUuidMasks);
+    }
+
+    private BluetoothDeviceFilter(Parcel in) {
+        this(
+            patternFromString(in.readString()),
+            in.readString(),
+            readUuids(in),
+            readUuids(in));
+    }
+
+    private static List<ParcelUuid> readUuids(Parcel in) {
+        final ArrayList<ParcelUuid> list = new ArrayList<>();
+        in.readParcelableList(list, ParcelUuid.class.getClassLoader());
+        return list;
+    }
+
+    /** @hide */
+    @NonNull
+    public static BluetoothDeviceFilter nullsafe(@Nullable BluetoothDeviceFilter nullable) {
+        return nullable != null ? nullable : noOp();
+    }
+
+    /** @hide */
+    @NonNull
+    public static BluetoothDeviceFilter noOp() {
+        if (NO_OP == null) NO_OP = new Builder().build();
+        return NO_OP;
+    }
+
+    /** @hide */
+    @Override
+    public boolean matches(BluetoothDevice device) {
+        return matchesAddress(mAddress, device)
+                && matchesServiceUuids(mServiceUuids, mServiceUuidMasks, device)
+                && matchesName(getNamePattern(), device);
+    }
+
+    /** @hide */
+    @Nullable
+    public Pattern getNamePattern() {
+        return mNamePattern;
+    }
+
+    /** @hide */
+    @Nullable
+    public String getAddress() {
+        return mAddress;
+    }
+
+    /** @hide */
+    @NonNull
+    public List<ParcelUuid> getServiceUuids() {
+        return mServiceUuids;
+    }
+
+    /** @hide */
+    @NonNull
+    public List<ParcelUuid> getServiceUuidMasks() {
+        return mServiceUuidMasks;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(patternToString(getNamePattern()));
+        dest.writeString(mAddress);
+        dest.writeParcelableList(mServiceUuids, flags);
+        dest.writeParcelableList(mServiceUuidMasks, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<BluetoothDeviceFilter> CREATOR
+            = new Creator<BluetoothDeviceFilter>() {
+        @Override
+        public BluetoothDeviceFilter createFromParcel(Parcel in) {
+            return new BluetoothDeviceFilter(in);
+        }
+
+        @Override
+        public BluetoothDeviceFilter[] newArray(int size) {
+            return new BluetoothDeviceFilter[size];
+        }
+    };
+
+    /**
+     * A builder for {@link BluetoothDeviceFilter}
+     */
+    public static final class Builder extends OneTimeUseBuilder<BluetoothDeviceFilter> {
+        private Pattern mNamePattern;
+        private String mAddress;
+        private ArrayList<ParcelUuid> mServiceUuid;
+        private ArrayList<ParcelUuid> mServiceUuidMask;
+
+        /**
+         * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
+         *              given regular expression will be shown
+         */
+        public Builder setNamePattern(@Nullable Pattern regex) {
+            checkNotUsed();
+            mNamePattern = regex;
+            return this;
+        }
+
+        /**
+         * @param address if set, only devices with MAC address exactly matching the given one will
+         *                pass the filter
+         */
+        @NonNull
+        public Builder setAddress(@Nullable String address) {
+            checkNotUsed();
+            mAddress = address;
+            return this;
+        }
+
+        /**
+         * Add filtering by certain bits of {@link BluetoothDevice#getUuids()}
+         *
+         * A device with any uuid matching the given bits is considered passing
+         *
+         * @param serviceUuid the values for the bits to match
+         * @param serviceUuidMask if provided, only those bits would have to match.
+         */
+        @NonNull
+        public Builder addServiceUuid(
+                @Nullable ParcelUuid serviceUuid, @Nullable ParcelUuid serviceUuidMask) {
+            checkNotUsed();
+            mServiceUuid = ArrayUtils.add(mServiceUuid, serviceUuid);
+            mServiceUuidMask = ArrayUtils.add(mServiceUuidMask, serviceUuidMask);
+            return this;
+        }
+
+        /** @inheritDoc */
+        @Override
+        @NonNull
+        public BluetoothDeviceFilter build() {
+            markUsed();
+            return new BluetoothDeviceFilter(
+                    mNamePattern, mAddress, mServiceUuid, mServiceUuidMask);
+        }
+    }
+}
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
new file mode 100644
index 0000000..289f995
--- /dev/null
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.companion;
+
+import static android.text.TextUtils.firstNotEmpty;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/** @hide */
+public class BluetoothDeviceFilterUtils {
+    private BluetoothDeviceFilterUtils() {}
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "BluetoothDeviceFilterUtil";
+
+    @Nullable
+    static String patternToString(@Nullable Pattern p) {
+        return p == null ? null : p.pattern();
+    }
+
+    @Nullable
+    static Pattern patternFromString(@Nullable String s) {
+        return s == null ? null : Pattern.compile(s);
+    }
+
+    static boolean matches(ScanFilter filter, BluetoothDevice device) {
+        return matchesAddress(filter.getDeviceAddress(), device)
+                && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device);
+    }
+
+    static boolean matchesAddress(String deviceAddress, BluetoothDevice device) {
+        final boolean result = deviceAddress == null
+                || (device == null || !deviceAddress.equals(device.getAddress()));
+        if (DEBUG) debugLogMatchResult(result, device, deviceAddress);
+        return result;
+    }
+
+    static boolean matchesServiceUuids(List<ParcelUuid> serviceUuids,
+            List<ParcelUuid> serviceUuidMasks, BluetoothDevice device) {
+        for (int i = 0; i < serviceUuids.size(); i++) {
+            ParcelUuid uuid = serviceUuids.get(i);
+            ParcelUuid uuidMask = serviceUuidMasks.get(i);
+            if (!matchesServiceUuid(uuid, uuidMask, device)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static boolean matchesServiceUuid(ParcelUuid serviceUuid, ParcelUuid serviceUuidMask,
+            BluetoothDevice device) {
+        final boolean result = serviceUuid == null ||
+                ScanFilter.matchesServiceUuids(
+                        serviceUuid,
+                        serviceUuidMask,
+                        Arrays.asList(device.getUuids()));
+        if (DEBUG) debugLogMatchResult(result, device, serviceUuid);
+        return result;
+    }
+
+    static boolean matchesName(@Nullable Pattern namePattern, BluetoothDevice device) {
+        boolean result;
+        if (namePattern == null)  {
+            result = true;
+        } else if (device == null) {
+            result = false;
+        } else {
+            final String name = device.getName();
+            result = name != null && namePattern.matcher(name).find();
+        }
+        if (DEBUG) debugLogMatchResult(result, device, namePattern);
+        return result;
+    }
+
+    private static void debugLogMatchResult(
+            boolean result, BluetoothDevice device, Object criteria) {
+        Log.i(LOG_TAG, getDeviceDisplayName(device) + (result ? " ~ " : " !~ ") + criteria);
+    }
+
+    public static String getDeviceDisplayName(@NonNull BluetoothDevice device) {
+        return firstNotEmpty(device.getAliasName(), device.getAddress());
+    }
+}
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.aidl b/core/java/android/companion/BluetoothLEDeviceFilter.aidl
new file mode 100644
index 0000000..628cf7b
--- /dev/null
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+parcelable BluetoothLEDeviceFilter;
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
new file mode 100644
index 0000000..4a481ca
--- /dev/null
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
+import static android.companion.BluetoothDeviceFilterUtils.patternToString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.os.Parcel;
+import android.provider.OneTimeUseBuilder;
+
+import com.android.internal.util.ObjectUtils;
+
+import java.util.regex.Pattern;
+
+/**
+ * A filter for Bluetooth LE devices
+ *
+ * @see ScanFilter
+ */
+public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevice> {
+
+    private static BluetoothLEDeviceFilter NO_OP;
+
+    private final Pattern mNamePattern;
+    private final ScanFilter mScanFilter;
+
+    private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter) {
+        mNamePattern = namePattern;
+        mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY);
+    }
+
+    @SuppressLint("ParcelClassLoader")
+    private BluetoothLEDeviceFilter(Parcel in) {
+        this(
+            patternFromString(in.readString()),
+            in.readParcelable(null));
+    }
+
+    /** @hide */
+    @NonNull
+    public static BluetoothLEDeviceFilter nullsafe(@Nullable BluetoothLEDeviceFilter nullable) {
+        return nullable != null ? nullable : noOp();
+    }
+
+    /** @hide */
+    @NonNull
+    public static BluetoothLEDeviceFilter noOp() {
+        if (NO_OP == null) NO_OP = new Builder().build();
+        return NO_OP;
+    }
+
+    /** @hide */
+    @Nullable
+    public Pattern getNamePattern() {
+        return mNamePattern;
+    }
+
+    /** @hide */
+    @NonNull
+    public ScanFilter getScanFilter() {
+        return mScanFilter;
+    }
+
+    /** @hide */
+    @Override
+    public boolean matches(BluetoothDevice device) {
+        return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)
+                && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(patternToString(getNamePattern()));
+        dest.writeParcelable(mScanFilter, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<BluetoothLEDeviceFilter> CREATOR
+            = new Creator<BluetoothLEDeviceFilter>() {
+        @Override
+        public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
+            return new BluetoothLEDeviceFilter(in);
+        }
+
+        @Override
+        public BluetoothLEDeviceFilter[] newArray(int size) {
+            return new BluetoothLEDeviceFilter[size];
+        }
+    };
+
+    /**
+     * Builder for {@link BluetoothLEDeviceFilter}
+     */
+    public static final class Builder extends OneTimeUseBuilder<BluetoothLEDeviceFilter> {
+        private ScanFilter mScanFilter;
+        private Pattern mNamePattern;
+
+        /**
+         * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
+         *              given regular expression will be shown
+         */
+        public Builder setNamePattern(@Nullable Pattern regex) {
+            checkNotUsed();
+            mNamePattern = regex;
+            return this;
+        }
+
+        /**
+         * @param scanFilter a {@link ScanFilter} to filter devices by
+         *
+         * @see ScanFilter for specific details on its various fields
+         */
+        @NonNull
+        public Builder setScanFilter(@Nullable ScanFilter scanFilter) {
+            checkNotUsed();
+            mScanFilter = scanFilter;
+            return this;
+        }
+
+        /** @inheritDoc */
+        @Override
+        @NonNull
+        public BluetoothLEDeviceFilter build() {
+            markUsed();
+            return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter);
+        }
+    }
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
new file mode 100644
index 0000000..b379c7c
--- /dev/null
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.IntentSender;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+
+/**
+ * System level service for managing companion devices
+ *
+ * Usage:
+ * To obtain an instance call
+ * {@link Context#getSystemService}({@link Context#COMPANION_DEVICE_SERVICE})
+ *
+ * Then, call {@link #associate} to initiate the flow of associating current package
+ * with a device selected by user
+ *
+ * @see AssociationRequest
+ */
+public final class CompanionDeviceManager {
+
+    /**
+     * A device, returned in the activity result of the {@link IntentSender} received in
+     * {@link Callback#onDeviceFound}
+     */
+    public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+
+    /**
+     * A callback to receive once at least one suitable device is found, or the search failed
+     * (e.g. timed out)
+     */
+    public abstract static class Callback {
+
+        /**
+         * Called once at least one suitable device is found
+         *
+         * @param chooserLauncher a {@link IntentSender} to launch the UI for user to select a
+         *                        device
+         */
+        public abstract void onDeviceFound(IntentSender chooserLauncher);
+
+        /**
+         * Called if there was an error looking for device(s), e.g. timeout
+         *
+         * @param error the cause of the error
+         */
+        public abstract void onFailure(CharSequence error);
+    }
+
+    private final ICompanionDeviceManager mService;
+    private final Context mContext;
+
+    /** @hide */
+    public CompanionDeviceManager(
+            @NonNull ICompanionDeviceManager service, @NonNull Context context) {
+        mService = service;
+        mContext = context;
+    }
+
+    /**
+     * Associate this app with a companion device, selected by user
+     *
+     * Once at least one appropriate device is found, {@code callback} will be called with a
+     * {@link PendingIntent} that can be used to show the list of available devices for the user
+     * to select.
+     * It should be started for result (i.e. using
+     * {@link android.app.Activity#startIntentSenderForResult}), as the resulting
+     * {@link android.content.Intent} will contain extra {@link #EXTRA_DEVICE}, with the selected
+     * device. (e.g. {@link android.bluetooth.BluetoothDevice})
+     *
+     * @param request specific details about this request
+     * @param callback will be called once there's at least one device found for user to choose from
+     * @param handler A handler to control which thread the callback will be delivered on, or null,
+     *                to deliver it on main thread
+     *
+     * @see AssociationRequest
+     */
+    public void associate(
+            @NonNull AssociationRequest<?> request,
+            @NonNull Callback callback,
+            @Nullable Handler handler) {
+        final Handler finalHandler = handler != null
+                ? handler
+                : new Handler(Looper.getMainLooper());
+        try {
+            mService.associate(
+                    request,
+                    new IOnAssociateCallback.Stub() {
+
+                        @Override
+                        public void onSuccess(PendingIntent launcher) throws RemoteException {
+                            finalHandler.post(() -> {
+                                callback.onDeviceFound(launcher.getIntentSender());
+                            });
+                        }
+
+                        @Override
+                        public void onFailure(CharSequence reason) throws RemoteException {
+                            finalHandler.post(() -> callback.onFailure(reason));
+                        }
+                    },
+                    mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void requestNotificationAccess() {
+        //TODO implement
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    /** @hide */
+    public boolean haveNotificationAccess() {
+        //TODO implement
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+}
diff --git a/core/java/android/companion/DeviceFilter.java b/core/java/android/companion/DeviceFilter.java
new file mode 100644
index 0000000..8362b2d
--- /dev/null
+++ b/core/java/android/companion/DeviceFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.companion;
+
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+/**
+ * A filter for companion devices of type {@code D}
+ *
+ * @param <D> Type of devices, filtered by this filter,
+ *           e.g. {@link android.bluetooth.BluetoothDevice}, {@link android.net.wifi.WifiInfo}
+ */
+public interface DeviceFilter<D extends Parcelable> extends Parcelable {
+
+    /**
+     * @return whether the given device matches this filter
+     *
+     * @hide
+     */
+    boolean matches(D device);
+
+    /**
+     * A nullsafe {@link #matches(Parcelable)}, returning true if the filter is null
+     *
+     * @hide
+     */
+    static <D extends Parcelable> boolean matches(@Nullable DeviceFilter<D> filter, D device) {
+        return filter == null || filter.matches(device);
+    }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
new file mode 100644
index 0000000..065e31be
--- /dev/null
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.companion.IOnAssociateCallback;
+import android.companion.AssociationRequest;
+
+/**
+ * Interface for communication with the core companion device manager service.
+ *
+ * @hide
+ */
+interface ICompanionDeviceManager {
+    void associate(in AssociationRequest request,
+        in IOnAssociateCallback callback,
+        in String callingPackage); //TODO int userId?
+
+    //TODO add these
+//    boolean haveNotificationAccess(String packageName);
+//    oneway void requestNotificationAccess(String packageName);
+}
diff --git a/core/java/android/companion/ICompanionDeviceManagerService.aidl b/core/java/android/companion/ICompanionDeviceManagerService.aidl
new file mode 100644
index 0000000..ff2a7eb
--- /dev/null
+++ b/core/java/android/companion/ICompanionDeviceManagerService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.companion.AssociationRequest;
+import android.companion.IOnAssociateCallback;
+
+
+/** @hide */
+interface ICompanionDeviceManagerService {
+    void startDiscovery(
+        in AssociationRequest request,
+        in IOnAssociateCallback callback,
+        in String callingPackage);
+}
diff --git a/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl b/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl
new file mode 100644
index 0000000..c9dd345
--- /dev/null
+++ b/core/java/android/companion/ICompanionDeviceManagerServiceCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing per  missions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.bluetooth.BluetoothDevice;
+
+/** @hide */
+interface ICompanionDeviceManagerServiceCallback {
+    void onDeviceSelected(in BluetoothDevice device);
+}
diff --git a/core/java/android/companion/IOnAssociateCallback.aidl b/core/java/android/companion/IOnAssociateCallback.aidl
new file mode 100644
index 0000000..4867eadd
--- /dev/null
+++ b/core/java/android/companion/IOnAssociateCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing per  missions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.app.PendingIntent;
+
+/** @hide */
+interface IOnAssociateCallback {
+    void onSuccess(in PendingIntent launcher);
+    void onFailure(in CharSequence reason);
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a7d5ac6..06f7303 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3600,6 +3600,14 @@
     public static final String PRINT_SERVICE = "print";
 
     /**
+     * {@link android.companion.CompanionDeviceManager} for managing companion devices
+     *
+     * @see #getSystemService
+     * @see android.companion.CompanionDeviceManager
+     */
+    public static final String COMPANION_DEVICE_SERVICE = "companion_device";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a
      * {@link android.hardware.ConsumerIrManager} for transmitting infrared
      * signals from the device.
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index dd53eac..74d2f11 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -351,7 +351,7 @@
          * @param requiresCharging true if sync requires the phone to be plugged in. Default false.
          */
         public Builder setRequiresCharging(boolean requiresCharging) {
-            mRequiresCharging = true;
+            mRequiresCharging = requiresCharging;
             return this;
         }
 
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 522575f..d951294 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -259,10 +259,6 @@
             throw new IllegalArgumentException("Unknow surface source class type");
         }
 
-        if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) {
-            throw new IllegalArgumentException("Surface size needs to be non-zero");
-        }
-
         mSurfaceGroupId = SURFACE_GROUP_ID_NONE;
         mSurfaces = new ArrayList<Surface>();
         mRotation = ROTATION_0;
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 3eb5844..98a5749 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -230,9 +230,6 @@
         public int dozeScreenBrightness;
         public int dozeScreenState;
 
-        // If true, use twilight to affect the brightness.
-        public boolean useTwilight;
-
         public DisplayPowerRequest() {
             policy = POLICY_BRIGHT;
             useProximitySensor = false;
@@ -268,7 +265,6 @@
             boostScreenBrightness = other.boostScreenBrightness;
             dozeScreenBrightness = other.dozeScreenBrightness;
             dozeScreenState = other.dozeScreenState;
-            useTwilight = other.useTwilight;
         }
 
         @Override
@@ -289,8 +285,7 @@
                     && lowPowerMode == other.lowPowerMode
                     && boostScreenBrightness == other.boostScreenBrightness
                     && dozeScreenBrightness == other.dozeScreenBrightness
-                    && dozeScreenState == other.dozeScreenState
-                    && useTwilight == other.useTwilight;
+                    && dozeScreenState == other.dozeScreenState;
         }
 
         @Override
@@ -310,8 +305,7 @@
                     + ", lowPowerMode=" + lowPowerMode
                     + ", boostScreenBrightness=" + boostScreenBrightness
                     + ", dozeScreenBrightness=" + dozeScreenBrightness
-                    + ", dozeScreenState=" + Display.stateToString(dozeScreenState)
-                    + ", useTwilight=" + useTwilight;
+                    + ", dozeScreenState=" + Display.stateToString(dozeScreenState);
         }
 
         public static String policyToString(int policy) {
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 2c9e6c7..7947620 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -766,7 +766,7 @@
 
     /**
      * Retrieves the authenticator token for binding keys to the lifecycle
-     * of the current set of fingerprints. Used only by internal clients.
+     * of the calling user's fingerprints. Used only by internal clients.
      *
      * @hide
      */
diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java
index b89a245..9f97c5a 100644
--- a/core/java/android/net/RecommendationRequest.java
+++ b/core/java/android/net/RecommendationRequest.java
@@ -50,7 +50,7 @@
         private WifiConfiguration mDefaultConfig;
         private WifiConfiguration mConnectedConfig;
         private WifiConfiguration[] mConnectableConfigs;
-        private int mLastSelectedNetworkId;
+        private int mLastSelectedNetworkId = -1;
         private long mLastSelectedTimestamp;
 
         public Builder setScanResults(ScanResult[] scanResults) {
@@ -161,7 +161,7 @@
 
     /**
      * @return The {@link WifiConfiguration#networkId} of the last user selected network.
-     *         {@code 0} if not set.
+     *         {@code -1} if not set.
      */
     public int getLastSelectedNetworkId() {
         return mLastSelectedNetworkId;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index dff0a28..31b3bc9 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -508,15 +508,6 @@
     }
 
     /**
-     * Returns true if the twilight service should be used to adjust screen brightness
-     * policy.  This setting is experimental and disabled by default.
-     * @hide
-     */
-    public static boolean useTwilightAdjustmentFeature() {
-        return SystemProperties.getBoolean("persist.power.usetwilightadj", false);
-    }
-
-    /**
      * Creates a new wake lock with the specified level and flags.
      * <p>
      * The {@code levelAndFlags} parameter specifies a wake lock level and optional flags
diff --git a/core/java/android/provider/OneTimeUseBuilder.java b/core/java/android/provider/OneTimeUseBuilder.java
new file mode 100644
index 0000000..682e9c7
--- /dev/null
+++ b/core/java/android/provider/OneTimeUseBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.provider;
+
+/**
+ * A builder that facilitates prohibiting its use after an instance was created with it.
+ *
+ * Suggested usage:
+ * call {@link #checkNotUsed} in each setter, and {@link #markUsed} in {@link #build}
+ *
+ * @param <T> Type of object being built
+ * @hide
+ */
+public abstract class OneTimeUseBuilder<T> {
+    private boolean used = false;
+
+    protected void markUsed() {
+        checkNotUsed();
+        used = true;
+    }
+
+    protected void checkNotUsed() {
+        if (used) {
+            throw new IllegalStateException(
+                    "This Builder should not be reused. Use a new Builder instance instead");
+        }
+    }
+
+    /**
+     * Builds the instance
+     *
+     * Once this method is called, this builder should no longer be used. Any subsequent calls to a
+     * setter or {@code build()} will throw an exception
+     */
+    public abstract T build();
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d83f2cb..2936dfb 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6721,12 +6721,6 @@
         public static final String NIGHT_DISPLAY_CUSTOM_END_TIME = "night_display_custom_end_time";
 
         /**
-         * Whether brightness should automatically adjust based on twilight state.
-         * @hide
-         */
-        public static final String BRIGHTNESS_USE_TWILIGHT = "brightness_use_twilight";
-
-        /**
          * Names of the service components that the current user has explicitly allowed to
          * be a VR mode listener, separated by ':'.
          *
diff --git a/core/java/android/provider/SettingsStringUtil.java b/core/java/android/provider/SettingsStringUtil.java
new file mode 100644
index 0000000..f242d79
--- /dev/null
+++ b/core/java/android/provider/SettingsStringUtil.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.provider;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.text.TextUtils;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.function.Function;
+
+/**
+ * Utilities for dealing with {@link String} values in {@link Settings}
+ *
+ * @hide
+ */
+public class SettingsStringUtil {
+    private SettingsStringUtil() {}
+
+    public static final String DELIMITER = ":";
+
+    /**
+     * A {@link HashSet} of items, that uses a common convention of setting string
+     * serialization/deserialization of separating multiple items with {@link #DELIMITER}
+     */
+    public static abstract class ColonDelimitedSet<T> extends HashSet<T> {
+
+        public ColonDelimitedSet(String colonSeparatedItems) {
+            for (String cn :
+                    TextUtils.split(TextUtils.emptyIfNull(colonSeparatedItems), DELIMITER)) {
+                add(itemFromString(cn));
+            }
+        }
+
+        protected abstract T itemFromString(String s);
+        protected String itemToString(T item) {
+            return String.valueOf(item);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            Iterator<T> it = iterator();
+            if (it.hasNext()) {
+                sb.append(it.next());
+                while (it.hasNext()) {
+                    sb.append(DELIMITER);
+                    sb.append(itemToString(it.next()));
+                }
+            }
+            return sb.toString();
+        }
+
+
+        public static class OfStrings extends ColonDelimitedSet<String> {
+            public OfStrings(String colonSeparatedItems) {
+                super(colonSeparatedItems);
+            }
+
+            @Override
+            protected String itemFromString(String s) {
+                return s;
+            }
+
+            public static String add(String delimitedElements, String element) {
+                final ColonDelimitedSet<String> set
+                        = new ColonDelimitedSet.OfStrings(delimitedElements);
+                if (set.contains(element)) {
+                    return delimitedElements;
+                }
+                set.add(element);
+                return set.toString();
+            }
+
+            public static String remove(String delimitedElements, String element) {
+                final ColonDelimitedSet<String> set
+                        = new ColonDelimitedSet.OfStrings(delimitedElements);
+                if (!set.contains(element)) {
+                    return delimitedElements;
+                }
+                set.remove(element);
+                return set.toString();
+            }
+
+            public static boolean contains(String delimitedElements, String element) {
+                final String[] elements = TextUtils.split(delimitedElements, DELIMITER);
+                return ArrayUtils.indexOf(elements, element) != -1;
+            }
+        }
+    }
+
+    public static class ComponentNameSet extends ColonDelimitedSet<ComponentName> {
+        public ComponentNameSet(String colonSeparatedPackageNames) {
+            super(colonSeparatedPackageNames);
+        }
+
+        @Override
+        protected ComponentName itemFromString(String s) {
+            return ComponentName.unflattenFromString(s);
+        }
+
+        @Override
+        protected String itemToString(ComponentName item) {
+            return item.flattenToString();
+        }
+
+        public static String add(String delimitedElements, ComponentName element) {
+            final ComponentNameSet set = new ComponentNameSet(delimitedElements);
+            if (set.contains(element)) {
+                return delimitedElements;
+            }
+            set.add(element);
+            return set.toString();
+        }
+
+        public static String remove(String delimitedElements, ComponentName element) {
+            final ComponentNameSet set = new ComponentNameSet(delimitedElements);
+            if (!set.contains(element)) {
+                return delimitedElements;
+            }
+            set.remove(element);
+            return set.toString();
+        }
+
+        public static boolean contains(String delimitedElements, ComponentName element) {
+            return ColonDelimitedSet.OfStrings.contains(
+                    delimitedElements, element.flattenToString());
+        }
+    }
+
+    public static class SettingStringHelper {
+        private final ContentResolver mContentResolver;
+        private final String mSettingName;
+        private final int mUserId;
+
+        public SettingStringHelper(ContentResolver contentResolver, String name, int userId) {
+            mContentResolver = contentResolver;
+            mUserId = userId;
+            mSettingName = name;
+        }
+
+        public String read() {
+            return Settings.Secure.getStringForUser(
+                    mContentResolver, mSettingName, mUserId);
+        }
+
+        public boolean write(String value) {
+            return Settings.Secure.putStringForUser(
+                    mContentResolver, mSettingName, value, mUserId);
+        }
+
+        public boolean modify(Function<String, String> change) {
+            return write(change.apply(read()));
+        }
+    }
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index bb952ab..55aeb1e 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -62,6 +62,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
 import java.lang.reflect.Array;
 import java.util.Iterator;
@@ -466,6 +467,16 @@
         return isEmpty(str) ? null : str;
     }
 
+    /** {@hide} */
+    public static String emptyIfNull(@Nullable String str) {
+        return str == null ? "" : str;
+    }
+
+    /** {@hide} */
+    public static String firstNotEmpty(@Nullable String a, @NonNull String b) {
+        return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b);
+    }
+
     /**
      * Returns the length that the specified CharSequence would have if
      * spaces and ASCII control characters were trimmed from the start and end,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 93a9c02..a150529 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24659,7 +24659,7 @@
      * Determine if this view is rendered on a round wearable device and is the main view
      * on the screen.
      */
-    private boolean shouldDrawRoundScrollbar() {
+    boolean shouldDrawRoundScrollbar() {
         if (!mResources.getConfiguration().isScreenRound() || mAttachInfo == null) {
             return false;
         }
@@ -24676,7 +24676,7 @@
             return false;
         }
 
-        getLocationOnScreen(mAttachInfo.mTmpLocation);
+        getLocationInWindow(mAttachInfo.mTmpLocation);
         return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
                 && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
     }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 1ef0d17..45302b6 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -779,6 +779,49 @@
         }
     }
 
+    /**
+     * Notifies that the accessibility button in the system's navigation area has been clicked
+     *
+     * @hide
+     */
+    public void notifyAccessibilityButtonClicked() {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonClicked();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+        }
+    }
+
+    /**
+     * Notifies that the availability of the accessibility button in the system's navigation area
+     * has changed.
+     *
+     * @param available {@code true} if the accessibility button is available within the system
+     *                  navigation area, {@code false} otherwise
+     * @hide
+     */
+    public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonAvailabilityChanged(available);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button availability change", re);
+        }
+    }
+
     private IAccessibilityManager getServiceLocked() {
         if (mService == null) {
             tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 136bbbe..8fde47a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -59,6 +59,10 @@
 
     IBinder getWindowToken(int windowId, int userId);
 
+    void notifyAccessibilityButtonClicked();
+
+    void notifyAccessibilityButtonAvailabilityChanged(boolean available);
+
     // Requires WRITE_SECURE_SETTINGS
     void performAccessibilityShortcut();
 
diff --git a/core/java/android/widget/TextInputTimePickerView.java b/core/java/android/widget/TextInputTimePickerView.java
new file mode 100644
index 0000000..ef91576
--- /dev/null
+++ b/core/java/android/widget/TextInputTimePickerView.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+import android.view.View;
+
+import com.android.internal.R;
+
+/**
+ * View to show text input based time picker with hour and minute fields and an optional AM/PM
+ * spinner.
+ *
+ * @hide
+ */
+public class TextInputTimePickerView extends RelativeLayout {
+    public static final int HOURS = 0;
+    public static final int MINUTES = 1;
+    public static final int AMPM = 2;
+
+    private static final int AM = 0;
+    private static final int PM = 1;
+
+    private final EditText mHourEditText;
+    private final EditText mMinuteEditText;
+    private final TextView mInputSeparatorView;
+    private final Spinner mAmPmSpinner;
+    private final TextView mErrorLabel;
+    private final TextView mHourLabel;
+    private final TextView mMinuteLabel;
+
+    private boolean mIs24Hour;
+    private boolean mHourFormatStartsAtZero;
+    private OnValueTypedListener mListener;
+
+    private boolean mErrorShowing;
+
+    interface OnValueTypedListener {
+        void onValueChanged(int inputType, int newValue);
+    }
+
+    public TextInputTimePickerView(Context context) {
+        this(context, null);
+    }
+
+    public TextInputTimePickerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, 0);
+    }
+
+    public TextInputTimePickerView(Context context, AttributeSet attrs, int defStyle,
+            int defStyleRes) {
+        super(context, attrs, defStyle, defStyleRes);
+
+        inflate(context, R.layout.time_picker_text_input_material, this);
+
+        mHourEditText = (EditText) findViewById(R.id.input_hour);
+        mMinuteEditText = (EditText) findViewById(R.id.input_minute);
+        mInputSeparatorView = (TextView) findViewById(R.id.input_separator);
+        mErrorLabel = (TextView) findViewById(R.id.label_error);
+        mHourLabel = (TextView) findViewById(R.id.label_hour);
+        mMinuteLabel = (TextView) findViewById(R.id.label_minute);
+
+        mHourEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                parseAndSetHourInternal(editable.toString());
+            }
+        });
+
+        mMinuteEditText.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                parseAndSetMinuteInternal(editable.toString());
+            }
+        });
+
+        mAmPmSpinner = (Spinner) findViewById(R.id.am_pm_spinner);
+        final String[] amPmStrings = TimePicker.getAmPmStrings(context);
+        ArrayAdapter<CharSequence> adapter =
+                new ArrayAdapter<CharSequence>(context, R.layout.simple_spinner_dropdown_item);
+        adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[0]));
+        adapter.add(TimePickerClockDelegate.obtainVerbatim(amPmStrings[1]));
+        mAmPmSpinner.setAdapter(adapter);
+        mAmPmSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> adapterView, View view, int position,
+                    long id) {
+                if (position == 0) {
+                    mListener.onValueChanged(AMPM, AM);
+                } else {
+                    mListener.onValueChanged(AMPM, PM);
+                }
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> adapterView) {}
+        });
+    }
+
+    void setListener(OnValueTypedListener listener) {
+        mListener = listener;
+    }
+
+    void setHourFormat(int maxCharLength) {
+        mHourEditText.setFilters(new InputFilter[] {
+                new InputFilter.LengthFilter(maxCharLength)});
+        mMinuteEditText.setFilters(new InputFilter[] {
+                new InputFilter.LengthFilter(maxCharLength)});
+    }
+
+    boolean validateInput() {
+        final boolean inputValid = parseAndSetHourInternal(mHourEditText.getText().toString())
+                && parseAndSetMinuteInternal(mMinuteEditText.getText().toString());
+        setError(!inputValid);
+        return inputValid;
+    }
+
+    void updateSeparator(String separatorText) {
+        mInputSeparatorView.setText(separatorText);
+    }
+
+    private void setError(boolean enabled) {
+        mErrorShowing = enabled;
+
+        mErrorLabel.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+        mHourLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
+        mMinuteLabel.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE);
+    }
+
+    /**
+     * Computes the display value and updates the text of the view.
+     * <p>
+     * This method should be called whenever the current value or display
+     * properties (leading zeroes, max digits) change.
+     */
+    void updateTextInputValues(int localizedHour, int minute, int amOrPm, boolean is24Hour,
+            boolean hourFormatStartsAtZero) {
+        final String format = "%d";
+
+        mIs24Hour = is24Hour;
+        mHourFormatStartsAtZero = hourFormatStartsAtZero;
+
+        mAmPmSpinner.setVisibility(is24Hour ? View.INVISIBLE : View.VISIBLE);
+
+        mHourEditText.setText(String.format(format, localizedHour));
+        mMinuteEditText.setText(String.format(format, minute));
+
+        if (amOrPm == AM) {
+            mAmPmSpinner.setSelection(0);
+        } else {
+            mAmPmSpinner.setSelection(1);
+        }
+
+        if (mErrorShowing) {
+            validateInput();
+        }
+    }
+
+    private boolean parseAndSetHourInternal(String input) {
+        try {
+            final int hour = Integer.parseInt(input);
+            if (!isValidLocalizedHour(hour)) {
+                final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+                final int maxHour = mIs24Hour ? 23 : 11 + minHour;
+                mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(
+                        MathUtils.constrain(hour, minHour, maxHour)));
+                return false;
+            }
+            mListener.onValueChanged(HOURS, getHourOfDayFromLocalizedHour(hour));
+            return true;
+        } catch (NumberFormatException e) {
+            // Do nothing since we cannot parse the input.
+            return false;
+        }
+    }
+
+    private boolean parseAndSetMinuteInternal(String input) {
+        try {
+            final int minutes = Integer.parseInt(input);
+            if (minutes < 0 || minutes > 59) {
+                mListener.onValueChanged(MINUTES, MathUtils.constrain(minutes, 0, 59));
+                return false;
+            }
+            mListener.onValueChanged(MINUTES, minutes);
+            return true;
+        } catch (NumberFormatException e) {
+            // Do nothing since we cannot parse the input.
+            return false;
+        }
+    }
+
+    private boolean isValidLocalizedHour(int localizedHour) {
+        final int minHour = mHourFormatStartsAtZero ? 0 : 1;
+        final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
+        return localizedHour >= minHour && localizedHour <= maxHour;
+    }
+
+    private int getHourOfDayFromLocalizedHour(int localizedHour) {
+        int hourOfDay = localizedHour;
+        if (mIs24Hour) {
+            if (!mHourFormatStartsAtZero && localizedHour == 24) {
+                hourOfDay = 0;
+            }
+        } else {
+            if (!mHourFormatStartsAtZero && localizedHour == 12) {
+                hourOfDay = 0;
+            }
+            if (mAmPmSpinner.getSelectedItemPosition() == 1) {
+                hourOfDay += 12;
+            }
+        }
+        return hourOfDay;
+    }
+}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index e6cd798..9f38b04 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -278,6 +278,16 @@
         return mDelegate.getBaseline();
     }
 
+    /**
+     * Validates whether current input by the user is a valid time based on the locale. TimePicker
+     * will show an error message to the user if the time is not valid.
+     *
+     * @return {@code true} if the input is valid, {@code false} otherwise
+     */
+    public boolean validateInput() {
+        return mDelegate.validateInput();
+    }
+
     @Override
     protected Parcelable onSaveInstanceState() {
         Parcelable superState = super.onSaveInstanceState();
@@ -341,6 +351,8 @@
         void setIs24Hour(boolean is24Hour);
         boolean is24Hour();
 
+        boolean validateInput();
+
         void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
 
         void setEnabled(boolean enabled);
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 1d37a21..3a09063 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -16,12 +16,14 @@
 
 package android.widget;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.icu.text.DecimalFormatSymbols;
 import android.os.Parcelable;
 import android.text.SpannableStringBuilder;
 import android.text.format.DateFormat;
@@ -40,11 +42,14 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.RadialTimePickerView.OnValueSelectedListener;
+import android.widget.TextInputTimePickerView.OnValueTypedListener;
 
 import com.android.internal.R;
 import com.android.internal.widget.NumericTextView;
 import com.android.internal.widget.NumericTextView.OnValueChangedListener;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Calendar;
 
 /**
@@ -58,6 +63,13 @@
      */
     private static final long DELAY_COMMIT_MILLIS = 2000;
 
+    @IntDef({FROM_EXTERNAL_API, FROM_RADIAL_PICKER, FROM_INPUT_PICKER})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ChangeSource {}
+    private static final int FROM_EXTERNAL_API = 0;
+    private static final int FROM_RADIAL_PICKER = 1;
+    private static final int FROM_INPUT_PICKER = 2;
+
     // Index used by RadialPickerLayout
     private static final int HOUR_INDEX = RadialTimePickerView.HOURS;
     private static final int MINUTE_INDEX = RadialTimePickerView.MINUTES;
@@ -78,6 +90,15 @@
     private final RadialTimePickerView mRadialTimePickerView;
     private final TextView mSeparatorView;
 
+    private boolean mRadialPickerModeEnabled = true;
+    private final ImageButton mRadialTimePickerModeButton;
+    private final String mRadialTimePickerModeEnabledDescription;
+    private final String mTextInputPickerModeEnabledDescription;
+    private final View mRadialTimePickerHeader;
+    private final View mTextInputPickerHeader;
+
+    private final TextInputTimePickerView mTextInputPickerView;
+
     private final Calendar mTempCalendar;
 
     // Accessibility strings.
@@ -116,8 +137,8 @@
         final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
                 R.layout.time_picker_material);
         final View mainView = inflater.inflate(layoutResourceId, delegator);
-        final View headerView = mainView.findViewById(R.id.time_header);
-        headerView.setOnTouchListener(new NearestTouchDelegate());
+        mRadialTimePickerHeader = mainView.findViewById(R.id.time_header);
+        mRadialTimePickerHeader.setOnTouchListener(new NearestTouchDelegate());
 
         // Set up hour/minute labels.
         mHourView = (NumericTextView) mainView.findViewById(R.id.hours);
@@ -170,6 +191,8 @@
             headerTextColor = a.getColorStateList(R.styleable.TimePicker_headerTextColor);
         }
 
+        mTextInputPickerHeader = mainView.findViewById(R.id.input_header);
+
         if (headerTextColor != null) {
             mHourView.setTextColor(headerTextColor);
             mSeparatorView.setTextColor(headerTextColor);
@@ -180,7 +203,10 @@
 
         // Set up header background, if available.
         if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) {
-            headerView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
+            mRadialTimePickerHeader.setBackground(a.getDrawable(
+                    R.styleable.TimePicker_headerBackground));
+            mTextInputPickerHeader.setBackground(a.getDrawable(
+                    R.styleable.TimePicker_headerBackground));
         }
 
         a.recycle();
@@ -189,6 +215,22 @@
         mRadialTimePickerView.applyAttributes(attrs, defStyleAttr, defStyleRes);
         mRadialTimePickerView.setOnValueSelectedListener(mOnValueSelectedListener);
 
+        mTextInputPickerView = (TextInputTimePickerView) mainView.findViewById(R.id.input_mode);
+        mTextInputPickerView.setListener(mOnValueTypedListener);
+
+        mRadialTimePickerModeButton =
+                (ImageButton) mainView.findViewById(R.id.toggle_mode);
+        mRadialTimePickerModeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                toggleRadialPickerMode();
+            }
+        });
+        mRadialTimePickerModeEnabledDescription = context.getResources().getString(
+                R.string.time_picker_radial_mode_description);
+        mTextInputPickerModeEnabledDescription = context.getResources().getString(
+                R.string.time_picker_text_input_mode_description);
+
         mAllowAutoAdvance = true;
 
         updateHourFormat();
@@ -200,6 +242,34 @@
         initialize(currentHour, currentMinute, mIs24Hour, HOUR_INDEX);
     }
 
+    private void toggleRadialPickerMode() {
+        if (mRadialPickerModeEnabled) {
+            mRadialTimePickerView.setVisibility(View.GONE);
+            mRadialTimePickerHeader.setVisibility(View.GONE);
+            mTextInputPickerHeader.setVisibility(View.VISIBLE);
+            mTextInputPickerView.setVisibility(View.VISIBLE);
+            mRadialTimePickerModeButton.setImageResource(R.drawable.btn_event_material);
+            mRadialTimePickerModeButton.setContentDescription(
+                    mRadialTimePickerModeEnabledDescription);
+            mRadialPickerModeEnabled = false;
+        } else {
+            mRadialTimePickerView.setVisibility(View.VISIBLE);
+            mRadialTimePickerHeader.setVisibility(View.VISIBLE);
+            mTextInputPickerHeader.setVisibility(View.GONE);
+            mTextInputPickerView.setVisibility(View.GONE);
+            mRadialTimePickerModeButton.setImageResource(R.drawable.btn_keyboard_key_material);
+            mRadialTimePickerModeButton.setContentDescription(
+                    mTextInputPickerModeEnabledDescription);
+            updateTextInputPicker();
+            mRadialPickerModeEnabled = true;
+        }
+    }
+
+    @Override
+    public boolean validateInput() {
+        return mTextInputPickerView.validateInput();
+    }
+
     /**
      * Ensures that a TextView is wide enough to contain its text without
      * wrapping or clipping. Measures the specified view and sets the minimum
@@ -249,9 +319,16 @@
         final int maxHour = (mIs24Hour ? 23 : 11) + minHour;
         mHourView.setRange(minHour, maxHour);
         mHourView.setShowLeadingZeroes(mHourFormatShowLeadingZero);
+
+        final String[] digits = DecimalFormatSymbols.getInstance(mLocale).getDigitStrings();
+        int maxCharLength = 0;
+        for (int i = 0; i < 10; i++) {
+            maxCharLength = Math.max(maxCharLength, digits[i].length());
+        }
+        mTextInputPickerView.setHourFormat(maxCharLength * 2);
     }
 
-    private static final CharSequence obtainVerbatim(String text) {
+    static final CharSequence obtainVerbatim(String text) {
         return new SpannableStringBuilder().append(text,
                 new TtsSpan.VerbatimBuilder(text).build(), 0);
     }
@@ -333,10 +410,16 @@
         updateHeaderSeparator();
         updateHeaderMinute(mCurrentMinute, false);
         updateRadialPicker(index);
+        updateTextInputPicker();
 
         mDelegator.invalidate();
     }
 
+    private void updateTextInputPicker() {
+        mTextInputPickerView.updateTextInputValues(getLocalizedHour(mCurrentHour), mCurrentMinute,
+                mCurrentHour < 12 ? AM : PM, mIs24Hour, mHourFormatStartsAtZero);
+    }
+
     private void updateRadialPicker(int index) {
         mRadialTimePickerView.initialize(mCurrentHour, mCurrentMinute, mIs24Hour);
         setCurrentItemShowing(index, false, true);
@@ -381,10 +464,10 @@
      */
     @Override
     public void setHour(int hour) {
-        setHourInternal(hour, false, true);
+        setHourInternal(hour, FROM_EXTERNAL_API, true);
     }
 
-    private void setHourInternal(int hour, boolean isFromPicker, boolean announce) {
+    private void setHourInternal(int hour, @ChangeSource int source, boolean announce) {
         if (mCurrentHour == hour) {
             return;
         }
@@ -393,10 +476,13 @@
         updateHeaderHour(hour, announce);
         updateHeaderAmPm();
 
-        if (!isFromPicker) {
+        if (source != FROM_RADIAL_PICKER) {
             mRadialTimePickerView.setCurrentHour(hour);
             mRadialTimePickerView.setAmOrPm(hour < 12 ? AM : PM);
         }
+        if (source != FROM_INPUT_PICKER) {
+            updateTextInputPicker();
+        }
 
         mDelegator.invalidate();
         onTimeChanged();
@@ -424,10 +510,10 @@
      */
     @Override
     public void setMinute(int minute) {
-        setMinuteInternal(minute, false);
+        setMinuteInternal(minute, FROM_EXTERNAL_API);
     }
 
-    private void setMinuteInternal(int minute, boolean isFromPicker) {
+    private void setMinuteInternal(int minute, @ChangeSource int source) {
         if (mCurrentMinute == minute) {
             return;
         }
@@ -435,9 +521,12 @@
         mCurrentMinute = minute;
         updateHeaderMinute(minute, true);
 
-        if (!isFromPicker) {
+        if (source != FROM_RADIAL_PICKER) {
             mRadialTimePickerView.setCurrentMinute(minute);
         }
+        if (source != FROM_INPUT_PICKER) {
+            updateTextInputPicker();
+        }
 
         mDelegator.invalidate();
         onTimeChanged();
@@ -661,6 +750,7 @@
             separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1));
         }
         mSeparatorView.setText(separatorText);
+        mTextInputPickerView.updateSeparator(separatorText);
     }
 
     static private int lastIndexOfAny(String str, char[] any) {
@@ -712,7 +802,7 @@
 
         if (mRadialTimePickerView.setAmOrPm(amOrPm)) {
             mCurrentHour = getHour();
-
+            updateTextInputPicker();
             if (mOnTimeChangedListener != null) {
                 mOnTimeChangedListener.onTimeChanged(mDelegator, getHour(), getMinute());
             }
@@ -726,7 +816,7 @@
             switch (pickerType) {
                 case RadialTimePickerView.HOURS:
                     final boolean isTransition = mAllowAutoAdvance && autoAdvance;
-                    setHourInternal(newValue, true, !isTransition);
+                    setHourInternal(newValue, FROM_RADIAL_PICKER, !isTransition);
                     if (isTransition) {
                         setCurrentItemShowing(MINUTE_INDEX, true, false);
 
@@ -735,7 +825,7 @@
                     }
                     break;
                 case RadialTimePickerView.MINUTES:
-                    setMinuteInternal(newValue, true);
+                    setMinuteInternal(newValue, FROM_RADIAL_PICKER);
                     break;
             }
 
@@ -745,6 +835,23 @@
         }
     };
 
+    private final OnValueTypedListener mOnValueTypedListener = new OnValueTypedListener() {
+        @Override
+        public void onValueChanged(int pickerType, int newValue) {
+            switch (pickerType) {
+                case TextInputTimePickerView.HOURS:
+                    setHourInternal(newValue, FROM_INPUT_PICKER, false);
+                    break;
+                case TextInputTimePickerView.MINUTES:
+                    setMinuteInternal(newValue, FROM_INPUT_PICKER);
+                    break;
+                case TextInputTimePickerView.AMPM:
+                    setAmOrPm(newValue);
+                    break;
+            }
+        }
+    };
+
     /** Listener for keyboard interaction. */
     private final OnValueChangedListener mDigitEnteredListener = new OnValueChangedListener() {
         @Override
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 6a68f60..7ef54a5 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -219,6 +219,11 @@
         }
     }
 
+    @Override
+    public boolean validateInput() {
+        return true;
+    }
+
     private void getHourFormatData() {
         final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale,
                 (mIs24HourView) ? "Hm" : "hm");
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 20f10b3..9cbbc5a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -53,9 +53,6 @@
     void setImeWindowStatus(in IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher);
     void setWindowState(int window, int state);
-    void buzzBeepBlinked();
-    void notificationLightOff();
-    void notificationLightPulse(int argb, int millisOn, int millisOff);
 
     void showRecentApps(boolean triggeredFromAltTab, boolean fromHome);
     void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index a9a6364..cb3a250 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -435,10 +435,6 @@
         }
     }
 
-    public static <T> boolean contains(@Nullable ArraySet<T> cur, T val) {
-        return (cur != null) ? cur.contains(val) : false;
-    }
-
     public static @NonNull <T> ArrayList<T> add(@Nullable ArrayList<T> cur, T val) {
         if (cur == null) {
             cur = new ArrayList<>();
@@ -459,6 +455,16 @@
         }
     }
 
+    /**
+     * Returns the given list, or an immutable empty list if the provided list is null
+     *
+     * @see Collections#emptyList
+     */
+
+    public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
+        return cur == null ? Collections.emptyList() : cur;
+    }
+
     public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
         return (cur != null) ? cur.contains(val) : false;
     }
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
new file mode 100644
index 0000000..d109a5a
--- /dev/null
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.internal.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** @hide */
+public class ObjectUtils {
+    private ObjectUtils() {}
+
+    @NonNull
+    public static <T> T firstNotNull(@Nullable T a, @NonNull T b) {
+        return a != null ? a : Preconditions.checkNotNull(b);
+    }
+}
diff --git a/core/java/com/android/internal/widget/DrawingSpace.java b/core/java/com/android/internal/widget/DrawingSpace.java
deleted file mode 100644
index b8222db..0000000
--- a/core/java/com/android/internal/widget/DrawingSpace.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-/**
- * Implementation of {@link android.widget.Space} that uses normal View drawing
- * rather than a no-op. Useful for dialogs and other places where the base View
- * class is too greedy when measured with AT_MOST.
- */
-public final class DrawingSpace extends View {
-    public DrawingSpace(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    public DrawingSpace(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public DrawingSpace(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DrawingSpace(Context context) {
-        this(context, null);
-    }
-
-    /**
-     * Compare to: {@link View#getDefaultSize(int, int)}
-     * <p>
-     * If mode is AT_MOST, return the child size instead of the parent size
-     * (unless it is too big).
-     */
-    private static int getDefaultSizeNonGreedy(int size, int measureSpec) {
-        int result = size;
-        int specMode = MeasureSpec.getMode(measureSpec);
-        int specSize = MeasureSpec.getSize(measureSpec);
-
-        switch (specMode) {
-            case MeasureSpec.UNSPECIFIED:
-                result = size;
-                break;
-            case MeasureSpec.AT_MOST:
-                result = Math.min(size, specSize);
-                break;
-            case MeasureSpec.EXACTLY:
-                result = specSize;
-                break;
-        }
-        return result;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        setMeasuredDimension(
-                getDefaultSizeNonGreedy(getSuggestedMinimumWidth(), widthMeasureSpec),
-                getDefaultSizeNonGreedy(getSuggestedMinimumHeight(), heightMeasureSpec));
-    }
-}
diff --git a/core/jni/android_os_seccomp.cpp b/core/jni/android_os_seccomp.cpp
index 2fe5d39..3f7bab2 100644
--- a/core/jni/android_os_seccomp.cpp
+++ b/core/jni/android_os_seccomp.cpp
@@ -147,6 +147,9 @@
     // Needed for kernel to restart syscalls
     AllowSyscall(f, 128); // __NR_restart_syscall
 
+    // b/35034743
+    AllowSyscall(f, 267); // __NR_fstatfs64
+
     Trap(f);
 
     if (SetValidateArchitectureJumpTarget(offset_to_32bit_filter, f) != 0)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 11eb47b..a6e43ff 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2365,6 +2365,11 @@
     <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by the CompanionDeviceManager to ensure that only the system can bind to it.
+         @hide -->
+    <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure
          that only the system can bind to it.
          @hide -->
diff --git a/core/res/res/drawable/btn_event_material.xml b/core/res/res/drawable/btn_event_material.xml
new file mode 100644
index 0000000..47c49cf
--- /dev/null
+++ b/core/res/res/drawable/btn_event_material.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+
+    <path
+        android:fillColor="#90ffffff"
+        android:pathData="M17 12h-5v5h5v-5zM16 1v2H8V1H6v2H5c-1.11 0-1.99 .9 -1.99 2L3 19c0 1.1 .89 2 2
+2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2h-1V1h-2zm3 18H5V8h14v11z" />
+    <path android:pathData="M0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/btn_keyboard_key_material.xml b/core/res/res/drawable/btn_keyboard_key_material.xml
new file mode 100644
index 0000000..14a9492f
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_material.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+
+    <path
+        android:fillColor="#90ffffff"
+        android:pathData="M20 5H4c-1.1 0-1.99 .9 -1.99 2L2 17c0 1.1 .9 2 2 2h16c1.1 0 2-.9
+2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2V8zm0 3h2v2h-2v-2zM8 8h2v2H8V8zm0
+3h2v2H8v-2zm-1 2H5v-2h2v2zm0-3H5V8h2v2zm9
+7H8v-2h8v2zm0-4h-2v-2h2v2zm0-3h-2V8h2v2zm3 3h-2v-2h2v2zm0-3h-2V8h2v2z" />
+    <path
+        android:pathData="M0 0h24v24H0zm0 0h24v24H0z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index 70833d6..8b95f9f 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -15,28 +15,17 @@
      limitations under the License.
 -->
 
-<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layoutDirection="ltr">
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
 
-    <!-- Provides a background for the time layout that extends into the button bar area. -->
-    <com.android.internal.widget.DrawingSpace
+    <LinearLayout
         android:id="@+id/time_header"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_column="@dimen/time_picker_column_start_material"
-        android:layout_row="0"
-        android:layout_rowSpan="3"
-        android:layout_gravity="center|fill"
-        android:layoutDirection="locale" />
-
-    <RelativeLayout
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_column="@dimen/time_picker_column_start_material"
-        android:layout_row="1"
-        android:layout_gravity="center|fill"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:gravity="center"
         android:paddingStart="?attr/dialogPreferredPadding"
         android:paddingEnd="?attr/dialogPreferredPadding">
 
@@ -44,10 +33,8 @@
             android:id="@+id/time_layout"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:layout_centerInParent="true"
             android:paddingTop="@dimen/timepicker_radial_picker_top_margin"
-            android:layout_marginBottom="-12dp">
+            android:orientation="horizontal">
 
             <!-- The hour should always be to the left of the separator,
                  regardless of the current locale's layout direction. -->
@@ -125,38 +112,71 @@
                 android:includeFontPadding="false"
                 android:button="@null" />
         </RadioGroup>
-    </RelativeLayout>
+    </LinearLayout>
 
-    <ViewStub
-        android:id="@id/topPanel"
-        android:layout="@layout/alert_dialog_title_material"
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/input_header"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:paddingStart="@dimen/dialog_padding_material"
+        android:paddingEnd="@dimen/dialog_padding_material"
+        android:paddingTop="20dp"
+        android:paddingBottom="20dp"
+        android:includeFontPadding="false"
+        android:textAppearance="@style/TextAppearance.Material.TimePicker.InputHeader"
+        android:text="@string/time_picker_header_text"/>
+
+    <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_column="@dimen/time_picker_column_end_material"
-        android:layout_row="0"
-        android:layout_gravity="top|fill_horizontal"
-        android:layoutDirection="locale" />
+        android:orientation="vertical">
 
-    <android.widget.RadialTimePickerView
-        android:id="@+id/radial_picker"
-        android:layout_width="@dimen/timepicker_radial_picker_dimen"
-        android:layout_height="@dimen/timepicker_radial_picker_dimen"
-        android:layout_column="@dimen/time_picker_column_end_material"
-        android:layout_row="1"
-        android:layout_rowWeight="1"
-        android:layout_gravity="center|fill"
-        android:layout_marginTop="@dimen/timepicker_radial_picker_top_margin"
-        android:layout_marginStart="@dimen/timepicker_radial_picker_horizontal_margin"
-        android:layout_marginEnd="@dimen/timepicker_radial_picker_horizontal_margin"
-        android:layoutDirection="locale" />
+        <android.widget.RadialTimePickerView
+            android:id="@+id/radial_picker"
+            android:layout_width="@dimen/timepicker_radial_picker_dimen"
+            android:layout_height="@dimen/timepicker_radial_picker_dimen"
+            android:layout_gravity="center|fill"
+            android:layout_marginTop="@dimen/timepicker_radial_picker_top_margin"
+            android:layout_marginStart="@dimen/timepicker_radial_picker_horizontal_margin"
+            android:layout_marginEnd="@dimen/timepicker_radial_picker_horizontal_margin"
+            android:layoutDirection="locale" />
 
-    <ViewStub
-        android:id="@id/buttonPanel"
-        android:layout="@layout/alert_dialog_button_bar_material"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_column="@dimen/time_picker_column_end_material"
-        android:layout_row="2"
-        android:layout_gravity="bottom|fill_horizontal"
-        android:layoutDirection="locale" />
-</GridLayout>
+        <android.widget.TextInputTimePickerView
+            android:id="@+id/input_mode"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingStart="?attr/dialogPreferredPadding"
+            android:paddingEnd="?attr/dialogPreferredPadding"
+            android:visibility="gone" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <ImageButton
+                android:id="@+id/toggle_mode"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="12dp"
+                android:layout_marginEnd="12dp"
+                android:padding="12dp"
+                android:background="?attr/selectableItemBackgroundBorderless"
+                android:tint="?attr/colorControlNormal"
+                android:src="@drawable/btn_keyboard_key_material"
+                android:contentDescription="@string/time_picker_text_input_mode_description" />
+            <Space
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1" />
+            <ViewStub
+                android:id="@id/buttonPanel"
+                android:layout="@layout/alert_dialog_button_bar_material"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layoutDirection="locale" />
+        </LinearLayout>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/time_picker_material.xml b/core/res/res/layout/time_picker_material.xml
index 37a7384..7597379 100644
--- a/core/res/res/layout/time_picker_material.xml
+++ b/core/res/res/layout/time_picker_material.xml
@@ -34,4 +34,53 @@
         android:layout_marginTop="@dimen/timepicker_radial_picker_top_margin"
         android:layout_marginStart="@dimen/timepicker_radial_picker_horizontal_margin"
         android:layout_marginEnd="@dimen/timepicker_radial_picker_horizontal_margin" />
+    <TextView
+        android:visibility="gone"
+        android:id="@+id/input_header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="@dimen/dialog_padding_material"
+        android:paddingEnd="@dimen/dialog_padding_material"
+        android:paddingTop="20dp"
+        android:paddingBottom="20dp"
+        android:includeFontPadding="false"
+        android:fontFamily="sans-serif-medium"
+        android:textSize="34sp"
+        android:textColor="@color/white"
+        android:text="@string/time_picker_header_text"/>
+    <android.widget.TextInputTimePickerView
+        android:id="@+id/input_mode"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="?attr/dialogPreferredPadding"
+        android:paddingEnd="?attr/dialogPreferredPadding"
+        android:visibility="gone" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ImageButton
+            android:id="@+id/toggle_mode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="12dp"
+            android:layout_marginEnd="12dp"
+            android:padding="12dp"
+            android:background="?attr/selectableItemBackgroundBorderless"
+            android:tint="?attr/colorControlNormal"
+            android:src="@drawable/btn_keyboard_key_material"
+            android:contentDescription="@string/time_picker_text_input_mode_description" />
+        <Space
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+        <ViewStub
+            android:id="@id/buttonPanel"
+            android:layout="@layout/alert_dialog_button_bar_material"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layoutDirection="locale" />
+    </LinearLayout>
+
 </LinearLayout>
diff --git a/core/res/res/layout/time_picker_text_input_material.xml b/core/res/res/layout/time_picker_text_input_material.xml
new file mode 100644
index 0000000..f17b80e
--- /dev/null
+++ b/core/res/res/layout/time_picker_text_input_material.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextView
+        android:id="@+id/top_label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="26dp"
+        android:layout_marginBottom="8dp"
+        android:text="@string/time_picker_prompt_label"
+        android:textAppearance="@style/TextAppearance.Material.TimePicker.PromptLabel" />
+
+    <RelativeLayout
+        android:id="@+id/input_block"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/top_label"
+        android:layoutDirection="ltr">
+        <EditText
+            android:id="@+id/input_hour"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content"
+            android:inputType="number"
+            android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" />
+        <TextView
+            android:id="@+id/label_hour"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/input_hour"
+            android:layout_alignStart="@id/input_hour"
+            android:text="@string/time_picker_hour_label"/>
+
+        <TextView
+            android:id="@+id/input_separator"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignEnd="@id/input_hour"
+            android:layout_alignBaseline="@id/input_hour"
+            android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" />
+
+        <EditText
+            android:id="@+id/input_minute"
+            android:layout_width="50dp"
+            android:layout_height="wrap_content"
+            android:layout_alignBaseline="@id/input_hour"
+            android:layout_toEndOf="@id/input_separator"
+            android:inputType="number"
+            android:textAppearance="@style/TextAppearance.Material.TimePicker.InputField" />
+        <TextView
+            android:id="@+id/label_minute"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/input_minute"
+            android:layout_alignStart="@id/input_minute"
+            android:text="@string/time_picker_minute_label"/>
+
+        <TextView
+            android:visibility="invisible"
+            android:id="@+id/label_error"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/input_hour"
+            android:layout_alignStart="@id/input_hour"
+            android:textColor="?attr/textColorError"
+            android:text="@string/time_picker_input_error" />
+    </RelativeLayout>
+    <Spinner
+        android:id="@+id/am_pm_spinner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBaseline="@id/input_block"
+        android:layout_alignParentEnd="true"/>
+
+</merge>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c548219..7f49f05 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3392,6 +3392,8 @@
             <flag name="flagRetrieveInteractiveWindows" value="0x00000040" />
             <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_ENABLE_ACCESSIBILITY_VOLUME} -->
             <flag name="flagEnableAccessibilityVolume" value="0x00000080" />
+            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} -->
+            <flag name="flagRequestAccessibilityButton" value="0x00000100" />
             <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_CAPTURE_FINGERPRINT_GESTURES} -->
             <flag name="flagCaptureFingerprintGestures" value="0x00000200" />
         </attr>
@@ -3429,18 +3431,9 @@
         <attr name="canRequestFilterKeyEvents" format="boolean" />
         <!-- Attribute whether the accessibility service wants to be able to control
              display magnification.
-             <p>
-             Required to allow setting the {@link android.accessibilityservice
-             #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag.
-             </p>
          -->
         <attr name="canControlMagnification" format="boolean" />
-        <!-- Attribute whether the accessibility service wants to be able to perform gestures.
-             <p>
-             Required to allow setting the {@link android.accessibilityservice
-             #AccessibilityServiceInfo#FLAG_CAN_PERFORM_GESTURES} flag.
-             </p>
-         -->
+        <!-- Attribute whether the accessibility service wants to be able to perform gestures. -->
         <attr name="canPerformGestures" format="boolean" />
         <!-- Attribute whether the accessibility service wants to be able to capture gestures from
              the fingerprint sensor.
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 927988f..9824051 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -475,9 +475,6 @@
 
     <dimen name="chooser_grid_padding">0dp</dimen>
 
-    <item type="dimen" format="integer" name="time_picker_column_start_material">0</item>
-    <item type="dimen" format="integer" name="time_picker_column_end_material">1</item>
-
     <item type="dimen" name="aerr_padding_list_top">15dp</item>
     <item type="dimen" name="aerr_padding_list_bottom">8dp</item>
 
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index ebe577c..e3fdcec 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -149,6 +149,7 @@
     <dimen name="timepicker_radial_picker_dimen">296dp</dimen>
     <dimen name="timepicker_radial_picker_top_margin">16dp</dimen>
     <dimen name="timepicker_radial_picker_horizontal_margin">16dp</dimen>
+    <dimen name="timepicker_edit_text_size">24sp</dimen>
 
     <!-- Used by RadialTimePicker in clock-style TimePicker. -->
     <dimen name="timepicker_selector_radius">20dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e00874f..19c5643 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4471,4 +4471,20 @@
     <string name="device_storage_monitor_notification_channel">Device storage</string>
     <!-- Channel name for UsbDeviceManager adb debugging notifications -->
     <string name="adb_debugging_notification_channel_tv">USB debugging</string>
+
+    <!-- Label for the time picker hour input field. [CHAR LIMIT=20] -->
+    <string name="time_picker_hour_label">hour</string>
+    <!-- Label for the time picker minute input field. [CHAR LIMIT=20] -->
+    <string name="time_picker_minute_label">minute</string>
+    <!-- The title for the time picker dialog. [CHAR LIMIT=NONE] -->
+    <string name="time_picker_header_text">Set time</string>
+    <!-- Error shown to the user if they type in invalid hour or minute in the time picker. [CHAR LIMIT=NONE] -->
+    <string name="time_picker_input_error">Enter a valid time</string>
+    <!-- Label shown to the user in time picker to let them know that should type in time. [CHAR LIMIT=NONE] -->
+    <string name="time_picker_prompt_label">Type in time</string>
+    <!-- Accessibility string used for describing the button in time picker that changes the dialog to text input mode. [CHAR LIMIT=NONE] -->
+    <string name="time_picker_text_input_mode_description">Switch to text input mode for the time input.</string>
+    <!-- Accessibility string used for describing the button in time picker that changes the dialog to circular clock mode. [CHAR LIMIT=NONE] -->
+    <string name="time_picker_radial_mode_description">Switch to clock mode for the time input.</string>
+
 </resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 1e15348..8f061a3 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -426,6 +426,21 @@
         <item name="textColor">@color/primary_text_secondary_when_activated_material_inverse</item>
     </style>
 
+    <style name="TextAppearance.Material.TimePicker.InputHeader" parent="TextAppearance.Material">
+        <item name="textSize">@dimen/text_size_display_1_material</item>
+        <item name="textColor">@color/white</item>
+        <item name="fontFamily">sans-serif-medium</item>
+    </style>
+
+    <style name="TextAppearance.Material.TimePicker.InputField" parent="TextAppearance.Material">
+        <item name="textSize">@dimen/timepicker_edit_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Material.TimePicker.PromptLabel" parent="TextAppearance.Material">
+        <item name="textSize">@dimen/timepicker_text_size_normal</item>
+        <item name="fontFamily">sans-serif-medium</item>
+    </style>
+
     <style name="TextAppearance.Material.DatePicker.YearLabel" parent="TextAppearance.Material">
         <item name="textColor">@color/primary_text_secondary_when_activated_material_inverse</item>
         <item name="textSize">@dimen/date_picker_year_label_size</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b95c20b..a420055 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2842,4 +2842,21 @@
   <!-- Accessibility fingerprint gestures -->
   <java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />
   <java-symbol type="string" name="capability_desc_canCaptureFingerprintGestures" />
+
+  <!-- Time picker -->
+  <java-symbol type="id" name="toggle_mode"/>
+  <java-symbol type="id" name="input_mode"/>
+  <java-symbol type="id" name="input_header"/>
+  <java-symbol type="id" name="input_separator"/>
+  <java-symbol type="id" name="input_hour"/>
+  <java-symbol type="id" name="input_minute"/>
+  <java-symbol type="id" name="am_pm_spinner"/>
+  <java-symbol type="id" name="label_hour"/>
+  <java-symbol type="id" name="label_minute"/>
+  <java-symbol type="id" name="label_error"/>
+  <java-symbol type="layout" name="time_picker_text_input_material"/>
+  <java-symbol type="drawable" name="btn_keyboard_key_material"/>
+  <java-symbol type="drawable" name="btn_event_material"/>
+  <java-symbol type="string" name="time_picker_text_input_mode_description"/>
+  <java-symbol type="string" name="time_picker_radial_mode_description"/>
 </resources>
diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
index bd25500..e2e6883 100644
--- a/core/tests/coretests/src/android/net/RecommendationRequestTest.java
+++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
@@ -96,7 +96,7 @@
 
         RecommendationRequest parceled = passThroughParcel(request);
 
-        assertEquals(0, parceled.getLastSelectedNetworkId());
+        assertEquals(-1, parceled.getLastSelectedNetworkId());
         assertEquals(0, parceled.getLastSelectedNetworkTimestamp());
     }
 
diff --git a/core/tests/coretests/src/android/provider/SettingsTest.java b/core/tests/coretests/src/android/provider/SettingsTest.java
index d76980a..1ff2056 100644
--- a/core/tests/coretests/src/android/provider/SettingsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsTest.java
@@ -392,7 +392,6 @@
                  Settings.Secure.BACKUP_PROVISIONED,
                  Settings.Secure.BACKUP_TRANSPORT,
                  Settings.Secure.BLUETOOTH_HCI_LOG,
-                 Settings.Secure.BRIGHTNESS_USE_TWILIGHT,  // Candidate for backup?
                  Settings.Secure.CARRIER_APPS_HANDLED,
                  Settings.Secure.COMPLETED_CATEGORY_PREFIX,
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index a73f5a6..44fcd13 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -16,9 +16,17 @@
 
 package android.view;
 
+import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
 import android.os.SystemClock;
 import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.frameworks.coretests.R;
 
 public class ViewAttachTest extends
         ActivityInstrumentationTestCase2<ViewAttachTestActivity> {
@@ -51,4 +59,38 @@
             SystemClock.sleep(250);
         }
     }
+
+    /**
+     * Make sure that on any attached view, if the view is full-screen and hosted
+     * on a round device, the round scrollbars will be displayed even if the activity
+     * window is offset.
+     *
+     * @throws Throwable
+     */
+    @UiThreadTest
+    public void testRoundScrollbars() throws Throwable {
+        final ViewAttachTestActivity activity = getActivity();
+        final View rootView = activity.getWindow().getDecorView();
+        final WindowManager.LayoutParams params =
+            new WindowManager.LayoutParams(
+                rootView.getWidth(),
+                rootView.getHeight(),
+                50, /* xPosition */
+                0, /* yPosition */
+                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                PixelFormat.TRANSLUCENT);
+
+        rootView.setLayoutParams(params);
+
+        View contentView = activity.findViewById(R.id.view_attach_view);
+        boolean shouldDrawRoundScrollbars = contentView.shouldDrawRoundScrollbar();
+
+        if (activity.getResources().getConfiguration().isScreenRound()) {
+            assertTrue(shouldDrawRoundScrollbars);
+        } else {
+            // Never draw round scrollbars on non-round devices.
+            assertFalse(shouldDrawRoundScrollbars);
+        }
+    }
 }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c5961ab..039ab1f 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -132,6 +132,7 @@
         <permission name="android.permission.ACCESS_IMS_CALL_SERVICE"/>
         <permission name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"/>
         <permission name="android.permission.BIND_CARRIER_SERVICES"/>
+        <permission name="android.permission.BIND_IMS_SERVICE"/>
         <permission name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"/>
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
@@ -339,4 +340,4 @@
         <permission name="android.permission.CONTROL_VPN"/>
     </privapp-permissions>
 
-</permissions>
\ No newline at end of file
+</permissions>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index a799fdf..692199d 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -192,7 +192,7 @@
 ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
     hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT
     hwui_c_includes += \
-        $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) \
+        $(call intermediates-dir-for,STATIC_LIBRARIES,TARGET,) \
         frameworks/rs/cpp \
         frameworks/rs
 endif
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
index 37126a6..f69da48 100644
--- a/libs/hwui/hwui_static_deps.mk
+++ b/libs/hwui/hwui_static_deps.mk
@@ -29,5 +29,5 @@
     libandroidfw
 
 ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
-    LOCAL_SHARED_LIBRARIES += libRS libRScpp
+    LOCAL_SHARED_LIBRARIES += libRScpp
 endif
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index d05e7f6..2ead5c5 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -24,6 +24,7 @@
 #include <SkGaussianEdgeShader.h>
 #include <SkPathOps.h>
 #include <SkRRectsGaussianEdgeMaskFilter.h>
+#include <SkShadowUtils.h>
 
 namespace android {
 namespace uirenderer {
@@ -115,498 +116,6 @@
     }
 }
 
-/**
- * @param canvas             the destination for the shadow draws
- * @param shape              the shape casting the shadow
- * @param casterZValue       the Z value of the caster RRect
- * @param ambientAlpha       the maximum alpha value to use when drawing the ambient shadow
- * @param draw               the function used to draw 'shape'
- */
-template <typename Shape, typename F>
-static void DrawAmbientShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
-        float ambientAlpha, F&& draw) {
-    if (ambientAlpha <= 0) {
-        return;
-    }
-
-    const float kHeightFactor = 1.f/128.f;
-    const float kGeomFactor = 64;
-
-    float umbraAlpha = 1 / (1 + SkMaxScalar(casterZValue*kHeightFactor, 0));
-    float radius = casterZValue*kHeightFactor*kGeomFactor;
-
-    sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
-            SkBlurMask::ConvertRadiusToSigma(radius), SkBlurMaskFilter::kNone_BlurFlag);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setMaskFilter(std::move(mf));
-    paint.setARGB(ambientAlpha*umbraAlpha, 0, 0, 0);
-
-    draw(shape, paint);
-}
-
-/**
- * @param canvas             the destination for the shadow draws
- * @param shape              the shape casting the shadow
- * @param casterZValue       the Z value of the caster RRect
- * @param lightPos           the position of the light casting the shadow
- * @param lightWidth
- * @param spotAlpha          the maximum alpha value to use when drawing the spot shadow
- * @param draw               the function used to draw 'shape'
- */
-template <typename Shape, typename F>
-static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float casterZValue,
-        float spotAlpha, F&& draw) {
-    if (spotAlpha <= 0) {
-        return;
-    }
-
-    const Vector3 lightPos = SkiaPipeline::getLightCenter();
-    float zRatio = casterZValue / (lightPos.z - casterZValue);
-    // clamp
-    if (zRatio < 0.0f) {
-        zRatio = 0.0f;
-    } else if (zRatio > 0.95f) {
-        zRatio = 0.95f;
-    }
-
-    float blurRadius = SkiaPipeline::getLightRadius()*zRatio;
-
-    SkAutoCanvasRestore acr(canvas, true);
-
-    sk_sp<SkMaskFilter> mf = SkBlurMaskFilter::Make(kNormal_SkBlurStyle,
-            SkBlurMask::ConvertRadiusToSigma(blurRadius), SkBlurMaskFilter::kNone_BlurFlag);
-
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setMaskFilter(std::move(mf));
-    paint.setARGB(spotAlpha, 0, 0, 0);
-
-    // approximate projection by translating and scaling projected offset of bounds center
-    // TODO: compute the actual 2D projection
-    SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
-    canvas->scale(scale, scale);
-    SkPoint center = SkPoint::Make(shape.getBounds().centerX(), shape.getBounds().centerY());
-    SkMatrix ctmInverse;
-    if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
-        ALOGW("Matrix is degenerate. Will not render shadow!");
-        return;
-    }
-    SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
-    ctmInverse.mapPoints(&lightPos2D, 1);
-    canvas->translate(zRatio*(center.fX - lightPos2D.fX), zRatio*(center.fY - lightPos2D.fY));
-
-    draw(shape, paint);
-}
-
-#define MAX_BLUR_RADIUS 16383.75f
-#define MAX_PAD         64
-
-/**
- * @param casterRect         the rectangle bounds of the RRect casting the shadow
- * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
- * @param ambientAlpha       the maximum alpha value to use when drawing the ambient shadow
- * @param spotAlpha          the maximum alpha value to use when drawing the spot shadow
- * @param casterAlpha        the alpha value of the RRect casting the shadow (0.0-1.0 range)
- * @param casterZValue       the Z value of the caster RRect
- * @param scaleFactor        the scale needed to map from src-space to device-space
- * @param canvas             the destination for the shadow draws
- */
-static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadius,
-        SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue,
-        SkScalar scaleFactor, SkCanvas* canvas) {
-    SkASSERT(casterCornerRadius >= 0.0f);
-
-    // For all of these, we need to ensure we have a rrect with radius >= 0.5f in device space
-    const SkScalar minRadius = 0.5f / scaleFactor;
-
-    const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
-            SkScalarHalf(casterRect.height()));
-    const bool isRect = casterCornerRadius <= minRadius;
-
-    sk_sp<SkShader> edgeShader(SkGaussianEdgeShader::Make());
-
-    if (ambientAlpha > 0.0f) {
-        static const float kHeightFactor = 1.0f / 128.0f;
-        static const float kGeomFactor = 64.0f;
-
-        SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
-        // the device-space radius sent to the blur shader must fit in 14.2 fixed point
-        if (srcSpaceAmbientRadius*scaleFactor > MAX_BLUR_RADIUS) {
-            srcSpaceAmbientRadius = MAX_BLUR_RADIUS/scaleFactor;
-        }
-        const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
-        const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
-
-        // For the ambient rrect, we inset the offset rect by half the srcSpaceAmbientRadius
-        // to get our stroke shape.
-        SkScalar ambientPathOutset = std::max(ambientOffset - srcSpaceAmbientRadius * 0.5f,
-                minRadius);
-
-        SkRRect ambientRRect;
-        const SkRect temp = casterRect.makeOutset(ambientPathOutset, ambientPathOutset);
-        if (isOval) {
-            ambientRRect = SkRRect::MakeOval(temp);
-        } else if (isRect) {
-            ambientRRect = SkRRect::MakeRectXY(temp, ambientPathOutset, ambientPathOutset);
-        } else {
-            ambientRRect = SkRRect::MakeRectXY(temp, casterCornerRadius + ambientPathOutset,
-                    casterCornerRadius + ambientPathOutset);
-        }
-
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        // we outset the stroke a little to cover up AA on the interior edge
-        float pad = 0.5f;
-        paint.setStrokeWidth(srcSpaceAmbientRadius + 2.0f * pad);
-        // handle scale of radius and pad due to CTM
-        pad *= scaleFactor;
-        const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
-        SkASSERT(devSpaceAmbientRadius <= MAX_BLUR_RADIUS);
-        SkASSERT(pad < MAX_PAD);
-        // convert devSpaceAmbientRadius to 14.2 fixed point and place in the R & G components
-        // convert pad to 6.2 fixed point and place in the B component
-        uint16_t iDevSpaceAmbientRadius = (uint16_t)(4.0f * devSpaceAmbientRadius);
-        paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, iDevSpaceAmbientRadius >> 8,
-                iDevSpaceAmbientRadius & 0xff, (unsigned char)(4.0f * pad)));
-
-        paint.setShader(edgeShader);
-        canvas->drawRRect(ambientRRect, paint);
-    }
-
-    if (spotAlpha > 0.0f) {
-        const Vector3 lightPos = SkiaPipeline::getLightCenter();
-        float zRatio = casterZValue / (lightPos.z - casterZValue);
-        // clamp
-        if (zRatio < 0.0f) {
-            zRatio = 0.0f;
-        } else if (zRatio > 0.95f) {
-            zRatio = 0.95f;
-        }
-
-        const SkScalar lightWidth = SkiaPipeline::getLightRadius();
-        SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
-        // the device-space radius sent to the blur shader must fit in 14.2 fixed point
-        if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) {
-            srcSpaceSpotRadius = MAX_BLUR_RADIUS/scaleFactor;
-        }
-
-        SkRRect spotRRect;
-        if (isOval) {
-            spotRRect = SkRRect::MakeOval(casterRect);
-        } else if (isRect) {
-            spotRRect = SkRRect::MakeRectXY(casterRect, minRadius, minRadius);
-        } else {
-            spotRRect = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
-        }
-
-        SkRRect spotShadowRRect;
-        // Compute the scale and translation for the spot shadow.
-        const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
-        spotRRect.transform(SkMatrix::MakeScale(scale, scale), &spotShadowRRect);
-
-        SkPoint center = SkPoint::Make(spotShadowRRect.rect().centerX(),
-                                       spotShadowRRect.rect().centerY());
-        SkMatrix ctmInverse;
-        if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
-            ALOGW("Matrix is degenerate. Will not render spot shadow!");
-            return;
-        }
-        SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
-        ctmInverse.mapPoints(&lightPos2D, 1);
-        const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
-                zRatio*(center.fY - lightPos2D.fY));
-
-        SkAutoCanvasRestore acr(canvas, true);
-
-        // We want to extend the stroked area in so that it meets up with the caster
-        // geometry. The stroked geometry will, by definition already be inset half the
-        // stroke width but we also have to account for the scaling.
-        // We also add 1/2 to cover up AA on the interior edge.
-        SkScalar scaleOffset = (scale - 1.0f) * SkTMax(SkTMax(SkTAbs(casterRect.fLeft),
-                SkTAbs(casterRect.fRight)), SkTMax(SkTAbs(casterRect.fTop),
-                SkTAbs(casterRect.fBottom)));
-        SkScalar insetAmount = spotOffset.length() - (0.5f * srcSpaceSpotRadius) +
-                scaleOffset + 0.5f;
-
-        // Compute area
-        SkScalar strokeWidth = srcSpaceSpotRadius + insetAmount;
-        SkScalar strokedArea = 2.0f*strokeWidth * (spotShadowRRect.width()
-                + spotShadowRRect.height());
-        SkScalar filledArea = (spotShadowRRect.height() + srcSpaceSpotRadius)
-                * (spotShadowRRect.width() + srcSpaceSpotRadius);
-
-        SkPaint paint;
-        paint.setAntiAlias(true);
-
-        // If the area of the stroked geometry is larger than the fill geometry, just fill it.
-        if (strokedArea > filledArea || casterAlpha < 1.0f || insetAmount < 0.0f) {
-            paint.setStyle(SkPaint::kStrokeAndFill_Style);
-            paint.setStrokeWidth(srcSpaceSpotRadius);
-        } else {
-            // Since we can't have unequal strokes, inset the shadow rect so the inner
-            // and outer edges of the stroke will land where we want.
-            SkRect insetRect = spotShadowRRect.rect().makeInset(insetAmount/2.0f, insetAmount/2.0f);
-            SkScalar insetRad = SkTMax(spotShadowRRect.getSimpleRadii().fX - insetAmount/2.0f,
-                    minRadius);
-            spotShadowRRect = SkRRect::MakeRectXY(insetRect, insetRad, insetRad);
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(strokeWidth);
-        }
-
-        // handle scale of radius and pad due to CTM
-        const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
-        SkASSERT(devSpaceSpotRadius <= MAX_BLUR_RADIUS);
-
-        const SkScalar devSpaceSpotPad = 0;
-        SkASSERT(devSpaceSpotPad < MAX_PAD);
-
-        // convert devSpaceSpotRadius to 14.2 fixed point and place in the R & G
-        // components convert devSpaceSpotPad to 6.2 fixed point and place in the B component
-        uint16_t iDevSpaceSpotRadius = (uint16_t)(4.0f * devSpaceSpotRadius);
-        paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, iDevSpaceSpotRadius >> 8,
-                iDevSpaceSpotRadius & 0xff, (unsigned char)(4.0f * devSpaceSpotPad)));
-        paint.setShader(edgeShader);
-
-        canvas->translate(spotOffset.fX, spotOffset.fY);
-        canvas->drawRRect(spotShadowRRect, paint);
-    }
-}
-
-/**
- * @param casterRect         the rectangle bounds of the RRect casting the shadow
- * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
- * @param ambientAlpha       the maximum alpha value to use when drawing the ambient shadow
- * @param spotAlpha          the maximum alpha value to use when drawing the spot shadow
- * @param casterZValue       the Z value of the caster RRect
- * @param scaleFactor        the scale needed to map from src-space to device-space
- * @param clipRR             the oval or rect with which the drawn roundrect must be intersected
- * @param canvas             the destination for the shadow draws
- */
-static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCornerRadius,
-        SkScalar ambientAlpha, SkScalar spotAlpha, SkScalar casterZValue, SkScalar scaleFactor,
-        const SkRRect& clipRR, SkCanvas* canvas) {
-    SkASSERT(casterCornerRadius >= 0.0f);
-
-    const bool isOval = casterCornerRadius >= std::max(SkScalarHalf(casterRect.width()),
-            SkScalarHalf(casterRect.height()));
-
-    if (ambientAlpha > 0.0f) {
-        static const float kHeightFactor = 1.0f / 128.0f;
-        static const float kGeomFactor = 64.0f;
-
-        const SkScalar srcSpaceAmbientRadius = casterZValue * kHeightFactor * kGeomFactor;
-        const SkScalar devSpaceAmbientRadius = srcSpaceAmbientRadius * scaleFactor;
-
-        const float umbraAlpha = 1.0f / (1.0f + std::max(casterZValue * kHeightFactor, 0.0f));
-        const SkScalar ambientOffset = srcSpaceAmbientRadius * umbraAlpha;
-
-        const SkRect srcSpaceAmbientRect = casterRect.makeOutset(ambientOffset, ambientOffset);
-        SkRect devSpaceAmbientRect;
-        canvas->getTotalMatrix().mapRect(&devSpaceAmbientRect, srcSpaceAmbientRect);
-
-        SkRRect devSpaceAmbientRRect;
-        if (isOval) {
-            devSpaceAmbientRRect = SkRRect::MakeOval(devSpaceAmbientRect);
-        } else {
-            const SkScalar devSpaceCornerRadius = scaleFactor * (casterCornerRadius + ambientOffset);
-            devSpaceAmbientRRect = SkRRect::MakeRectXY(devSpaceAmbientRect, devSpaceCornerRadius,
-                    devSpaceCornerRadius);
-        }
-
-        const SkRect srcSpaceAmbClipRect = clipRR.rect().makeOutset(ambientOffset, ambientOffset);
-        SkRect devSpaceAmbClipRect;
-        canvas->getTotalMatrix().mapRect(&devSpaceAmbClipRect, srcSpaceAmbClipRect);
-        SkRRect devSpaceAmbientClipRR;
-        if (clipRR.isOval()) {
-            devSpaceAmbientClipRR = SkRRect::MakeOval(devSpaceAmbClipRect);
-        } else {
-            SkASSERT(clipRR.isRect());
-            devSpaceAmbientClipRR = SkRRect::MakeRect(devSpaceAmbClipRect);
-        }
-
-        SkRect cover = srcSpaceAmbClipRect;
-        if (!cover.intersect(srcSpaceAmbientRect)) {
-            return;
-        }
-
-        SkPaint paint;
-        paint.setColor(SkColorSetARGB((unsigned char) ambientAlpha, 0, 0, 0));
-        paint.setMaskFilter(SkRRectsGaussianEdgeMaskFilter::Make(devSpaceAmbientRRect,
-            devSpaceAmbientClipRR, devSpaceAmbientRadius));
-        canvas->drawRect(cover, paint);
-    }
-
-    if (spotAlpha > 0.0f) {
-        const Vector3 lightPos = SkiaPipeline::getLightCenter();
-        float zRatio = casterZValue / (lightPos.z - casterZValue);
-        // clamp
-        if (zRatio < 0.0f) {
-            zRatio = 0.0f;
-        } else if (zRatio > 0.95f) {
-            zRatio = 0.95f;
-        }
-
-        const SkScalar lightWidth = SkiaPipeline::getLightRadius();
-        const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
-        const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
-
-        // Compute the scale and translation for the spot shadow.
-        const SkScalar scale = lightPos.z / (lightPos.z - casterZValue);
-        const SkMatrix spotMatrix = SkMatrix::MakeScale(scale, scale);
-
-        SkRect srcSpaceScaledRect = casterRect;
-        spotMatrix.mapRect(&srcSpaceScaledRect);
-        srcSpaceScaledRect.outset(SkScalarHalf(srcSpaceSpotRadius),
-                SkScalarHalf(srcSpaceSpotRadius));
-
-        SkRRect srcSpaceSpotRRect;
-        if (isOval) {
-            srcSpaceSpotRRect = SkRRect::MakeOval(srcSpaceScaledRect);
-        } else {
-            srcSpaceSpotRRect = SkRRect::MakeRectXY(srcSpaceScaledRect, casterCornerRadius * scale,
-                    casterCornerRadius * scale);
-        }
-
-        SkPoint center = SkPoint::Make(srcSpaceSpotRRect.rect().centerX(),
-                srcSpaceSpotRRect.rect().centerY());
-        SkMatrix ctmInverse;
-        if (!canvas->getTotalMatrix().invert(&ctmInverse)) {
-            ALOGW("Matrix is degenerate. Will not render spot shadow!");
-            return;
-        }
-        SkPoint lightPos2D = SkPoint::Make(lightPos.x, lightPos.y);
-        ctmInverse.mapPoints(&lightPos2D, 1);
-        const SkPoint spotOffset = SkPoint::Make(zRatio*(center.fX - lightPos2D.fX),
-                zRatio*(center.fY - lightPos2D.fY));
-
-        SkAutoCanvasRestore acr(canvas, true);
-        canvas->translate(spotOffset.fX, spotOffset.fY);
-
-        SkRect devSpaceScaledRect;
-        canvas->getTotalMatrix().mapRect(&devSpaceScaledRect, srcSpaceScaledRect);
-
-        SkRRect devSpaceSpotRRect;
-        if (isOval) {
-            devSpaceSpotRRect = SkRRect::MakeOval(devSpaceScaledRect);
-        } else {
-            const SkScalar devSpaceScaledCornerRadius = casterCornerRadius * scale * scaleFactor;
-            devSpaceSpotRRect = SkRRect::MakeRectXY(devSpaceScaledRect, devSpaceScaledCornerRadius,
-                    devSpaceScaledCornerRadius);
-        }
-
-        SkPaint paint;
-        paint.setColor(SkColorSetARGB((unsigned char) spotAlpha, 0, 0, 0));
-
-        SkRect srcSpaceScaledClipRect = clipRR.rect();
-        spotMatrix.mapRect(&srcSpaceScaledClipRect);
-        srcSpaceScaledClipRect.outset(SkScalarHalf(srcSpaceSpotRadius),
-                SkScalarHalf(srcSpaceSpotRadius));
-
-        SkRect devSpaceScaledClipRect;
-        canvas->getTotalMatrix().mapRect(&devSpaceScaledClipRect, srcSpaceScaledClipRect);
-        SkRRect devSpaceSpotClipRR;
-        if (clipRR.isOval()) {
-            devSpaceSpotClipRR = SkRRect::MakeOval(devSpaceScaledClipRect);
-        } else {
-            SkASSERT(clipRR.isRect());
-            devSpaceSpotClipRR = SkRRect::MakeRect(devSpaceScaledClipRect);
-        }
-
-        paint.setMaskFilter(SkRRectsGaussianEdgeMaskFilter::Make(devSpaceSpotRRect,
-            devSpaceSpotClipRR, devSpaceSpotRadius));
-
-        SkRect cover = srcSpaceScaledClipRect;
-        if (!cover.intersect(srcSpaceSpotRRect.rect())) {
-            return;
-        }
-
-        canvas->drawRect(cover, paint);
-    }
-}
-
-/**
- * @param casterRect         the rectangle bounds of the RRect casting the shadow
- * @param casterCornerRadius the x&y radius for all the corners of the RRect casting the shadow
- * @param casterClipRect     a rectangular clip that must be intersected with the
- *                           shadow-casting RRect prior to casting the shadow
- * @param revealClip         a circular clip that must be interested with the castClipRect
- *                           and the shadow-casting rect prior to casting the shadow
- * @param ambientAlpha       the maximum alpha value to use when drawing the ambient shadow
- * @param spotAlpha          the maximum alpha value to use when drawing the spot shadow
- * @param casterAlpha        the alpha value of the RRect casting the shadow (0.0-1.0 range)
- * @param casterZValue       the Z value of the caster RRect
- * @param canvas             the destination for the shadow draws
- *
- * We have special cases for 4 round rect shadow draws:
- *    1) a RRect clipped by a reveal animation
- *    2) a RRect clipped by a rectangle
- *    3) an unclipped RRect with non-uniform scale
- *    4) an unclipped RRect with uniform scale
- * 1,2 and 4 require that the scale is uniform.
- * 1 and 2 require that rects stay rects.
- */
-static bool DrawShadowsAsRRects(const SkRect& casterRect, SkScalar casterCornerRadius,
-        const SkRect& casterClipRect, const RevealClip& revealClip, SkScalar ambientAlpha,
-        SkScalar spotAlpha, SkScalar casterAlpha, SkScalar casterZValue, SkCanvas* canvas) {
-    SkScalar scaleFactors[2];
-    if (!canvas->getTotalMatrix().getMinMaxScales(scaleFactors)) {
-        ALOGW("Matrix is degenerate. Will not render shadow!");
-        return false;
-    }
-
-    // The casterClipRect will be empty when bounds clipping is disabled
-    bool casterIsClippedByRect = !casterClipRect.isEmpty();
-    bool uniformScale = scaleFactors[0] == scaleFactors[1];
-
-    if (revealClip.willClip()) {
-        if (casterIsClippedByRect || !uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
-            return false;  // Fall back to the slow path since PathOps are required
-        }
-
-        const float revealRadius = revealClip.getRadius();
-        SkRect revealClipRect = SkRect::MakeLTRB(revealClip.getX()-revealRadius,
-                revealClip.getY()-revealRadius, revealClip.getX()+revealRadius,
-                revealClip.getY()+revealRadius);
-        SkRRect revealClipRR = SkRRect::MakeOval(revealClipRect);
-
-        DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
-                casterZValue, scaleFactors[0], revealClipRR, canvas);
-        return true;
-    }
-
-    if (casterIsClippedByRect) {
-        if (!uniformScale || !canvas->getTotalMatrix().rectStaysRect()) {
-            return false;  // Fall back to the slow path since PathOps are required
-        }
-
-        SkRRect casterClipRR = SkRRect::MakeRect(casterClipRect);
-
-        DrawRRectShadowsWithClip(casterRect, casterCornerRadius, ambientAlpha, spotAlpha,
-                casterZValue, scaleFactors[0], casterClipRR, canvas);
-        return true;
-    }
-
-    // The fast path needs uniform scale
-    if (!uniformScale) {
-        SkRRect casterRR = SkRRect::MakeRectXY(casterRect, casterCornerRadius, casterCornerRadius);
-        DrawAmbientShadowGeneral(canvas, casterRR, casterZValue, ambientAlpha,
-                [&](const SkRRect& rrect, const SkPaint& paint) {
-                    canvas->drawRRect(rrect, paint);
-                });
-        DrawSpotShadowGeneral(canvas, casterRR, casterZValue, spotAlpha,
-                [&](const SkRRect& rrect, const SkPaint& paint) {
-                canvas->drawRRect(rrect, paint);
-                });
-        return true;
-    }
-
-    DrawRRectShadows(casterRect, casterCornerRadius, ambientAlpha, spotAlpha, casterAlpha,
-            casterZValue, scaleFactors[0], canvas);
-    return true;
-}
-
 // copied from FrameBuilder::deferShadow
 void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster) {
     const RenderProperties& casterProperties = caster->getNodeProperties();
@@ -626,8 +135,8 @@
         return;
     }
 
-    float ambientAlpha = SkiaPipeline::getAmbientShadowAlpha()*casterAlpha;
-    float spotAlpha = SkiaPipeline::getSpotShadowAlpha()*casterAlpha;
+    float ambientAlpha = (SkiaPipeline::getAmbientShadowAlpha()/255.f)*casterAlpha;
+    float spotAlpha = (SkiaPipeline::getSpotShadowAlpha()/255.f)*casterAlpha;
     const float casterZValue = casterProperties.getZ();
 
     const RevealClip& revealClip = casterProperties.getRevealClip();
@@ -659,19 +168,7 @@
     hwuiMatrix.copyTo(shadowMatrix);
     canvas->concat(shadowMatrix);
 
-    const Outline& casterOutline = casterProperties.getOutline();
-    Rect possibleRect;
-    float radius;
-    if (casterOutline.getAsRoundRect(&possibleRect, &radius)) {
-        if (DrawShadowsAsRRects(possibleRect.toSkRect(), radius, casterClipRect, revealClip,
-                ambientAlpha, spotAlpha, casterAlpha, casterZValue, canvas)) {
-            return;
-        }
-    }
-
-    // Hard cases and calls to general shadow code
     const SkPath* casterOutlinePath = casterProperties.getOutline().getPath();
-
     // holds temporary SkPath to store the result of intersections
     SkPath tmpPath;
     const SkPath* casterPath = casterOutlinePath;
@@ -691,16 +188,11 @@
         Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, &tmpPath);
         casterPath = &tmpPath;
     }
-
-    DrawAmbientShadowGeneral(canvas, *casterPath, casterZValue, ambientAlpha,
-            [&](const SkPath& path, const SkPaint& paint) {
-                canvas->drawPath(path, paint);
-            });
-
-    DrawSpotShadowGeneral(canvas, *casterPath, casterZValue, spotAlpha,
-            [&](const SkPath& path, const SkPaint& paint) {
-                canvas->drawPath(path, paint);
-            });
+    const Vector3 lightPos = SkiaPipeline::getLightCenter();
+    SkPoint3 skiaLightPos = SkPoint3::Make(lightPos.x, lightPos.y, lightPos.z);
+    SkShadowUtils::DrawShadow(canvas, *casterPath, casterZValue, skiaLightPos,
+            SkiaPipeline::getLightRadius(), ambientAlpha, spotAlpha, SK_ColorBLACK,
+            casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0);
 }
 
 }; // namespace skiapipeline
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 0d567f7..7dfc2ee 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -80,6 +80,7 @@
     delete mStencil;
     mStencil = nullptr;
 
+    destroyLayersInUpdater();
     GpuMemoryTracker::onGpuContextDestroyed();
 }
 
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index a44fa9d..f78bf7a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -99,8 +99,6 @@
         mActiveLayerUpdaters.erase(layerUpdater);
     }
 
-    void destroyLayersInUpdater();
-
     // TODO: This system is a little clunky feeling, this could use some
     // more thinking...
     void postDecStrong(VirtualLightRefBase* object);
@@ -121,6 +119,7 @@
 private:
     void interruptForFunctorInvoke();
     void resumeFromFunctorInvoke();
+    void destroyLayersInUpdater();
 
     explicit RenderState(renderthread::RenderThread& thread);
     ~RenderState();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5a7de1d..a53e5e0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -570,7 +570,6 @@
         }
         mRenderPipeline->onDestroyHardwareResources();
     }
-    mRenderThread.renderState().destroyLayersInUpdater();
 }
 
 void CanvasContext::trimMemory(RenderThread& thread, int level) {
diff --git a/opengl/java/android/opengl/GLU.java b/opengl/java/android/opengl/GLU.java
index ed64556..ef9bf167 100644
--- a/opengl/java/android/opengl/GLU.java
+++ b/opengl/java/android/opengl/GLU.java
@@ -203,8 +203,8 @@
      * @param view the current view, {x, y, width, height}
      * @param viewOffset the offset into the view array where the view vector
      *        data starts.
-     * @param obj the output vector {objX, objY, objZ}, that returns the
-     *        computed object coordinates.
+     * @param obj the output vector {objX, objY, objZ, objW}, that returns the
+     *        computed homogeneous object coordinates.
      * @param objOffset the offset into the obj array where the obj vector data
      *        starts.
      * @return A return value of GL10.GL_TRUE indicates success, a return value
diff --git a/packages/CompanionDeviceManager/Android.mk b/packages/CompanionDeviceManager/Android.mk
new file mode 100644
index 0000000..f730356
--- /dev/null
+++ b/packages/CompanionDeviceManager/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CompanionDeviceManager
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
new file mode 100644
index 0000000..3eede54
--- /dev/null
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2017 Google Inc.
+ *
+ * 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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.companiondevicemanager">
+
+    <permission
+        android:name="com.android.companiondevicemanager.permission.BIND"
+        android:protectionLevel="signature" />
+
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+
+    <application
+        android:allowClearUserData="true"
+        android:label="@string/app_label"
+        android:allowBackup="false"
+        android:supportsRtl="true">
+
+        <service
+            android:name=".DeviceDiscoveryService"
+            android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
+            android:exported="true">
+        </service>
+
+        <activity
+            android:name=".DeviceChooserActivity"
+            android:theme="@*android:style/Theme.Dialog.NoFrame"
+            android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE">
+            <!--TODO include url scheme filter similar to PrintSpooler -->
+            <intent-filter>
+                <action android:name="android.companiondevice.START_DISCOVERY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>
diff --git a/packages/CompanionDeviceManager/MODULE_LICENSE_APACHE2 b/packages/CompanionDeviceManager/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/CompanionDeviceManager/MODULE_LICENSE_APACHE2
diff --git a/packages/CompanionDeviceManager/NOTICE b/packages/CompanionDeviceManager/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/packages/CompanionDeviceManager/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/packages/CompanionDeviceManager/res/layout/device_chooser.xml b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
new file mode 100644
index 0000000..ee08582
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_weight="0.1"
+    >
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textColor="@android:color/black"
+        style="@*android:style/TextAppearance.Widget.Toolbar.Title"
+    />
+
+    <ListView
+        android:id="@+id/device_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/title"
+        android:layout_above="@+id/buttons"
+        style="@android:style/Widget.Material.Light.ListView"
+    />
+
+    <LinearLayout
+        android:id="@+id/buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:gravity="end"
+    >
+        <Button
+            android:id="@+id/button_cancel"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Cancel"
+            style="@android:style/Widget.Material.Light.Button.Borderless.Colored"
+        />
+        <Button
+            android:id="@+id/button_pair"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Pair"
+            style="@android:style/Widget.Material.Light.Button.Borderless.Colored"
+        />
+    </LinearLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/dimens.xml b/packages/CompanionDeviceManager/res/values/dimens.xml
new file mode 100644
index 0000000..da7b0d1
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/values/dimens.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!--  Padding applied on most UI elements  -->
+    <dimen name="padding">12dp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
new file mode 100644
index 0000000..c4195b50
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Title of the CompanionDeviceManager application. [CHAR LIMIT=50] -->
+    <string name="app_label">Companion Device Manager</string>
+
+    <!-- Title of the device selection dialog. -->
+    <string name="chooser_title">Pair with &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; via Bluetooth?</string>
+
+</resources>
diff --git a/core/res/res/values-ldrtl/dimens.xml b/packages/CompanionDeviceManager/res/values/themes.xml
similarity index 62%
rename from core/res/res/values-ldrtl/dimens.xml
rename to packages/CompanionDeviceManager/res/values/themes.xml
index 807c042..465f8fc 100644
--- a/core/res/res/values-ldrtl/dimens.xml
+++ b/packages/CompanionDeviceManager/res/values/themes.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
+<!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -15,6 +15,11 @@
 -->
 
 <resources>
-    <item type="dimen" format="integer" name="time_picker_column_start_material">1</item>
-    <item type="dimen" format="integer" name="time_picker_column_end_material">0</item>
+
+    <!--TODO-->
+    <!--<style name="Theme.ChooserActivity" parent="@*android:style/Theme.Dialog.NoFrame">-->
+        <!--&lt;!&ndash;<item name="android:windowBackground">@android:color/light_grey</item>&ndash;&gt;-->
+        <!--<item name="android:backgroundColor">@android:color/light_grey</item>-->
+    <!--</style>-->
+
 </resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
new file mode 100644
index 0000000..c95f940
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.companiondevicemanager;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.companion.CompanionDeviceManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.Html;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class DeviceChooserActivity extends Activity {
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "DeviceChooserActivity";
+
+    private ListView mDeviceListView;
+    private View mPairButton;
+    private View mCancelButton;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (DEBUG) Log.i(LOG_TAG, "Started with intent " + getIntent());
+
+        setContentView(R.layout.device_chooser);
+        setTitle(Html.fromHtml(getString(R.string.chooser_title, getCallingAppName()), 0));
+        getWindow().getDecorView().getRootView().setBackgroundColor(Color.LTGRAY); //TODO theme
+
+        if (getService().mDevicesFound.isEmpty()) {
+            Log.e(LOG_TAG, "About to show UI, but no devices to show");
+        }
+
+        final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter;
+        mDeviceListView = (ListView) findViewById(R.id.device_list);
+        mDeviceListView.setAdapter(adapter);
+        mDeviceListView.addFooterView(getProgressBar(), null, false);
+
+        mPairButton = findViewById(R.id.button_pair);
+        mPairButton.setOnClickListener((view) ->
+                onPairTapped(getService().mSelectedDevice));
+        adapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                updatePairButtonEnabled();
+            }
+        });
+        updatePairButtonEnabled();
+        mCancelButton = findViewById(R.id.button_cancel);
+        mCancelButton.setOnClickListener((view) -> {
+            setResult(RESULT_CANCELED);
+            finish();
+        });
+    }
+
+    private CharSequence getCallingAppName() {
+        try {
+            final PackageManager packageManager = getPackageManager();
+            return packageManager.getApplicationLabel(
+                    packageManager.getApplicationInfo(getService().mCallingPackage, 0));
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        final TextView titleView = (TextView) findViewById(R.id.title);
+        final int padding = getPadding(getResources());
+        titleView.setPadding(padding, padding, padding, padding);
+        titleView.setText(title);
+    }
+
+    //TODO put in resources xmls
+    private ProgressBar getProgressBar() {
+        final ProgressBar progressBar = new ProgressBar(this);
+        progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL);
+        final int padding = getPadding(getResources());
+        progressBar.setPadding(padding, padding, padding, padding);
+        return progressBar;
+    }
+
+    static int getPadding(Resources r) {
+        return r.getDimensionPixelSize(R.dimen.padding);
+        //TODO
+//        final float dp = r.getDisplayMetrics().density;
+//        return (int)(12 * dp);
+    }
+
+    private void updatePairButtonEnabled() {
+        mPairButton.setEnabled(getService().mSelectedDevice != null);
+    }
+
+    private DeviceDiscoveryService getService() {
+        return DeviceDiscoveryService.sInstance;
+    }
+
+    protected void onPairTapped(BluetoothDevice selectedDevice) {
+        setResult(RESULT_OK,
+                new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice));
+        finish();
+    }
+
+    private void toast(String msg) {
+        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+    }
+}
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
new file mode 100644
index 0000000..a3eec0d
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.companiondevicemanager;
+
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName;
+import static android.companion.BluetoothLEDeviceFilter.nullsafe;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.companion.AssociationRequest;
+import android.companion.BluetoothDeviceFilterUtils;
+import android.companion.BluetoothLEDeviceFilter;
+import android.companion.ICompanionDeviceManagerService;
+import android.companion.IOnAssociateCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class DeviceDiscoveryService extends Service {
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "DeviceDiscoveryService";
+
+    static DeviceDiscoveryService sInstance;
+
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothLEDeviceFilter mFilter;
+    private ScanFilter mScanFilter;
+    private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
+    List<BluetoothDevice> mDevicesFound;
+    BluetoothDevice mSelectedDevice;
+    DevicesAdapter mDevicesAdapter;
+    IOnAssociateCallback mCallback;
+    String mCallingPackage;
+
+    private final ICompanionDeviceManagerService mBinder =
+            new ICompanionDeviceManagerService.Stub() {
+        @Override
+        public void startDiscovery(AssociationRequest request,
+                IOnAssociateCallback callback,
+                String callingPackage) throws RemoteException {
+            if (DEBUG) {
+                Log.i(LOG_TAG,
+                        "startDiscovery() called with: filter = [" + request + "], callback = ["
+                                + callback + "]");
+            }
+            mCallback = callback;
+            mCallingPackage = callingPackage;
+            DeviceDiscoveryService.this.startDiscovery(request);
+        }
+    };
+
+    private final ScanCallback mBLEScanCallback = new ScanCallback() {
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            final BluetoothDevice device = result.getDevice();
+            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
+                onDeviceLost(device);
+            } else {
+                onDeviceFound(device);
+            }
+        }
+    };
+
+    private BluetoothLeScanner mBLEScanner;
+
+    private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final BluetoothDevice device = intent.getParcelableExtra(
+                    BluetoothDevice.EXTRA_DEVICE);
+            if (!mFilter.matches(device)) return; // ignore device
+
+            if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
+                onDeviceFound(device);
+            } else {
+                onDeviceLost(device);
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")");
+        return mBinder.asBinder();
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        if (DEBUG) Log.i(LOG_TAG, "onCreate()");
+
+        mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
+        mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
+
+        mDevicesFound = new ArrayList<>();
+        mDevicesAdapter = new DevicesAdapter();
+
+        sInstance = this;
+    }
+
+    private void startDiscovery(AssociationRequest<?> request) {
+        //TODO support other protocols as well
+        mFilter = nullsafe((BluetoothLEDeviceFilter) request.getDeviceFilter());
+        mScanFilter = mFilter.getScanFilter();
+
+        reset();
+
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
+        intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED);
+
+        registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter);
+        mBluetoothAdapter.startDiscovery();
+
+        mBLEScanner.startScan(
+                Collections.singletonList(mScanFilter), mDefaultScanSettings, mBLEScanCallback);
+    }
+
+    private void reset() {
+        mDevicesFound.clear();
+        mSelectedDevice = null;
+        mDevicesAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        stopScan();
+        return super.onUnbind(intent);
+    }
+
+    private void stopScan() {
+        if (DEBUG) Log.i(LOG_TAG, "stopScan() called");
+        mBluetoothAdapter.cancelDiscovery();
+        mBLEScanner.stopScan(mBLEScanCallback);
+        unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
+        stopSelf();
+    }
+
+    private void onDeviceFound(BluetoothDevice device) {
+        if (mDevicesFound.contains(device)) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.i(LOG_TAG, "Considering device " + getDeviceDisplayName(device));
+        }
+
+        if (!mFilter.matches(device)) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.i(LOG_TAG, "Found device " + getDeviceDisplayName(device));
+        }
+        if (mDevicesFound.isEmpty()) {
+            onReadyToShowUI();
+        }
+        mDevicesFound.add(device);
+        mDevicesAdapter.notifyDataSetChanged();
+    }
+
+    //TODO also, on timeout -> call onFailure
+    private void onReadyToShowUI() {
+        try {
+            mCallback.onSuccess(PendingIntent.getActivity(
+                    this, 0,
+                    new Intent(this, DeviceChooserActivity.class),
+                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
+                            | PendingIntent.FLAG_IMMUTABLE));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void onDeviceLost(BluetoothDevice device) {
+        mDevicesFound.remove(device);
+        mDevicesAdapter.notifyDataSetChanged();
+        if (DEBUG) {
+            Log.i(LOG_TAG, "Lost device " + getDeviceDisplayName(device));
+        }
+    }
+
+    class DevicesAdapter extends ArrayAdapter<BluetoothDevice> {
+        private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
+
+        private Drawable icon(int drawableRes) {
+            Drawable icon = getResources().getDrawable(drawableRes, null);
+            icon.setTint(Color.DKGRAY);
+            return icon;
+        }
+
+        public DevicesAdapter() {
+            super(DeviceDiscoveryService.this, 0, mDevicesFound);
+        }
+
+        @Override
+        public View getView(
+                int position,
+                @Nullable View convertView,
+                @NonNull ViewGroup parent) {
+            TextView view = convertView instanceof TextView
+                    ? (TextView) convertView
+                    : newView();
+            bind(view, getItem(position));
+            return view;
+        }
+
+        private void bind(TextView textView, BluetoothDevice device) {
+            textView.setText(getDeviceDisplayName(device));
+            textView.setBackgroundColor(
+                    device.equals(mSelectedDevice)
+                            ? Color.GRAY
+                            : Color.TRANSPARENT);
+            textView.setOnClickListener((view) -> {
+                mSelectedDevice = device;
+                notifyDataSetChanged();
+            });
+        }
+
+        //TODO move to a layout file
+        private TextView newView() {
+            final TextView textView = new TextView(DeviceDiscoveryService.this);
+            textView.setTextColor(Color.BLACK);
+            final int padding = DeviceChooserActivity.getPadding(getResources());
+            textView.setPadding(padding, padding, padding, padding);
+            textView.setCompoundDrawablesWithIntrinsicBounds(
+                    BLUETOOTH_ICON, null, null, null);
+            textView.setCompoundDrawablePadding(padding);
+            return textView;
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 7e3f67b..d6bde81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -36,7 +36,6 @@
     public static final String CATEGORY_SECURITY = "com.android.settings.category.ia.security";
     public static final String CATEGORY_ACCOUNT = "com.android.settings.category.ia.accounts";
     public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system";
-    public static final String CATEGORY_SYSTEM_INPUT = "com.android.settings.category.ia.input";
     public static final String CATEGORY_SYSTEM_LANGUAGE =
             "com.android.settings.category.ia.language";
     public static final String CATEGORY_SYSTEM_DEVELOPMENT =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index b209f4e..40353e7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -56,12 +56,11 @@
         allKeys.add(CategoryKey.CATEGORY_SECURITY);
         allKeys.add(CategoryKey.CATEGORY_ACCOUNT);
         allKeys.add(CategoryKey.CATEGORY_SYSTEM);
-        allKeys.add(CategoryKey.CATEGORY_SYSTEM_INPUT);
         allKeys.add(CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
         allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
         // DO NOT REMOVE ANYTHING ABOVE
 
-        assertThat(allKeys.size()).isEqualTo(14);
+        assertThat(allKeys.size()).isEqualTo(13);
     }
 
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 8bbc8c9..4ddafac 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1399,9 +1399,6 @@
                 Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
                 SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_END_TIME);
         dumpSetting(s, p,
-                Settings.Secure.BRIGHTNESS_USE_TWILIGHT,
-                SecureSettingsProto.BRIGHTNESS_USE_TWILIGHT);
-        dumpSetting(s, p,
                 Settings.Secure.ENABLED_VR_LISTENERS,
                 SecureSettingsProto.ENABLED_VR_LISTENERS);
         dumpSetting(s, p,
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..0615668
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..839e5ed
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..1480c865
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..66e11fb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..d2fe0c3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..5923269
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..d0196e5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..d3a2b39
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..726643c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..31078f6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index 5e85ba0..6bd79a4 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -39,4 +39,13 @@
         android:contentDescription="@string/accessibility_ime_switch_button"
         android:scaleType="centerInside"
         />
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/accessibility_button"
+        android:layout_width="@dimen/navigation_extra_key_width"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="2dp"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_accessibility_button"
+        android:scaleType="centerInside"
+    />
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 92a9da9..c2686f8 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -73,7 +73,7 @@
     <include layout="@layout/status_bar_expanded"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:visibility="gone" />
+        android:visibility="invisible" />
 
     <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 363b3e2..2cc2621 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -207,6 +207,8 @@
     <string name="accessibility_home">Home</string>
     <!-- Content description of the menu button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_menu">Menu</string>
+    <!-- Content description of the accessibility button in the navigation bar (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_accessibility_button">Accessibility</string>
     <!-- Content description of the recents button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_recent">Overview</string>
     <!-- Content description of the search button for accessibility. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index a9ac2d9..27070ed 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -87,6 +87,7 @@
      * above.
      */
     private final Class<?>[] SERVICES_PER_USER = new Class[] {
+            Dependency.class,
             Recents.class,
             PipUI.class
     };
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index aa22618..7c15096 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.doze;
 
+import android.app.AlarmManager;
 import android.app.Application;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -25,10 +26,7 @@
 import android.os.SystemClock;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
-import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIApplication;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.doze.DozeProvider;
 import com.android.systemui.statusbar.phone.DozeParameters;
 
@@ -45,6 +43,7 @@
         Context context = dozeService;
         SensorManager sensorManager = context.getSystemService(SensorManager.class);
         PowerManager powerManager = context.getSystemService(PowerManager.class);
+        AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
 
         DozeHost host = getHost(dozeService);
         AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
@@ -57,7 +56,7 @@
         machine.setParts(new DozeMachine.Part[]{
                 createDozeTriggers(context, sensorManager, host, config, params, handler, wakeLock,
                         machine),
-                createDozeUi(context, host, wakeLock, machine),
+                createDozeUi(context, host, wakeLock, machine, handler, alarmManager),
         });
 
         return machine;
@@ -72,7 +71,7 @@
     }
 
     private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
-            DozeMachine machine) {
+            DozeMachine machine, Handler handler, AlarmManager alarmManager) {
         if (mDozePlugin != null) {
             DozeProvider.DozeUi dozeUi = mDozePlugin.provideDozeUi(context,
                     pluginMachine(context, machine, host),
@@ -82,7 +81,7 @@
                         pluginState(newState));
             };
         } else {
-            return new DozeUi(context, machine, wakeLock, host);
+            return new DozeUi(context, alarmManager, machine, wakeLock, host, handler);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 00d2298..f3fb1ef 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -30,15 +30,12 @@
     void stopDozing();
     void dozeTimeTick();
     boolean isPowerSaveActive();
-    boolean isNotificationLightOn();
     boolean isPulsingBlocked();
 
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
 
     interface Callback {
-        default void onNewNotifications() {}
         default void onNotificationHeadsUp() {}
-        default void onNotificationLight(boolean on) {}
         default void onPowerSaveChanged(boolean active) {}
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 95e49ce..76e0283 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -16,7 +16,13 @@
 
 package com.android.systemui.doze;
 
+import android.app.AlarmManager;
 import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 
 /**
  * The policy controlling doze.
@@ -24,16 +30,25 @@
 public class DozeUi implements DozeMachine.Part {
 
     private final Context mContext;
+    private final AlarmManager mAlarmManager;
     private final DozeHost mHost;
-    private DozeFactory.WakeLock mWakeLock;
-    private DozeMachine mMachine;
+    private final Handler mHandler;
+    private final DozeFactory.WakeLock mWakeLock;
+    private final DozeMachine mMachine;
+    private final AlarmManager.OnAlarmListener mTimeTick;
 
-    public DozeUi(Context context, DozeMachine machine, DozeFactory.WakeLock wakeLock,
-            DozeHost host) {
+    private boolean mTimeTickScheduled = false;
+
+    public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine,
+            DozeFactory.WakeLock wakeLock, DozeHost host, Handler handler) {
         mContext = context;
+        mAlarmManager = alarmManager;
         mMachine = machine;
         mWakeLock = wakeLock;
         mHost = host;
+        mHandler = handler;
+
+        mTimeTick = this::onTimeTick;
     }
 
     private void pulseWhileDozing(int reason) {
@@ -54,6 +69,12 @@
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
+            case DOZE_AOD:
+                scheduleTimeTick();
+                break;
+            case DOZE:
+                unscheduleTimeTick();
+                break;
             case DOZE_REQUEST_PULSE:
                 pulseWhileDozing(DozeLog.PULSE_REASON_NOTIFICATION /* TODO */);
                 break;
@@ -62,7 +83,52 @@
                 break;
             case FINISH:
                 mHost.stopDozing();
+                unscheduleTimeTick();
                 break;
         }
     }
+
+    private void scheduleTimeTick() {
+        if (mTimeTickScheduled) {
+            return;
+        }
+
+        long delta = roundToNextMinute(System.currentTimeMillis()) - System.currentTimeMillis();
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + delta, "doze_time_tick", mTimeTick, mHandler);
+
+        mTimeTickScheduled = true;
+    }
+
+    private void unscheduleTimeTick() {
+        if (!mTimeTickScheduled) {
+            return;
+        }
+        mAlarmManager.cancel(mTimeTick);
+    }
+
+    private long roundToNextMinute(long timeInMillis) {
+        Calendar calendar = GregorianCalendar.getInstance();
+        calendar.setTimeInMillis(timeInMillis);
+        calendar.set(Calendar.MILLISECOND, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.add(Calendar.MINUTE, 1);
+
+        return calendar.getTimeInMillis();
+    }
+
+    private void onTimeTick() {
+        if (!mTimeTickScheduled) {
+            // Alarm was canceled, but we still got the callback. Ignore.
+            return;
+        }
+
+        mHost.dozeTimeTick();
+
+        // Keep wakelock until a frame has been pushed.
+        mHandler.post(mWakeLock.wrap(() -> {}));
+
+        mTimeTickScheduled = false;
+        scheduleTimeTick();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 9157e33..e635162 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -205,7 +205,6 @@
         sTaskLoader = new RecentsTaskLoader(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
         mHandler = new Handler();
-        getComponent(CommandQueue.class).addCallbacks(this);
         UiModeManager uiModeManager = (UiModeManager) mContext.
                 getSystemService(Context.UI_MODE_SERVICE);
         if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
@@ -234,6 +233,7 @@
         if (sSystemServicesProxy.isSystemUser(processUser)) {
             // For the system user, initialize an instance of the interface that we can pass to the
             // secondary user
+            getComponent(CommandQueue.class).addCallbacks(this);
             mSystemToUserCallbacks = new RecentsSystemUser(mContext, mImpl);
         } else {
             // For the secondary user, bind to the primary user's service to get a persistent
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 609e3fb..8091199 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -76,6 +76,7 @@
 import com.android.systemui.recents.views.TaskStackViewScroller;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.stackdivider.DividerView;
 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -637,10 +638,21 @@
             stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
                     TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
             mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
+            // Get the width of a task view so that we know how wide to draw the header bar.
+            int taskViewWidth = 0;
+            if (mDummyStackView.useGridLayout()) {
+                TaskGridLayoutAlgorithm gridLayout = mDummyStackView.getGridAlgorithm();
+                gridLayout.initialize(windowRect);
+                taskViewWidth = (int) gridLayout.getTransform(0 /* taskIndex */,
+                        stack.getTaskCount(), new TaskViewTransform(), stackLayout).rect.width();
+            } else {
+                Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
+                if (!taskViewBounds.isEmpty()) {
+                    taskViewWidth = taskViewBounds.width();
+                }
+            }
 
-            Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
-            if (!taskViewBounds.isEmpty()) {
-                int taskViewWidth = taskViewBounds.width();
+            if (taskViewWidth > 0) {
                 synchronized (mHeaderBarLock) {
                     if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
                             mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index fb94c7e..0160eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -96,6 +96,7 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import com.android.systemui.recents.views.grid.GridTaskView;
+import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -447,6 +448,11 @@
         return mLayoutAlgorithm;
     }
 
+    /** Returns the grid algorithm for this task stack. */
+    public TaskGridLayoutAlgorithm getGridAlgorithm() {
+        return mLayoutAlgorithm.mTaskGridLayoutAlgorithm;
+    }
+
     /**
      * Returns the touch handler for this task stack.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index b9a0f74..0b09acc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -72,6 +72,10 @@
         return mMinimized;
     }
 
+    public boolean isHomeStackResizable() {
+        return mHomeStackResizable;
+    }
+
     private void addDivider(Configuration configuration) {
         mView = (DividerView)
                 LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
@@ -119,6 +123,7 @@
         mView.post(new Runnable() {
             @Override
             public void run() {
+                mHomeStackResizable = isHomeStackResizable;
                 if (mMinimized != minimized) {
                     mMinimized = minimized;
                     updateTouchable();
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 49035ba..6f59fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1241,7 +1241,8 @@
 
     public final void onBusEvent(UndockingTaskEvent undockingTaskEvent) {
         int dockSide = mWindowManagerProxy.getDockSide();
-        if (dockSide != WindowManager.DOCKED_INVALID && !mDockedStackMinimized) {
+        if (dockSide != WindowManager.DOCKED_INVALID && (mHomeStackResizable
+                || !mDockedStackMinimized)) {
             startDragging(false /* animate */, false /* touching */);
             SnapTarget target = dockSideTopLeft(dockSide)
                     ? mSnapAlgorithm.getDismissEndTarget()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 477701c..9245df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -63,9 +63,6 @@
     private static final int MSG_SET_WINDOW_STATE              = 12 << MSG_SHIFT;
     private static final int MSG_SHOW_RECENT_APPS              = 13 << MSG_SHIFT;
     private static final int MSG_HIDE_RECENT_APPS              = 14 << MSG_SHIFT;
-    private static final int MSG_BUZZ_BEEP_BLINKED             = 15 << MSG_SHIFT;
-    private static final int MSG_NOTIFICATION_LIGHT_OFF        = 16 << MSG_SHIFT;
-    private static final int MSG_NOTIFICATION_LIGHT_PULSE      = 17 << MSG_SHIFT;
     private static final int MSG_SHOW_SCREEN_PIN_REQUEST       = 18 << MSG_SHIFT;
     private static final int MSG_APP_TRANSITION_PENDING        = 19 << MSG_SHIFT;
     private static final int MSG_APP_TRANSITION_CANCELLED      = 20 << MSG_SHIFT;
@@ -121,9 +118,6 @@
         default void toggleKeyboardShortcutsMenu(int deviceId) { }
         default void cancelPreloadRecentApps() { }
         default void setWindowState(int window, int state) { }
-        default void buzzBeepBlinked() { }
-        default void notificationLightOff() { }
-        default void notificationLightPulse(int argb, int onMillis, int offMillis) { }
         default void showScreenPinningRequest(int taskId) { }
         default void appTransitionPending() { }
         default void appTransitionCancelled() { }
@@ -313,26 +307,6 @@
         }
     }
 
-    public void buzzBeepBlinked() {
-        synchronized (mLock) {
-            mHandler.removeMessages(MSG_BUZZ_BEEP_BLINKED);
-            mHandler.sendEmptyMessage(MSG_BUZZ_BEEP_BLINKED);
-        }
-    }
-
-    public void notificationLightOff() {
-        synchronized (mLock) {
-            mHandler.sendEmptyMessage(MSG_NOTIFICATION_LIGHT_OFF);
-        }
-    }
-
-    public void notificationLightPulse(int argb, int onMillis, int offMillis) {
-        synchronized (mLock) {
-            mHandler.obtainMessage(MSG_NOTIFICATION_LIGHT_PULSE, onMillis, offMillis, argb)
-                    .sendToTarget();
-        }
-    }
-
     public void showScreenPinningRequest(int taskId) {
         synchronized (mLock) {
             mHandler.obtainMessage(MSG_SHOW_SCREEN_PIN_REQUEST, taskId, 0, null)
@@ -524,21 +498,6 @@
                         mCallbacks.get(i).setWindowState(msg.arg1, msg.arg2);
                     }
                     break;
-                case MSG_BUZZ_BEEP_BLINKED:
-                    for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).buzzBeepBlinked();
-                    }
-                    break;
-                case MSG_NOTIFICATION_LIGHT_OFF:
-                    for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).notificationLightOff();
-                    }
-                    break;
-                case MSG_NOTIFICATION_LIGHT_PULSE:
-                    for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).notificationLightPulse((Integer) msg.obj, msg.arg1, msg.arg2);
-                    }
-                    break;
                 case MSG_SHOW_SCREEN_PIN_REQUEST:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).showScreenPinningRequest(msg.arg1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index e8e9d4e..2425076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -68,6 +68,7 @@
     private float mMaxShelfEnd;
     private int mRelativeOffset;
     private boolean mInteractive;
+    private boolean mAnimationsEnabled = true;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -388,12 +389,13 @@
 
         View rowIcon = row.getNotificationIcon();
         float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
-        if (usingLinearInterpolation) {
+        boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
+        if (usingLinearInterpolation && !stayingInShelf) {
             // If we interpolate from the notification position, this might lead to a slightly
             // odd interpolation, since the notification position changes as well. Let's interpolate
             // from a fixed distance. We can only do this if we don't animate and the icon is
             // always in the interpolated positon.
-            notificationIconPosition = mMaxShelfEnd - getIntrinsicHeight() - iconTransformDistance;
+            notificationIconPosition = getTranslationY() - iconTransformDistance;
         }
         float notificationIconSize = 0.0f;
         int iconTopPadding;
@@ -426,7 +428,7 @@
             iconState.hidden = transitionAmount == 0.0f;
             iconState.alpha = alpha;
             iconState.yTranslation = iconYTranslation;
-            if (row.isInShelf() && !row.isTransformingIntoShelf()) {
+            if (stayingInShelf) {
                 iconState.iconAppearAmount = 1.0f;
                 iconState.alpha = 1.0f;
                 iconState.scaleX = 1.0f;
@@ -573,6 +575,15 @@
         mMaxShelfEnd = maxShelfEnd;
     }
 
+    public void setAnimationsEnabled(boolean enabled) {
+        mAnimationsEnabled = enabled;
+        mCollapsedIcons.setAnimationsEnabled(enabled);
+        if (!enabled) {
+            // we need to wait with enabling the animations until the first frame has passed
+            mShelfIcons.setAnimationsEnabled(false);
+        }
+    }
+
     private class ShelfState extends ExpandableViewState {
         private float openedAmount;
         private boolean hasItemsInStableShelf;
@@ -585,6 +596,7 @@
             setOpenedAmount(openedAmount);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
+            mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
         }
 
         @Override
@@ -594,6 +606,7 @@
             setOpenedAmount(openedAmount);
             updateAppearance();
             setHasItemsInStableShelf(hasItemsInStableShelf);
+            mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index 03c7325..e5f32df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -105,6 +105,11 @@
     }
 
     @Override
+    protected boolean shouldClearBackgroundOnReapply() {
+        return false;
+    }
+
+    @Override
     public int getCustomBackgroundColor() {
         int customBackgroundColor = super.getCustomBackgroundColor();
         if (customBackgroundColor == 0 && mShowingLegacyBackground) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 836482a..5f5e1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -93,7 +93,9 @@
     public void notifyContentUpdated(StatusBarNotification notification, boolean isLowPriority) {
         mDarkInitialized = false;
         Drawable background = mView.getBackground();
-        mBackgroundColor = 0;
+        if (shouldClearBackgroundOnReapply()) {
+            mBackgroundColor = 0;
+        }
         if (background instanceof ColorDrawable) {
             mBackgroundColor = ((ColorDrawable) background).getColor();
             mView.setBackground(null);
@@ -101,6 +103,10 @@
         mShouldInvertDark = mBackgroundColor == 0 || isColorLight(mBackgroundColor);
     }
 
+    protected boolean shouldClearBackgroundOnReapply() {
+        return true;
+    }
+
     private boolean isColorLight(int backgroundColor) {
         return Color.alpha(backgroundColor) == 0
                 || ColorUtils.calculateLuminance(backgroundColor) > 0.5;
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 62b536e..808cd21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
@@ -79,6 +80,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -101,7 +103,7 @@
 
     private int mNavigationIconHints = 0;
     private int mNavigationBarMode;
-    protected AccessibilityManager mAccessibilityManager;
+    private AccessibilityManager mAccessibilityManager;
 
     private int mDisabledFlags1;
     private StatusBar mStatusBar;
@@ -132,6 +134,8 @@
         mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
         mWindowManager = getContext().getSystemService(WindowManager.class);
         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
+        mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+                this::updateAccessibilityServicesState);
         if (savedInstanceState != null) {
             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
         }
@@ -149,6 +153,8 @@
     public void onDestroy() {
         super.onDestroy();
         mCommandQueue.removeCallbacks(this);
+        mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
+                this::updateAccessibilityServicesState);
         try {
             WindowManagerGlobal.getWindowManagerService()
                     .removeRotationWatcher(mRotationWatcher);
@@ -402,6 +408,10 @@
         ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
         homeButton.setOnTouchListener(this::onHomeTouch);
         homeButton.setOnLongClickListener(this::onHomeLongClick);
+
+        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
+        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
+        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
     }
 
     private boolean onHomeTouch(View v, MotionEvent event) {
@@ -553,6 +563,34 @@
                 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
     }
 
+    private void onAccessibilityClick(View v) {
+        mAccessibilityManager.notifyAccessibilityButtonClicked();
+    }
+
+    private boolean onAccessibilityLongClick(View v) {
+        // TODO(b/34720082): Target service selection via long click
+        android.widget.Toast.makeText(getContext(), "Service selection coming soon...",
+                android.widget.Toast.LENGTH_LONG).show();
+        return true;
+    }
+
+    private void updateAccessibilityServicesState() {
+        final List<AccessibilityServiceInfo> services =
+                mAccessibilityManager.getEnabledAccessibilityServiceList(
+                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        int requestingServices = 0;
+        for (int i = services.size() - 1; i >= 0; --i) {
+            AccessibilityServiceInfo info = services.get(i);
+            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+                requestingServices++;
+            }
+        }
+
+        final boolean showAccessibilityButton = requestingServices >= 1;
+        final boolean targetSelection = requestingServices >= 2;
+        mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
+    }
+
     // ----- Methods that StatusBar talks to (should be minimized) -----
 
     public void setLightBarController(LightBarController lightBarController) {
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 dced747..5d13289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -78,6 +78,8 @@
     private int mCurrentRotation = -1;
 
     boolean mShowMenu;
+    boolean mShowAccessibilityButton;
+    boolean mLongClickableAccessibilityButton;
     int mDisabledFlags = 0;
     int mNavigationIconHints = 0;
 
@@ -89,6 +91,7 @@
     private KeyButtonDrawable mDockedIcon;
     private KeyButtonDrawable mImeIcon;
     private KeyButtonDrawable mMenuIcon;
+    private KeyButtonDrawable mAccessibilityIcon;
 
     private GestureHelper mGestureHelper;
     private DeadZone mDeadZone;
@@ -202,6 +205,9 @@
         mVertical = false;
         mShowMenu = false;
 
+        mShowAccessibilityButton = false;
+        mLongClickableAccessibilityButton = false;
+
         mConfiguration = new Configuration();
         mConfiguration.updateFrom(context.getResources().getConfiguration());
         updateIcons(context, Configuration.EMPTY, mConfiguration);
@@ -213,6 +219,8 @@
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
         mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
+        mButtonDispatchers.put(R.id.accessibility_button,
+                new ButtonDispatcher(R.id.accessibility_button));
     }
 
     public BarTransitions getBarTransitions() {
@@ -287,6 +295,10 @@
         return mButtonDispatchers.get(R.id.ime_switcher);
     }
 
+    public ButtonDispatcher getAccessibilityButton() {
+        return mButtonDispatchers.get(R.id.accessibility_button);
+    }
+
     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
         return mButtonDispatchers;
     }
@@ -320,6 +332,8 @@
             mRecentIcon = getDrawable(ctx,
                     R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
             mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
+            mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button,
+                    R.drawable.ic_sysbar_accessibility_button_dark);
 
             Context darkContext = new ContextThemeWrapper(ctx, R.style.DualToneDarkTheme);
             Context lightContext = new ContextThemeWrapper(ctx, R.style.DualToneLightTheme);
@@ -411,6 +425,9 @@
         setMenuVisibility(mShowMenu, true);
         getMenuButton().setImageDrawable(mMenuIcon);
 
+        setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
+        getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
+
         setDisabledFlags(mDisabledFlags, true);
 
         mBarTransitions.reapplyDarkIntensity();
@@ -517,13 +534,25 @@
 
         mShowMenu = show;
 
-        // Only show Menu if IME switcher not shown.
-        final boolean shouldShow = mShowMenu &&
+        // Only show Menu if IME switcher and Accessibility button not shown.
+        final boolean shouldShow = mShowMenu && !mShowAccessibilityButton &&
                 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
 
         getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
     }
 
+    public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
+        mShowAccessibilityButton = visible;
+        mLongClickableAccessibilityButton = longClickable;
+        if (visible) {
+            // Accessibility button overrides Menu button.
+            setMenuVisibility(false, true);
+        }
+
+        getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        getAccessibilityButton().setLongClickable(longClickable);
+    }
+
     @Override
     public void onFinishInflate() {
         mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
@@ -814,6 +843,7 @@
         dumpButton(pw, "home", getHomeButton());
         dumpButton(pw, "rcnt", getRecentsButton());
         dumpButton(pw, "menu", getMenuButton());
+        dumpButton(pw, "a11y", getAccessibilityButton());
 
         pw.println("    }");
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c25a45c..571ae26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -104,6 +104,7 @@
     private float mOpenedAmount = 0.0f;
     private float mVisualOverflowAdaption;
     private boolean mDisallowNextAnimation;
+    private boolean mAnimationsEnabled = true;
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -255,7 +256,7 @@
             iconState.visibleState = StatusBarIconView.STATE_ICON;
             if (firstOverflowIndex == -1 && (isAmbient
                     || (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart)))) {
-                firstOverflowIndex = noOverflowAfter ? i - 1 : i;
+                firstOverflowIndex = noOverflowAfter && !isAmbient ? i - 1 : i;
                 int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
                 visualOverflowStart = overflowStart + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)
                         - totalDotLength / 2
@@ -422,6 +423,20 @@
         return mIconSize;
     }
 
+    public void setAnimationsEnabled(boolean enabled) {
+        if (!enabled && mAnimationsEnabled) {
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                ViewState childState = mIconStates.get(child);
+                if (childState != null) {
+                    childState.cancelAnimations(child);
+                    childState.applyToView(child);
+                }
+            }
+        }
+        mAnimationsEnabled = enabled;
+    }
+
     public class IconState extends ViewState {
         public float iconAppearAmount = 1.0f;
         public float clampedAppearAmount = 1.0f;
@@ -438,52 +453,59 @@
                 StatusBarIconView icon = (StatusBarIconView) view;
                 boolean animate = false;
                 AnimationProperties animationProperties = null;
-                if (justAdded) {
-                    super.applyToView(icon);
-                    icon.setAlpha(0.0f);
-                    icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */);
-                    animationProperties = ADD_ICON_PROPERTIES;
-                    animate = true;
-                } else if (visibleState != icon.getVisibleState()) {
-                    animationProperties = DOT_ANIMATION_PROPERTIES;
-                    animate = true;
-                }
-                if (!animate && mAddAnimationStartIndex >= 0
-                        && indexOfChild(view) >= mAddAnimationStartIndex
-                        && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
-                            || visibleState != StatusBarIconView.STATE_HIDDEN)) {
-                    animationProperties = DOT_ANIMATION_PROPERTIES;
-                    animate = true;
-                }
-                if (needsCannedAnimation) {
-                    AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
-                    animationFilter.reset();
-                    animationFilter.combineFilter(ICON_ANIMATION_PROPERTIES.getAnimationFilter());
-                    mTempProperties.resetCustomInterpolators();
-                    mTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES);
-                    if (animationProperties != null) {
-                        animationFilter.combineFilter(animationProperties.getAnimationFilter());
-                        mTempProperties.combineCustomInterpolators(animationProperties);
+                boolean animationsAllowed = mAnimationsEnabled && !mDisallowNextAnimation;
+                if (animationsAllowed) {
+                    if (justAdded) {
+                        super.applyToView(icon);
+                        if (iconAppearAmount != 0.0f) {
+                            icon.setAlpha(0.0f);
+                            icon.setVisibleState(StatusBarIconView.STATE_HIDDEN,
+                                    false /* animate */);
+                            animationProperties = ADD_ICON_PROPERTIES;
+                            animate = true;
+                        }
+                    } else if (visibleState != icon.getVisibleState()) {
+                        animationProperties = DOT_ANIMATION_PROPERTIES;
+                        animate = true;
                     }
-                    animationProperties = mTempProperties;
-                    animationProperties.setDuration(CANNED_ANIMATION_DURATION);
-                    animate = true;
-                    mCannedAnimationStartIndex = indexOfChild(view);
+                    if (!animate && mAddAnimationStartIndex >= 0
+                            && indexOfChild(view) >= mAddAnimationStartIndex
+                            && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+                            || visibleState != StatusBarIconView.STATE_HIDDEN)) {
+                        animationProperties = DOT_ANIMATION_PROPERTIES;
+                        animate = true;
+                    }
+                    if (needsCannedAnimation) {
+                        AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+                        animationFilter.reset();
+                        animationFilter.combineFilter(
+                                ICON_ANIMATION_PROPERTIES.getAnimationFilter());
+                        mTempProperties.resetCustomInterpolators();
+                        mTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES);
+                        if (animationProperties != null) {
+                            animationFilter.combineFilter(animationProperties.getAnimationFilter());
+                            mTempProperties.combineCustomInterpolators(animationProperties);
+                        }
+                        animationProperties = mTempProperties;
+                        animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+                        animate = true;
+                        mCannedAnimationStartIndex = indexOfChild(view);
+                    }
+                    if (!animate && mCannedAnimationStartIndex >= 0
+                            && indexOfChild(view) > mCannedAnimationStartIndex
+                            && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+                            || visibleState != StatusBarIconView.STATE_HIDDEN)) {
+                        AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+                        animationFilter.reset();
+                        animationFilter.animateX();
+                        mTempProperties.resetCustomInterpolators();
+                        animationProperties = mTempProperties;
+                        animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+                        animate = true;
+                    }
                 }
-                if (!animate && mCannedAnimationStartIndex >= 0
-                        && indexOfChild(view) > mCannedAnimationStartIndex
-                        && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
-                        || visibleState != StatusBarIconView.STATE_HIDDEN)) {
-                    AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
-                    animationFilter.reset();
-                    animationFilter.animateX();
-                    mTempProperties.resetCustomInterpolators();
-                    animationProperties = mTempProperties;
-                    animationProperties.setDuration(CANNED_ANIMATION_DURATION);
-                    animate = true;
-                }
-                icon.setVisibleState(visibleState);
-                if (animate && !mDisallowNextAnimation) {
+                icon.setVisibleState(visibleState, animationsAllowed);
+                if (animate) {
                     animateTo(icon, animationProperties);
                 } else {
                     super.applyToView(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 638c12f..54c67d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -603,8 +603,6 @@
             new ArraySet<>();
     private long mLastVisibilityReportUptimeMs;
 
-    private final ShadeUpdates mShadeUpdates = new ShadeUpdates();
-
     private Runnable mLaunchTransitionEndRunnable;
     protected boolean mLaunchTransitionFadingAway;
     private ExpandableNotificationRow mDraggedDownRow;
@@ -1501,7 +1499,7 @@
                     ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
         } else {
             Divider divider = getComponent(Divider.class);
-            if (divider != null && divider.isMinimized()) {
+            if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
                 // Undocking from the minimized state is not supported
                 return false;
             } else {
@@ -1894,7 +1892,6 @@
         updateEmptyShadeView();
 
         updateQsExpansionEnabled();
-        mShadeUpdates.check();
 
         // Let's also update the icons
         mIconController.updateNotificationIcons(mNotificationData);
@@ -3071,24 +3068,6 @@
     }
 
     @Override // CommandQueue
-    public void buzzBeepBlinked() {
-    }
-
-    @Override
-    public void notificationLightOff() {
-        if (mDozeServiceHost != null) {
-            mDozeServiceHost.fireNotificationLight(false);
-        }
-    }
-
-    @Override
-    public void notificationLightPulse(int argb, int onMillis, int offMillis) {
-        if (mDozeServiceHost != null) {
-            mDozeServiceHost.fireNotificationLight(true);
-        }
-    }
-
-    @Override // CommandQueue
     public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
             int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
         final int oldVal = mSystemUiVisibility;
@@ -5059,44 +5038,9 @@
         return mStatusBarKeyguardViewManager.isShowing();
     }
 
-    private final class ShadeUpdates {
-        private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
-        private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
-
-        public void check() {
-            mNewVisibleNotifications.clear();
-            ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-            for (int i = 0; i < activeNotifications.size(); i++) {
-                final Entry entry = activeNotifications.get(i);
-                final boolean visible = entry.row != null
-                        && entry.row.getVisibility() == View.VISIBLE;
-                if (visible) {
-                    mNewVisibleNotifications.add(entry.key + entry.notification.getPostTime());
-                }
-            }
-            final boolean updates = !mVisibleNotifications.containsAll(mNewVisibleNotifications);
-            mVisibleNotifications.clear();
-            mVisibleNotifications.addAll(mNewVisibleNotifications);
-
-            // We have new notifications
-            if (updates && mDozeServiceHost != null) {
-                mDozeServiceHost.fireNewNotifications();
-            }
-        }
-    }
-
     private final class DozeServiceHost implements DozeHost {
-        // Amount of time to allow to update the time shown on the screen before releasing
-        // the wakelock.  This timeout is design to compensate for the fact that we don't
-        // currently have a way to know when time display contents have actually been
-        // refreshed once we've finished rendering a new frame.
-        private static final long PROCESSING_TIME = 500;
-
         private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
 
-        // Keeps the last reported state by fireNotificationLight.
-        private boolean mNotificationLightOn;
-
         @Override
         public String toString() {
             return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
@@ -5114,19 +5058,6 @@
             }
         }
 
-        public void fireNotificationLight(boolean on) {
-            mNotificationLightOn = on;
-            for (Callback callback : mCallbacks) {
-                callback.onNotificationLight(on);
-            }
-        }
-
-        public void fireNewNotifications() {
-            for (Callback callback : mCallbacks) {
-                callback.onNewNotifications();
-            }
-        }
-
         @Override
         public void addCallback(@NonNull Callback callback) {
             mCallbacks.add(callback);
@@ -5192,11 +5123,6 @@
         }
 
         @Override
-        public boolean isNotificationLightOn() {
-            return mNotificationLightOn;
-        }
-
-        @Override
         public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
             StatusBar.this.startPendingIntentDismissingKeyguard(intent);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 6006d5a..11927729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -2766,6 +2766,7 @@
 
     private void updateNotificationAnimationStates() {
         boolean running = mAnimationsEnabled || mPulsing;
+        mShelf.setAnimationsEnabled(running);
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index ab562d1..5d11ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -42,104 +42,11 @@
     private IStatusBarService mBarService;
 
     @Override
-    public void setIcon(String slot, StatusBarIcon icon) {
-    }
-
-    @Override
-    public void removeIcon(String slot) {
-    }
-
-    public void removeNotification(String key, RankingMap ranking) {
-    }
-
-    @Override
-    public void disable(int state1, int state2, boolean animate) {
-    }
-
-    @Override
-    public void animateExpandNotificationsPanel() {
-    }
-
-    @Override
-    public void animateCollapsePanels(int flags) {
-    }
-
-    @Override
-    public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
-            int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
-    }
-
-    @Override
-    public void topAppWindowChanged(boolean visible) {
-    }
-
-    @Override
-    public void setImeWindowStatus(IBinder token, int vis, int backDisposition,
-            boolean showImeSwitcher) {
-    }
-
-    @Override // CommandQueue
-    public void setWindowState(int window, int state) {
-    }
-
-    @Override // CommandQueue
-    public void buzzBeepBlinked() {
-    }
-
-    @Override // CommandQueue
-    public void notificationLightOff() {
-    }
-
-    @Override // CommandQueue
-    public void notificationLightPulse(int argb, int onMillis, int offMillis) {
-    }
-
-    @Override
-    public void animateExpandSettingsPanel(String subPanel) {
-    }
-
-    @Override
-    public void showScreenPinningRequest(int taskId) {
-    }
-
-    @Override
-    public void appTransitionPending() {
-    }
-
-    @Override
-    public void appTransitionCancelled() {
-    }
-
-    @Override
-    public void appTransitionStarting(long startTime, long duration) {
-    }
-
-    @Override
-    public void appTransitionFinished() {
-    }
-
-    @Override
-    public void onCameraLaunchGestureDetected(int source) {
-    }
-
-    @Override
     public void showTvPictureInPictureMenu() {
         PipManager.getInstance().showTvPictureInPictureMenu();
     }
 
     @Override
-    public void addQsTile(ComponentName tile) {
-    }
-
-    @Override
-    public void remQsTile(ComponentName tile) {
-    }
-
-    @Override
-    public void clickTile(ComponentName tile) {
-    }
-
-    @Override
     public void start() {
         putComponent(TvStatusBar.class, this);
         CommandQueue commandQueue = getComponent(CommandQueue.class);
@@ -160,8 +67,4 @@
         }
     }
 
-    @Override
-    public void handleSystemNavigationKey(int arg1) {
-        // Not implemented
-    }
 }
diff --git a/packages/SystemUI/tests/res/layout/custom_view_dark.xml b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
new file mode 100644
index 0000000..9e460a5
--- /dev/null
+++ b/packages/SystemUI/tests/res/layout/custom_view_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ff000000"
+    />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 43f8629..5b22986 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -171,27 +171,6 @@
     }
 
     @Test
-    public void testBuzzBeepBlink() {
-        mCommandQueue.buzzBeepBlinked();
-        waitForIdleSync();
-        verify(mCallbacks).buzzBeepBlinked();
-    }
-
-    @Test
-    public void testNotificationLightOff() {
-        mCommandQueue.notificationLightOff();
-        waitForIdleSync();
-        verify(mCallbacks).notificationLightOff();
-    }
-
-    @Test
-    public void testNotificationLightPulse() {
-        mCommandQueue.notificationLightPulse(1, 2, 3);
-        waitForIdleSync();
-        verify(mCallbacks).notificationLightPulse(eq(1), eq(2), eq(3));
-    }
-
-    @Test
     public void testScreenPinRequest() {
         mCommandQueue.showScreenPinningRequest(1);
         waitForIdleSync();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
new file mode 100644
index 0000000..d07cea1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationCustomViewWrapperTest {
+
+    private Context mContext;
+    private ExpandableNotificationRow mRow;
+
+    @Before
+    @UiThreadTest
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mRow = new ExpandableNotificationRow(mContext, null);
+    }
+
+    @Test
+    public void testBackgroundPersists() {
+        RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.custom_view_dark);
+        View v = views.apply(mContext, null);
+        NotificationViewWrapper wrap = NotificationCustomViewWrapper.wrap(mContext, v, mRow);
+        wrap.notifyContentUpdated(null, false /* isLowPriority */);
+        Assert.assertTrue(wrap.getCustomBackgroundColor() != 0);
+        views.reapply(mContext, v);
+        wrap.notifyContentUpdated(null, false /* isLowPriority */);
+        Assert.assertTrue(wrap.getCustomBackgroundColor() != 0);
+    }
+
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a861522..3d9ef02 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -70,6 +70,8 @@
 import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.hardware.fingerprint.IFingerprintService;
+import android.provider.SettingsStringUtil.ComponentNameSet;
+import android.provider.SettingsStringUtil.SettingStringHelper;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
 import android.util.Slog;
@@ -98,9 +100,9 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
-
 import com.android.server.policy.AccessibilityShortcutController;
 import com.android.server.statusbar.StatusBarManagerInternal;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.FileDescriptor;
@@ -445,7 +447,7 @@
                 }
                 return userState.getClientState();
             } else {
-                userState.mClients.register(client);
+                userState.mUserClients.register(client);
                 // If this client is not for the current user we do not
                 // return a state since it is not for the foreground user.
                 // We will send the state to the client on a user switch.
@@ -813,6 +815,42 @@
         }
     }
 
+    /**
+     * Invoked remotely over AIDL by SysUi when the accessibility button within the system's
+     * navigation area has been clicked.
+     */
+    @Override
+    public void notifyAccessibilityButtonClicked() {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + android.Manifest.permission.STATUS_BAR);
+        }
+        synchronized (mLock) {
+            notifyAccessibilityButtonClickedLocked();
+        }
+    }
+
+    /**
+     * Invoked remotely over AIDL by SysUi when the availability of the accessibility
+     * button within the system's navigation area has changed.
+     *
+     * @param available {@code true} if the accessibility button is available to the
+     *                  user, {@code false} otherwise
+     */
+    @Override
+    public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + android.Manifest.permission.STATUS_BAR);
+        }
+        synchronized (mLock) {
+            notifyAccessibilityButtonAvailabilityChangedLocked(available);
+        }
+    }
+
+
     boolean onGesture(int gestureId) {
         synchronized (mLock) {
             boolean handled = notifyGestureLocked(gestureId, false);
@@ -927,7 +965,7 @@
             oldUserState.onSwitchToAnotherUser();
 
             // Disable the local managers for the old user.
-            if (oldUserState.mClients.getRegisteredCallbackCount() > 0) {
+            if (oldUserState.mUserClients.getRegisteredCallbackCount() > 0) {
                 mMainHandler.obtainMessage(MainHandler.MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER,
                         oldUserState.mUserId, 0).sendToTarget();
             }
@@ -1044,6 +1082,28 @@
         }
     }
 
+    private void notifyAccessibilityButtonClickedLocked() {
+        final UserState state = getCurrentUserStateLocked();
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            final Service service = state.mBoundServices.get(i);
+            // TODO(b/34720082): Only notify a single user-defined service
+            if (service.mRequestAccessibilityButton) {
+                service.notifyAccessibilityButtonClickedLocked();
+            }
+        }
+    }
+
+    private void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+        final UserState state = getCurrentUserStateLocked();
+        state.mIsAccessibilityButtonAvailable = available;
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            final Service service = state.mBoundServices.get(i);
+            if (service.mRequestAccessibilityButton) {
+                service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+            }
+        }
+    }
+
     /**
      * Removes an AccessibilityInteractionConnection.
      *
@@ -1178,7 +1238,7 @@
                 service.onAdded();
                 userState.mBoundServices.add(service);
                 userState.mComponentNameToServiceMap.put(service.mComponentName, service);
-                scheduleNotifyClientsOfServicesStateChange();
+                scheduleNotifyClientsOfServicesStateChange(userState);
             }
         } catch (RemoteException re) {
             /* do nothing */
@@ -1200,7 +1260,7 @@
             Service boundService = userState.mBoundServices.get(i);
             userState.mComponentNameToServiceMap.put(boundService.mComponentName, boundService);
         }
-        scheduleNotifyClientsOfServicesStateChange();
+        scheduleNotifyClientsOfServicesStateChange(userState);
     }
 
     /**
@@ -1362,16 +1422,16 @@
         final int clientState = userState.getClientState();
         if (userState.mLastSentClientState != clientState
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
-                        || userState.mClients.getRegisteredCallbackCount() > 0)) {
+                        || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
             userState.mLastSentClientState = clientState;
             mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS,
                     clientState, userState.mUserId).sendToTarget();
         }
     }
 
-    private void scheduleNotifyClientsOfServicesStateChange() {
-        mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS)
-                .sendToTarget();
+    private void scheduleNotifyClientsOfServicesStateChange(UserState userState) {
+        mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS,
+                userState.mUserId).sendToTarget();
     }
 
     private void scheduleUpdateInputFilter(UserState userState) {
@@ -2013,10 +2073,12 @@
      * Enables accessibility service specified by {@param componentName} for the {@param userId}.
      */
     private void enableAccessibilityServiceLocked(ComponentName componentName, int userId) {
-        SettingsStringHelper settingsHelper = new SettingsStringHelper(
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
-        settingsHelper.addService(componentName);
-        settingsHelper.writeToSettings();
+        final SettingStringHelper setting =
+                new SettingStringHelper(
+                        mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                        userId);
+        setting.write(ComponentNameSet.add(setting.read(), componentName));
 
         UserState userState = getUserStateLocked(userId);
         if (userState.mEnabledServices.add(componentName)) {
@@ -2028,10 +2090,12 @@
      * Disables accessibility service specified by {@param componentName} for the {@param userId}.
      */
     private void disableAccessibilityServiceLocked(ComponentName componentName, int userId) {
-        SettingsStringHelper settingsHelper = new SettingsStringHelper(
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userId);
-        settingsHelper.deleteService(componentName);
-        settingsHelper.writeToSettings();
+        final SettingStringHelper setting =
+                new SettingStringHelper(
+                        mContext.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                        userId);
+        setting.write(ComponentNameSet.remove(setting.read(), componentName));
 
         UserState userState = getUserStateLocked(userId);
         if (userState.mEnabledServices.remove(componentName)) {
@@ -2060,45 +2124,6 @@
         return mFingerprintGestureDispatcher.onFingerprintGesture(gestureKeyCode);
     }
 
-    private class SettingsStringHelper {
-        private static final String SETTINGS_DELIMITER = ":";
-        private ContentResolver mContentResolver;
-        private final String mSettingsName;
-        private Set<String> mServices;
-        private final int mUserId;
-
-        public SettingsStringHelper(String name, int userId) {
-            mUserId = userId;
-            mSettingsName = name;
-            mContentResolver = mContext.getContentResolver();
-            String servicesString = Settings.Secure.getStringForUser(
-                    mContentResolver, mSettingsName, userId);
-            mServices = new HashSet();
-            if (!TextUtils.isEmpty(servicesString)) {
-                final TextUtils.SimpleStringSplitter colonSplitter =
-                        new TextUtils.SimpleStringSplitter(SETTINGS_DELIMITER.charAt(0));
-                colonSplitter.setString(servicesString);
-                while (colonSplitter.hasNext()) {
-                    final String serviceName = colonSplitter.next();
-                    mServices.add(serviceName);
-                }
-            }
-        }
-
-        public void addService(ComponentName component) {
-            mServices.add(component.flattenToString());
-        }
-
-        public void deleteService(ComponentName component) {
-            mServices.remove(component.flattenToString());
-        }
-
-        public void writeToSettings() {
-            Settings.Secure.putStringForUser(mContentResolver, mSettingsName,
-                    TextUtils.join(SETTINGS_DELIMITER, mServices), mUserId);
-        }
-    }
-
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
@@ -2225,12 +2250,12 @@
                     final int clientState = msg.arg1;
                     final int userId = msg.arg2;
                     sendStateToClients(clientState, mGlobalClients);
-                    sendStateToClientsForUser(clientState, userId);
+                    sendStateToClients(clientState, getUserClientsForId(userId));
                 } break;
 
                 case MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER: {
                     final int userId = msg.arg1;
-                    sendStateToClientsForUser(0, userId);
+                    sendStateToClients(0, getUserClientsForId(userId));
                 } break;
 
                 case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: {
@@ -2257,7 +2282,9 @@
                 } break;
 
                 case MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS: {
-                    notifyClientsOfServicesStateChange();
+                    final int userId = msg.arg1;
+                    notifyClientsOfServicesStateChange(mGlobalClients);
+                    notifyClientsOfServicesStateChange(getUserClientsForId(userId));
                 } break;
 
                 case MSG_UPDATE_FINGERPRINT: {
@@ -2282,12 +2309,12 @@
             }
         }
 
-        private void sendStateToClientsForUser(int clientState, int userId) {
+        private RemoteCallbackList<IAccessibilityManagerClient> getUserClientsForId(int userId) {
             final UserState userState;
             synchronized (mLock) {
                 userState = getUserStateLocked(userId);
             }
-            sendStateToClients(clientState, userState.mClients);
+            return userState.mUserClients;
         }
 
         private void sendStateToClients(int clientState,
@@ -2307,11 +2334,8 @@
             }
         }
 
-        private void notifyClientsOfServicesStateChange() {
-            RemoteCallbackList<IAccessibilityManagerClient> clients;
-            synchronized (mLock) {
-                clients = getCurrentUserStateLocked().mClients;
-            }
+        private void notifyClientsOfServicesStateChange(
+                RemoteCallbackList<IAccessibilityManagerClient> clients) {
             try {
                 final int userClientCount = clients.beginBroadcast();
                 for (int i = 0; i < userClientCount; i++) {
@@ -2426,6 +2450,8 @@
 
         boolean mCaptureFingerprintGestures;
 
+        boolean mRequestAccessibilityButton;
+
         int mFetchFlags;
 
         long mNotificationTimeout;
@@ -2581,6 +2607,8 @@
                     & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
             mCaptureFingerprintGestures = (info.flags
                     & AccessibilityServiceInfo.FLAG_CAPTURE_FINGERPRINT_GESTURES) != 0;
+            mRequestAccessibilityButton = (info.flags
+                    & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
         }
 
         /**
@@ -2694,7 +2722,7 @@
                     }
                     UserState userState = getUserStateLocked(mUserId);
                     onUserStateChangedLocked(userState);
-                    scheduleNotifyClientsOfServicesStateChange();
+                    scheduleNotifyClientsOfServicesStateChange(userState);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3332,6 +3360,19 @@
         }
 
         @Override
+        public boolean isAccessibilityButtonAvailable() {
+            final UserState userState;
+            synchronized (mLock) {
+                if (!isCalledForCurrentUserLocked()) {
+                    return false;
+                }
+                userState = getCurrentUserStateLocked();
+            }
+
+            return mRequestAccessibilityButton && userState.mIsAccessibilityButtonAvailable;
+        }
+
+        @Override
         public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
             mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
             synchronized (mLock) {
@@ -3544,6 +3585,14 @@
             mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
         }
 
+        public void notifyAccessibilityButtonClickedLocked() {
+            mInvocationHandler.notifyAccessibilityButtonClickedLocked();
+        }
+
+        public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+            mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+        }
+
         /**
          * Called by the invocation handler to notify the service that the
          * state of magnification has changed.
@@ -3582,6 +3631,36 @@
             }
         }
 
+        private void notifyAccessibilityButtonClickedInternal() {
+            final IAccessibilityServiceClient listener;
+            synchronized (mLock) {
+                listener = mServiceInterface;
+            }
+            if (listener != null) {
+                try {
+                    listener.onAccessibilityButtonClicked();
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+                }
+            }
+        }
+
+        private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
+            final IAccessibilityServiceClient listener;
+            synchronized (mLock) {
+                listener = mServiceInterface;
+            }
+            if (listener != null) {
+                try {
+                    listener.onAccessibilityButtonAvailabilityChanged(available);
+                } catch (RemoteException re) {
+                    Slog.e(LOG_TAG,
+                            "Error sending accessibility button availability change to " + mService,
+                            re);
+                }
+            }
+        }
+
         private void notifyGestureInternal(int gestureId) {
             final IAccessibilityServiceClient listener;
             synchronized (mLock) {
@@ -3723,6 +3802,8 @@
 
             private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
             private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
+            private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
+            private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
 
             private boolean mIsMagnificationCallbackEnabled = false;
             private boolean mIsSoftKeyboardCallbackEnabled = false;
@@ -3758,6 +3839,15 @@
                         notifySoftKeyboardShowModeChangedInternal(showState);
                     } break;
 
+                    case MSG_ON_ACCESSIBILITY_BUTTON_CLICKED: {
+                        notifyAccessibilityButtonClickedInternal();
+                    } break;
+
+                    case MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
+                        final boolean available = (message.arg1 != 0);
+                        notifyAccessibilityButtonAvailabilityChangedInternal(available);
+                    } break;
+
                     default: {
                         throw new IllegalArgumentException("Unknown message: " + type);
                     }
@@ -3797,6 +3887,17 @@
             public void setSoftKeyboardCallbackEnabled(boolean enabled) {
                 mIsSoftKeyboardCallbackEnabled = enabled;
             }
+
+            public void notifyAccessibilityButtonClickedLocked() {
+                final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_CLICKED);
+                msg.sendToTarget();
+            }
+
+            public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+                final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED,
+                        (available ? 1 : 0), 0);
+                msg.sendToTarget();
+            }
         }
     }
 
@@ -4467,7 +4568,7 @@
 
         // Non-transient state.
 
-        public final RemoteCallbackList<IAccessibilityManagerClient> mClients =
+        public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
             new RemoteCallbackList<>();
 
         public final SparseArray<AccessibilityConnectionWrapper> mInteractionConnections =
@@ -4501,6 +4602,8 @@
 
         public int mSoftKeyboardShowMode = 0;
 
+        public boolean mIsAccessibilityButtonAvailable;
+
         public boolean mIsTouchExplorationEnabled;
         public boolean mIsTextHighContrastEnabled;
         public boolean mIsEnhancedWebAccessibilityEnabled;
@@ -4575,6 +4678,9 @@
             mIsDisplayMagnificationEnabled = false;
             mIsAutoclickEnabled = false;
             mSoftKeyboardShowMode = 0;
+
+            // Clear state tracked from system UI
+            mIsAccessibilityButtonAvailable = false;
         }
 
         public void destroyUiAutomationService() {
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 22dbf44..5fe8b1a 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -927,7 +927,7 @@
                 try {
                     for (int i = 0; i < count; i++) {
                         consumer.accept(callbackList.getBroadcastItem(i),
-                                callbackList.getRegisteredCallbackCookie(i));
+                                callbackList.getBroadcastCookie(i));
                     }
                 } finally {
                     callbackList.finishBroadcast();
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3da49d8..168744b 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -18,9 +18,6 @@
 
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
-import com.android.server.twilight.TwilightListener;
-import com.android.server.twilight.TwilightManager;
-import com.android.server.twilight.TwilightState;
 
 import android.annotation.Nullable;
 import android.hardware.Sensor;
@@ -55,9 +52,6 @@
     // non-zero, which in turn ensures that the total weight is non-zero.
     private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100;
 
-    // Specifies the maximum magnitude of the time of day adjustment.
-    private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1f;
-
     // Debounce for sampling user-initiated changes in display brightness to ensure
     // the user is satisfied with the result before storing the sample.
     private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
@@ -74,9 +68,6 @@
     // The light sensor, or null if not available or needed.
     private final Sensor mLightSensor;
 
-    // The twilight service.
-    private final TwilightManager mTwilight;
-
     // The auto-brightness spline adjustment.
     // The brightness values have been scaled to a range of 0..1.
     private final Spline mScreenAutoBrightnessSpline;
@@ -186,8 +177,6 @@
     private int mBrightnessAdjustmentSampleOldBrightness;
     private float mBrightnessAdjustmentSampleOldGamma;
 
-    private boolean mUseTwilight;
-
     public AutomaticBrightnessController(Callbacks callbacks, Looper looper,
             SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime,
             int brightnessMin, int brightnessMax, float dozeScaleFactor,
@@ -196,7 +185,6 @@
             int ambientLightHorizon, float autoBrightnessAdjustmentMaxGamma,
             HysteresisLevels dynamicHysteresis) {
         mCallbacks = callbacks;
-        mTwilight = LocalServices.getService(TwilightManager.class);
         mSensorManager = sensorManager;
         mScreenAutoBrightnessSpline = autoBrightnessSpline;
         mScreenBrightnessRangeMinimum = brightnessMin;
@@ -233,7 +221,7 @@
     }
 
     public void configure(boolean enable, float adjustment, boolean dozing,
-            boolean userInitiatedChange, boolean useTwilight) {
+            boolean userInitiatedChange) {
         // While dozing, the application processor may be suspended which will prevent us from
         // receiving new information from the light sensor. On some devices, we may be able to
         // switch to a wake-up light sensor instead but for now we will simply disable the sensor
@@ -242,7 +230,6 @@
         mDozing = dozing;
         boolean changed = setLightSensorEnabled(enable && !dozing);
         changed |= setScreenAutoBrightnessAdjustment(adjustment);
-        changed |= setUseTwilight(useTwilight);
         if (changed) {
             updateAutoBrightness(false /*sendUpdate*/);
         }
@@ -251,17 +238,6 @@
         }
     }
 
-    private boolean setUseTwilight(boolean useTwilight) {
-        if (mUseTwilight == useTwilight) return false;
-        if (useTwilight) {
-            mTwilight.registerListener(mTwilightListener, mHandler);
-        } else {
-            mTwilight.unregisterListener(mTwilightListener);
-        }
-        mUseTwilight = useTwilight;
-        return true;
-    }
-
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Automatic Brightness Controller Configuration:");
@@ -276,7 +252,6 @@
         pw.println();
         pw.println("Automatic Brightness Controller State:");
         pw.println("  mLightSensor=" + mLightSensor);
-        pw.println("  mTwilight.getLastTwilightState()=" + mTwilight.getLastTwilightState());
         pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
         pw.println("  mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime));
         pw.println("  mAmbientLux=" + mAmbientLux);
@@ -522,19 +497,6 @@
             }
         }
 
-        if (mUseTwilight) {
-            TwilightState state = mTwilight.getLastTwilightState();
-            if (state != null && state.isNight()) {
-                final long duration = state.sunriseTimeMillis() - state.sunsetTimeMillis();
-                final long progress = System.currentTimeMillis() - state.sunsetTimeMillis();
-                final float amount = (float) Math.pow(2.0 * progress / duration - 1.0, 2.0);
-                gamma *= 1 + amount * TWILIGHT_ADJUSTMENT_MAX_GAMMA;
-                if (DEBUG) {
-                    Slog.d(TAG, "updateAutoBrightness: twilight amount=" + amount);
-                }
-            }
-        }
-
         if (gamma != 1.0f) {
             final float in = value;
             value = MathUtils.pow(value, gamma);
@@ -649,13 +611,6 @@
         }
     };
 
-    private final TwilightListener mTwilightListener = new TwilightListener() {
-        @Override
-        public void onTwilightStateChanged(@Nullable TwilightState state) {
-            updateAutoBrightness(true /*sendUpdate*/);
-        }
-    };
-
     /** Callbacks to request updates to the display's power state. */
     interface Callbacks {
         void updateBrightness();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 015345c..bed269c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -642,7 +642,7 @@
                     && mPowerRequest.brightnessSetByUser;
             mAutomaticBrightnessController.configure(autoBrightnessEnabled,
                     mPowerRequest.screenAutoBrightnessAdjustment, state != Display.STATE_ON,
-                    userInitiatedChange, mPowerRequest.useTwilight);
+                    userInitiatedChange);
         }
 
         // Apply brightness boost.
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index d1f7cfd..e83b228 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -83,6 +83,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -113,6 +114,8 @@
             new ArrayList<>();
     private final CopyOnWriteArrayList<IFingerprintClientActiveCallback> mClientActiveCallbacks =
             new CopyOnWriteArrayList<>();
+    private final Map<Integer, Long> mAuthenticatorIds =
+            Collections.synchronizedMap(new HashMap<>());
     private final AppOpsManager mAppOps;
     private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000;
     private static final int MAX_FAILED_ATTEMPTS = 5;
@@ -130,7 +133,6 @@
     private final UserManager mUserManager;
     private ClientMonitor mCurrentClient;
     private ClientMonitor mPendingClient;
-    private long mCurrentAuthenticatorId;
     private PerformanceStats mPerformanceStats;
 
     // Normal fingerprint authentications are tracked by mPerformanceMap.
@@ -239,6 +241,7 @@
 
             if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId);
             if (mHalDeviceId != 0) {
+                loadAuthenticatorIds();
                 updateActiveGroup(ActivityManager.getCurrentUser(), null);
             } else {
                 Slog.w(TAG, "Failed to open Fingerprint HAL!");
@@ -249,6 +252,26 @@
         return mDaemon;
     }
 
+    /** Populates existing authenticator ids. To be used only during the start of the service. */
+    private void loadAuthenticatorIds() {
+        // This operation can be expensive, so keep track of the elapsed time. Might need to move to
+        // background if it takes too long.
+        long t = System.currentTimeMillis();
+
+        mAuthenticatorIds.clear();
+        for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) {
+            int userId = getUserOrWorkProfileId(null, user.id);
+            if (!mAuthenticatorIds.containsKey(userId)) {
+                updateActiveGroup(userId, null);
+            }
+        }
+
+        t = System.currentTimeMillis() - t;
+        if (t > 1000) {
+            Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms");
+        }
+    }
+
     protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
         if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId + ", gid="
                 + groupId + "rem=" + remaining);
@@ -499,14 +522,23 @@
 
     boolean isCurrentUserOrProfile(int userId) {
         UserManager um = UserManager.get(mContext);
-
-        // Allow current user or profiles of the current user...
-        for (int profileId : um.getEnabledProfileIds(mCurrentUserId)) {
-            if (profileId == userId) {
-                return true;
-            }
+        if (um == null) {
+            Slog.e(TAG, "Unable to acquire UserManager");
+            return false;
         }
-        return false;
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // Allow current user or profiles of the current user...
+            for (int profileId : um.getEnabledProfileIds(mCurrentUserId)) {
+                if (profileId == userId) {
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private boolean isForegroundActivity(int uid, int pid) {
@@ -1195,7 +1227,7 @@
                     daemon.setActiveGroup(userId, fpDir.getAbsolutePath());
                     mCurrentUserId = userId;
                 }
-                mCurrentAuthenticatorId = daemon.getAuthenticatorId();
+                mAuthenticatorIds.put(userId, daemon.getAuthenticatorId());
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to setActiveGroup():", e);
             }
@@ -1218,8 +1250,14 @@
      * @return true if this is a work profile
      */
     private boolean isWorkProfile(int userId) {
-        UserInfo info = mUserManager.getUserInfo(userId);
-        return info != null && info.isManagedProfile();
+        UserInfo userInfo = null;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            userInfo = mUserManager.getUserInfo(userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return userInfo != null && userInfo.isManagedProfile();
     }
 
     private void listenForUserSwitches() {
@@ -1239,9 +1277,11 @@
 
     /***
      * @param opPackageName the name of the calling package
-     * @return authenticator id for the current user
+     * @return authenticator id for the calling user
      */
     public long getAuthenticatorId(String opPackageName) {
-        return mCurrentAuthenticatorId;
+        final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
+        Long authenticatorId = mAuthenticatorIds.get(userId);
+        return authenticatorId != null ? authenticatorId : 0;
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 072959f..4207998 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -833,7 +833,6 @@
     private List<String> mKeepUninstalledPackages;
 
     private UserManagerInternal mUserManagerInternal;
-    private final UserDataPreparer mUserDataPreparer;
 
     private File mCacheDir;
 
@@ -1937,7 +1936,7 @@
 
                     // Clean up any users or apps that were removed or recreated
                     // while this volume was missing
-                    reconcileUsers(volumeUuid);
+                    sUserManager.reconcileUsers(volumeUuid);
                     reconcileApps(volumeUuid);
 
                     // Clean up any install sessions that expired or were
@@ -2270,8 +2269,8 @@
             mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
             mAsecInternalPath = new File(dataDir, "app-asec").getPath();
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
-            mUserDataPreparer = new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore);
-            sUserManager = new UserManagerService(context, this, mUserDataPreparer, mPackages);
+            sUserManager = new UserManagerService(context, this,
+                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
 
             // Propagate permission configuration in to package manager.
             ArrayMap<String, SystemConfig.PermissionEntry> permConfig
@@ -19971,7 +19970,7 @@
         });
 
         // Now that we're mostly running, clean up stale users and apps
-        reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
+        sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
         reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
     }
 
@@ -21309,60 +21308,6 @@
         }
     }
 
-    /**
-     * Examine all users present on given mounted volume, and destroy data
-     * belonging to users that are no longer valid, or whose user ID has been
-     * recycled.
-     */
-    private void reconcileUsers(String volumeUuid) {
-        final List<File> files = new ArrayList<>();
-        Collections.addAll(files, FileUtils
-                .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
-        Collections.addAll(files, FileUtils
-                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
-        Collections.addAll(files, FileUtils
-                .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
-        Collections.addAll(files, FileUtils
-                .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
-        Collections.addAll(files, FileUtils
-                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
-        for (File file : files) {
-            if (!file.isDirectory()) continue;
-
-            final int userId;
-            final UserInfo info;
-            try {
-                userId = Integer.parseInt(file.getName());
-                info = sUserManager.getUserInfo(userId);
-            } catch (NumberFormatException e) {
-                Slog.w(TAG, "Invalid user directory " + file);
-                continue;
-            }
-
-            boolean destroyUser = false;
-            if (info == null) {
-                logCriticalInfo(Log.WARN, "Destroying user directory " + file
-                        + " because no matching user was found");
-                destroyUser = true;
-            } else if (!mOnlyCore) {
-                try {
-                    UserManagerService.enforceSerialNumber(file, info.serialNumber);
-                } catch (IOException e) {
-                    logCriticalInfo(Log.WARN, "Destroying user directory " + file
-                            + " because we failed to enforce serial number: " + e);
-                    destroyUser = true;
-                }
-            }
-
-            if (destroyUser) {
-                synchronized (mInstallLock) {
-                    mUserDataPreparer.destroyUserDataLI(volumeUuid, userId,
-                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
-                }
-            }
-        }
-    }
-
     private void assertPackageKnown(String volumeUuid, String packageName)
             throws PackageManagerException {
         synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 52599fd..fc00acc 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -17,13 +17,28 @@
 package com.android.server.pm;
 
 import android.content.Context;
+import android.content.pm.UserInfo;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 import static com.android.server.pm.PackageManagerService.logCriticalInfo;
 
@@ -31,6 +46,9 @@
  * Helper class for preparing and destroying user storage
  */
 class UserDataPreparer {
+    private static final String TAG = "UserDataPreparer";
+    private static final String XATTR_SERIAL = "user.serial";
+
     private final Object mInstallLock;
     private final Context mContext;
     private final boolean mOnlyCore;
@@ -65,19 +83,15 @@
             storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
 
             if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
-                UserManagerService.enforceSerialNumber(
-                        Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial);
+                enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
                 if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
-                    UserManagerService.enforceSerialNumber(
-                            Environment.getDataSystemDeDirectory(userId), userSerial);
+                    enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
                 }
             }
             if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
-                UserManagerService.enforceSerialNumber(
-                        Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial);
+                enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
                 if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
-                    UserManagerService.enforceSerialNumber(
-                            Environment.getDataSystemCeDirectory(userId), userSerial);
+                    enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
                 }
             }
 
@@ -117,13 +131,13 @@
             // Clean up system data
             if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
                 if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
-                    FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId));
-                    FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId));
-                    FileUtils.deleteContentsAndDir(Environment.getDataMiscDeDirectory(userId));
+                    FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
+                    FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
+                    FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
                 }
                 if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
-                    FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId));
-                    FileUtils.deleteContentsAndDir(Environment.getDataMiscCeDirectory(userId));
+                    FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
+                    FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
                 }
             }
 
@@ -136,4 +150,183 @@
         }
     }
 
+    /**
+     * Examine all users present on given mounted volume, and destroy data
+     * belonging to users that are no longer valid, or whose user ID has been
+     * recycled.
+     */
+    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) {
+        final List<File> files = new ArrayList<>();
+        Collections.addAll(files, FileUtils
+                .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
+        Collections.addAll(files, FileUtils
+                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
+        Collections.addAll(files, FileUtils
+                .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
+        Collections.addAll(files, FileUtils
+                .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
+        Collections.addAll(files, FileUtils
+                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
+        reconcileUsers(volumeUuid, validUsersList, files);
+    }
+
+    @VisibleForTesting
+    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) {
+        final int userCount = validUsersList.size();
+        SparseArray<UserInfo> users = new SparseArray<>(userCount);
+        for (int i = 0; i < userCount; i++) {
+            UserInfo user = validUsersList.get(i);
+            users.put(user.id, user);
+        }
+        for (File file : files) {
+            if (!file.isDirectory()) {
+                continue;
+            }
+
+            final int userId;
+            final UserInfo info;
+            try {
+                userId = Integer.parseInt(file.getName());
+                info = users.get(userId);
+            } catch (NumberFormatException e) {
+                Slog.w(TAG, "Invalid user directory " + file);
+                continue;
+            }
+
+            boolean destroyUser = false;
+            if (info == null) {
+                logCriticalInfo(Log.WARN, "Destroying user directory " + file
+                        + " because no matching user was found");
+                destroyUser = true;
+            } else if (!mOnlyCore) {
+                try {
+                    enforceSerialNumber(file, info.serialNumber);
+                } catch (IOException e) {
+                    logCriticalInfo(Log.WARN, "Destroying user directory " + file
+                            + " because we failed to enforce serial number: " + e);
+                    destroyUser = true;
+                }
+            }
+
+            if (destroyUser) {
+                synchronized (mInstallLock) {
+                    destroyUserDataLI(volumeUuid, userId,
+                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    protected File getDataMiscCeDirectory(int userId) {
+        return Environment.getDataMiscCeDirectory(userId);
+    }
+
+    @VisibleForTesting
+    protected File getDataSystemCeDirectory(int userId) {
+        return Environment.getDataSystemCeDirectory(userId);
+    }
+
+    @VisibleForTesting
+    protected File getDataMiscDeDirectory(int userId) {
+        return Environment.getDataMiscDeDirectory(userId);
+    }
+
+    @VisibleForTesting
+    protected File getUserSystemDirectory(int userId) {
+        return Environment.getUserSystemDirectory(userId);
+    }
+
+    @VisibleForTesting
+    protected File getDataUserCeDirectory(String volumeUuid, int userId) {
+        return Environment.getDataUserCeDirectory(volumeUuid, userId);
+    }
+
+    @VisibleForTesting
+    protected File getDataSystemDeDirectory(int userId) {
+        return Environment.getDataSystemDeDirectory(userId);
+    }
+
+    @VisibleForTesting
+    protected File getDataUserDeDirectory(String volumeUuid, int userId) {
+        return Environment.getDataUserDeDirectory(volumeUuid, userId);
+    }
+
+    @VisibleForTesting
+    protected boolean isFileEncryptedEmulatedOnly() {
+        return StorageManager.isFileEncryptedEmulatedOnly();
+    }
+
+    /**
+     * Enforce that serial number stored in user directory inode matches the
+     * given expected value. Gracefully sets the serial number if currently
+     * undefined.
+     *
+     * @throws IOException when problem extracting serial number, or serial
+     *             number is mismatched.
+     */
+    void enforceSerialNumber(File file, int serialNumber) throws IOException {
+        if (isFileEncryptedEmulatedOnly()) {
+            // When we're emulating FBE, the directory may have been chmod
+            // 000'ed, meaning we can't read the serial number to enforce it;
+            // instead of destroying the user, just log a warning.
+            Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid");
+            return;
+        }
+
+        final int foundSerial = getSerialNumber(file);
+        Slog.v(TAG, "Found " + file + " with serial number " + foundSerial);
+
+        if (foundSerial == -1) {
+            Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid");
+            try {
+                setSerialNumber(file, serialNumber);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to set serial number on " + file, e);
+            }
+
+        } else if (foundSerial != serialNumber) {
+            throw new IOException("Found serial number " + foundSerial
+                    + " doesn't match expected " + serialNumber);
+        }
+    }
+
+    /**
+     * Set serial number stored in user directory inode.
+     *
+     * @throws IOException if serial number was already set
+     */
+    private static void setSerialNumber(File file, int serialNumber) throws IOException {
+        try {
+            final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
+            Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
+     * Return serial number stored in user directory inode.
+     *
+     * @return parsed serial number, or -1 if not set
+     */
+    @VisibleForTesting
+    static int getSerialNumber(File file) throws IOException {
+        try {
+            final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
+            final String serial = new String(buf);
+            try {
+                return Integer.parseInt(serial);
+            } catch (NumberFormatException e) {
+                throw new IOException("Bad serial number: " + serial);
+            }
+        } catch (ErrnoException e) {
+            if (e.errno == OsConstants.ENODATA) {
+                return -1;
+            } else {
+                throw e.rethrowAsIOException();
+            }
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 455d3e4..627fa54 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -218,8 +218,6 @@
     static final int WRITE_USER_MSG = 1;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
-    private static final String XATTR_SERIAL = "user.serial";
-
     // Tron counters
     private static final String TRON_GUEST_CREATED = "users_guest_created";
     private static final String TRON_USER_CREATED = "users_user_created";
@@ -3159,6 +3157,15 @@
     }
 
     /**
+     * Examine all users present on given mounted volume, and destroy data
+     * belonging to users that are no longer valid, or whose user ID has been
+     * recycled.
+     */
+    void reconcileUsers(String volumeUuid) {
+        mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+    }
+
+    /**
      * Make a note of the last started time of a user and do some cleanup.
      * This is called with ActivityManagerService lock held.
      * @param userId the user that was just foregrounded
@@ -3219,78 +3226,6 @@
         return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
     }
 
-    /**
-     * Enforce that serial number stored in user directory inode matches the
-     * given expected value. Gracefully sets the serial number if currently
-     * undefined.
-     *
-     * @throws IOException when problem extracting serial number, or serial
-     *             number is mismatched.
-     */
-    public static void enforceSerialNumber(File file, int serialNumber) throws IOException {
-        if (StorageManager.isFileEncryptedEmulatedOnly()) {
-            // When we're emulating FBE, the directory may have been chmod
-            // 000'ed, meaning we can't read the serial number to enforce it;
-            // instead of destroying the user, just log a warning.
-            Slog.w(LOG_TAG, "Device is emulating FBE; assuming current serial number is valid");
-            return;
-        }
-
-        final int foundSerial = getSerialNumber(file);
-        Slog.v(LOG_TAG, "Found " + file + " with serial number " + foundSerial);
-
-        if (foundSerial == -1) {
-            Slog.d(LOG_TAG, "Serial number missing on " + file + "; assuming current is valid");
-            try {
-                setSerialNumber(file, serialNumber);
-            } catch (IOException e) {
-                Slog.w(LOG_TAG, "Failed to set serial number on " + file, e);
-            }
-
-        } else if (foundSerial != serialNumber) {
-            throw new IOException("Found serial number " + foundSerial
-                    + " doesn't match expected " + serialNumber);
-        }
-    }
-
-    /**
-     * Set serial number stored in user directory inode.
-     *
-     * @throws IOException if serial number was already set
-     */
-    private static void setSerialNumber(File file, int serialNumber)
-            throws IOException {
-        try {
-            final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
-            Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
-        } catch (ErrnoException e) {
-            throw e.rethrowAsIOException();
-        }
-    }
-
-    /**
-     * Return serial number stored in user directory inode.
-     *
-     * @return parsed serial number, or -1 if not set
-     */
-    private static int getSerialNumber(File file) throws IOException {
-        try {
-            final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
-            final String serial = new String(buf);
-            try {
-                return Integer.parseInt(serial);
-            } catch (NumberFormatException e) {
-                throw new IOException("Bad serial number: " + serial);
-            }
-        } catch (ErrnoException e) {
-            if (e.errno == OsConstants.ENODATA) {
-                return -1;
-            } else {
-                throw e.rethrowAsIOException();
-            }
-        }
-    }
-
     @Override
     public void setSeedAccountData(int userId, String accountName, String accountType,
             PersistableBundle accountOptions, boolean persist) {
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index 6edb4d2..7a28081 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -18,6 +18,7 @@
 
 import android.app.StatusBarManager;
 import android.os.Handler;
+import android.os.Message;
 import android.os.SystemClock;
 import android.util.Slog;
 import android.view.View;
@@ -43,6 +44,8 @@
 
     private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
 
+    private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
+
     protected final String mTag;
     private final int mTransientFlag;
     private final int mUnhideFlag;
@@ -63,6 +66,8 @@
     private boolean mSetUnHideFlagWhenNextTransparent;
     private boolean mNoAnimationOnNextShow;
 
+    private OnBarVisibilityChangedListener mVisibilityChangeListener;
+
     public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag,
             int statusBarManagerId, int translucentWmFlag, int transparentFlag) {
         mTag = "BarController." + tag;
@@ -72,7 +77,7 @@
         mStatusBarManagerId = statusBarManagerId;
         mTranslucentWmFlag = translucentWmFlag;
         mTransparentFlag = transparentFlag;
-        mHandler = new Handler();
+        mHandler = new BarHandler();
     }
 
     public void setWindow(WindowState win) {
@@ -153,9 +158,18 @@
         mNoAnimationOnNextShow = false;
         final int state = computeStateLw(wasVis, wasAnim, mWin, change);
         final boolean stateChanged = updateStateLw(state);
+
+        if (change && (mVisibilityChangeListener != null)) {
+            mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, show ? 1 : 0, 0).sendToTarget();
+        }
+
         return change || stateChanged;
     }
 
+    void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener) {
+        mVisibilityChangeListener = listener;
+    }
+
     protected boolean skipAnimation() {
         return false;
     }
@@ -300,4 +314,22 @@
             pw.println(transientBarStateToString(mTransientBarState));
         }
     }
+
+    private class BarHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_NAV_BAR_VISIBILITY_CHANGED:
+                    final boolean visible = msg.arg1 != 0;
+                    if (mVisibilityChangeListener != null) {
+                        mVisibilityChangeListener.onBarVisibilityChanged(visible);
+                    }
+                    break;
+            }
+        }
+    }
+
+    interface OnBarVisibilityChangedListener {
+        void onBarVisibilityChanged(boolean visible);
+    }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 180f6c9..c795676 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -990,6 +990,14 @@
             WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
             View.NAVIGATION_BAR_TRANSPARENT);
 
+    private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
+            new BarController.OnBarVisibilityChangedListener() {
+        @Override
+        public void onBarVisibilityChanged(boolean visible) {
+            mAccessibilityManager.notifyAccessibilityButtonAvailabilityChanged(visible);
+        }
+    };
+
     private ImmersiveModeConfirmation mImmersiveModeConfirmation;
 
     private SystemGesturesPointerEventListener mSystemGestures;
@@ -2900,6 +2908,8 @@
                 }
                 mNavigationBar = win;
                 mNavigationBarController.setWindow(win);
+                mNavigationBarController.setOnBarVisibilityChangedListener(
+                        mNavBarVisibilityListener);
                 if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
                 break;
             case TYPE_NAVIGATION_BAR_PANEL:
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 238866a..91a5f4f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -517,9 +517,6 @@
     private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
             = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
 
-    // True if brightness should be affected by twilight.
-    private boolean mBrightnessUseTwilight;
-
     // True if we are currently in VR Mode.
     private boolean mIsVrModeEnabled;
 
@@ -735,9 +732,6 @@
             resolver.registerContentObserver(Settings.Secure.getUriFor(
                     Settings.Secure.DOUBLE_TAP_TO_WAKE),
                     false, mSettingsObserver, UserHandle.USER_ALL);
-            resolver.registerContentObserver(Settings.Secure.getUriFor(
-                    Secure.BRIGHTNESS_USE_TWILIGHT),
-                    false, mSettingsObserver, UserHandle.USER_ALL);
             IVrManager vrManager =
                     (IVrManager) getBinderService(VrManagerService.VR_MANAGER_BINDER_SERVICE);
             if (vrManager != null) {
@@ -878,9 +872,6 @@
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
 
-        mBrightnessUseTwilight = Settings.Secure.getIntForUser(resolver,
-                Secure.BRIGHTNESS_USE_TWILIGHT, 0, UserHandle.USER_CURRENT) != 0;
-
         final boolean lowPowerModeEnabled = Settings.Global.getInt(resolver,
                 Settings.Global.LOW_POWER_MODE, 0) != 0;
         final boolean autoLowPowerModeConfigured = Settings.Global.getInt(resolver,
@@ -2254,7 +2245,6 @@
             mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
             mDisplayPowerRequest.lowPowerMode = mLowPowerModeEnabled;
             mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
-            mDisplayPowerRequest.useTwilight = mBrightnessUseTwilight;
 
             if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
                 mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index fb0dd2a..b4467af 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -23,9 +23,6 @@
 
 public interface StatusBarManagerInternal {
     void setNotificationDelegate(NotificationDelegate delegate);
-    void buzzBeepBlinked();
-    void notificationLightPulse(int argb, int onMillis, int offMillis);
-    void notificationLightOff();
     void showScreenPinningRequest(int taskId);
     void showAssistDisclosure();
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7b7db0e..2dfe20a8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -120,40 +120,6 @@
         }
 
         @Override
-        public void buzzBeepBlinked() {
-            if (mBar != null) {
-                try {
-                    mBar.buzzBeepBlinked();
-                } catch (RemoteException ex) {
-                }
-            }
-        }
-
-        @Override
-        public void notificationLightPulse(int argb, int onMillis, int offMillis) {
-            mNotificationLightOn = true;
-            if (mBar != null) {
-                try {
-                    mBar.notificationLightPulse(argb, onMillis, offMillis);
-                } catch (RemoteException ex) {
-                }
-            }
-        }
-
-        @Override
-        public void notificationLightOff() {
-            if (mNotificationLightOn) {
-                mNotificationLightOn = false;
-                if (mBar != null) {
-                    try {
-                        mBar.notificationLightOff();
-                    } catch (RemoteException ex) {
-                    }
-                }
-            }
-        }
-
-        @Override
         public void showScreenPinningRequest(int taskId) {
             if (mBar != null) {
                 try {
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index 545b3d7..703518d 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -70,7 +70,7 @@
 static void nativeInit(JNIEnv* env, jobject obj) {
     // TODO(b/31632518)
     if (gThermalModule == nullptr) {
-        gThermalModule = IThermal::getService("thermal");
+        gThermalModule = IThermal::getService();
     }
 
     if (gThermalModule == nullptr) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3eebd89..83e209d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,8 @@
             "com.android.server.voiceinteraction.VoiceInteractionManagerService";
     private static final String PRINT_MANAGER_SERVICE_CLASS =
             "com.android.server.print.PrintManagerService";
+    private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
+            "com.android.server.print.CompanionDeviceManagerService";
     private static final String USB_SERVICE_CLASS =
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
@@ -1350,6 +1352,10 @@
                 traceEnd();
             }
 
+            traceBeginAndSlog("StartCompanionDeviceManager");
+            mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+            traceEnd();
+
             traceBeginAndSlog("StartRestrictionManager");
             mSystemServiceManager.startService(RestrictionsManagerService.class);
             traceEnd();
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
new file mode 100644
index 0000000..9824c1d
--- /dev/null
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.server.print;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.app.PendingIntent;
+import android.companion.AssociationRequest;
+import android.companion.ICompanionDeviceManager;
+import android.companion.ICompanionDeviceManagerService;
+import android.companion.IOnAssociateCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+//TODO move to own package!
+/** @hide */
+public class CompanionDeviceManagerService extends SystemService {
+
+    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
+            "com.android.companiondevicemanager", ".DeviceDiscoveryService");
+
+    private static final boolean DEBUG = false;
+    private static final String LOG_TAG = "CompanionDeviceManagerService";
+
+    private final CompanionDeviceManagerImpl mImpl;
+
+    public CompanionDeviceManagerService(Context context) {
+        super(context);
+        mImpl = new CompanionDeviceManagerImpl();
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
+    }
+
+    class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+
+        @Override
+        public void associate(
+                AssociationRequest request,
+                IOnAssociateCallback callback,
+                String callingPackage) throws RemoteException {
+            if (DEBUG) {
+                Log.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback
+                        + ", callingPackage = " + callingPackage + ")");
+            }
+            checkNotNull(request);
+            checkNotNull(callback);
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                //TODO bindServiceAsUser
+                getContext().bindService(
+                        new Intent().setComponent(SERVICE_TO_BIND_TO),
+                        getServiceConnection(request, callback, callingPackage),
+                        Context.BIND_AUTO_CREATE);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+    }
+
+    private ServiceConnection getServiceConnection(
+            final AssociationRequest<?> request,
+            final IOnAssociateCallback callback,
+            final String callingPackage) {
+        return new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG,
+                            "onServiceConnected(name = " + name + ", service = "
+                                    + service + ")");
+                }
+                try {
+                    ICompanionDeviceManagerService.Stub
+                            .asInterface(service)
+                            .startDiscovery(
+                                    request,
+                                    getCallback(callingPackage, callback),
+                                    callingPackage);
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                if (DEBUG) Log.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
+            }
+        };
+    }
+
+    private IOnAssociateCallback.Stub getCallback(
+            String callingPackage,
+            IOnAssociateCallback propagateTo) {
+        return new IOnAssociateCallback.Stub() {
+
+            @Override
+            public void onSuccess(PendingIntent launcher)
+                    throws RemoteException {
+                if (DEBUG) Log.i(LOG_TAG, "onSuccess(launcher = " + launcher + ")");
+                recordSpecialPriviledgesForPackage(callingPackage);
+                propagateTo.onSuccess(launcher);
+            }
+
+            @Override
+            public void onFailure(CharSequence reason) throws RemoteException {
+                if (DEBUG) Log.i(LOG_TAG, "onFailure()");
+                propagateTo.onFailure(reason);
+            }
+        };
+    }
+
+    void recordSpecialPriviledgesForPackage(String priviledgedPackage) {
+        //TODO Show dialog before recording notification access
+//        final SettingStringHelper setting =
+//                new SettingStringHelper(
+//                        getContext().getContentResolver(),
+//                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+//                        Binder.getCallingUid());
+//        setting.write(ColonDelimitedSet.OfStrings.add(setting.read(), priviledgedPackage));
+    }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
index aa08b41..064ab0a 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationComparatorTest.java
@@ -112,6 +112,7 @@
         Notification n2 = new Notification.Builder(mContext)
                 .setCategory(Notification.CATEGORY_CALL)
                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .setColorized(true /* colorized */)
                 .build();
         mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg,
                 callPkg, 1, "highcall", callUid, callUid, n2,
@@ -186,10 +187,10 @@
         final List<NotificationRecord> expected = new ArrayList<>();
         expected.add(mRecordHighCall);
         expected.add(mRecordDefaultMedia);
-        expected.add(mRecordStarredContact);
-        expected.add(mRecordContact);
         expected.add(mRecordInlineReply);
         expected.add(mRecordSms);
+        expected.add(mRecordStarredContact);
+        expected.add(mRecordContact);
         expected.add(mRecordEmail);
         expected.add(mRecordUrgent);
         expected.add(mRecordCheater);
@@ -207,14 +208,19 @@
     @Test
     public void testMessaging() throws Exception {
         NotificationComparator comp = new NotificationComparator(mContext);
-        assertTrue(comp.isImportantMessaging(mRecordStarredContact));
-        assertTrue(comp.isImportantMessaging(mRecordContact));
         assertTrue(comp.isImportantMessaging(mRecordInlineReply));
         assertTrue(comp.isImportantMessaging(mRecordSms));
         assertFalse(comp.isImportantMessaging(mRecordEmail));
         assertFalse(comp.isImportantMessaging(mRecordCheater));
     }
 
+    @Test
+    public void testPeople() throws Exception {
+        NotificationComparator comp = new NotificationComparator(mContext);
+        assertTrue(comp.isImportantPeople(mRecordStarredContact));
+        assertTrue(comp.isImportantPeople(mRecordContact));
+    }
+
     private NotificationChannel getDefaultChannel() {
         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
                 NotificationManager.IMPORTANCE_LOW);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
new file mode 100644
index 0000000..7a676e25
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.FileUtils;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * <p>Run with:<pre>
+ * m FrameworksServicesTests &&
+ * adb install \
+ * -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
+ * adb shell am instrument -e class com.android.server.pm.UserDataPreparerTest \
+ * -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
+public class UserDataPreparerTest {
+
+    private static final int TEST_USER_SERIAL = 1000;
+    private static final int TEST_USER_ID = 10;
+
+    private TestUserDataPreparer mUserDataPreparer;
+
+    @Mock
+    private StorageManager mStorageManagerMock;
+
+    @Mock
+    private Context mContextMock;
+
+    @Mock
+    private Installer mInstaller;
+
+    private Object mInstallLock;
+
+    @Before
+    public void setup() {
+        Context ctx = InstrumentationRegistry.getContext();
+        FileUtils.deleteContents(ctx.getCacheDir());
+        mInstallLock = new Object();
+        MockitoAnnotations.initMocks(this);
+        mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock, false,
+                ctx.getCacheDir());
+        when(mContextMock.getSystemServiceName(StorageManager.class))
+                .thenReturn(Context.STORAGE_SERVICE);
+        when(mContextMock.getSystemService(eq(Context.STORAGE_SERVICE)))
+                .thenReturn(mStorageManagerMock);
+        VolumeInfo testVolume = new VolumeInfo("testuuid", VolumeInfo.TYPE_PRIVATE, null, null);
+        when(mStorageManagerMock.getWritablePrivateVolumes()).thenReturn(Arrays.asList(testVolume));
+    }
+
+    @Test
+    public void testPrepareUserData_De() throws Exception {
+        File userDeDir = mUserDataPreparer.getDataUserDeDirectory(null, TEST_USER_ID);
+        userDeDir.mkdirs();
+        File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
+        systemDeDir.mkdirs();
+        mUserDataPreparer
+                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_DE);
+        verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+        verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
+        int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+        serialNumber = UserDataPreparer.getSerialNumber(systemDeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+    }
+
+    @Test
+    public void testPrepareUserData_Ce() throws Exception {
+        File userCeDir = mUserDataPreparer.getDataUserCeDirectory(null, TEST_USER_ID);
+        userCeDir.mkdirs();
+        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+        systemCeDir.mkdirs();
+        mUserDataPreparer
+                .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_CE);
+        verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+        verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+                eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
+        int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+        serialNumber = UserDataPreparer.getSerialNumber(systemCeDir);
+        assertEquals(TEST_USER_SERIAL, serialNumber);
+    }
+
+    @Test
+    public void testDestroyUserData() throws Exception {
+        // Add file in CE
+        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+        systemCeDir.mkdirs();
+        File ceFile = new File(systemCeDir, "file");
+        writeFile(ceFile, "-----" );
+        testDestroyUserData_De();
+        // CE directory should be preserved
+        assertEquals(Collections.singletonList(ceFile), Arrays.asList(FileUtils.listFilesOrEmpty(
+                systemCeDir)));
+
+        testDestroyUserData_Ce();
+
+        // Verify that testDir is empty
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                mUserDataPreparer.testDir)));
+    }
+
+    @Test
+    public void testDestroyUserData_De() throws Exception {
+        File systemDir = mUserDataPreparer.getUserSystemDirectory(TEST_USER_ID);
+        systemDir.mkdirs();
+        writeFile(new File(systemDir, "file"), "-----" );
+        File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
+        systemDeDir.mkdirs();
+        writeFile(new File(systemDeDir, "file"), "-----" );
+        File miscDeDir = mUserDataPreparer.getDataMiscDeDirectory(TEST_USER_ID);
+        miscDeDir.mkdirs();
+        writeFile(new File(miscDeDir, "file"), "-----" );
+
+        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
+
+        verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+                        eq(StorageManager.FLAG_STORAGE_DE));
+        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                        eq(StorageManager.FLAG_STORAGE_DE));
+
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(systemDir)));
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                systemDeDir)));
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                miscDeDir)));
+    }
+
+    @Test
+    public void testDestroyUserData_Ce() throws Exception {
+        File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
+        systemCeDir.mkdirs();
+        writeFile(new File(systemCeDir, "file"), "-----" );
+        File miscCeDir = mUserDataPreparer.getDataMiscCeDirectory(TEST_USER_ID);
+        miscCeDir.mkdirs();
+        writeFile(new File(miscCeDir, "file"), "-----" );
+
+        mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
+
+        verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+                eq(StorageManager.FLAG_STORAGE_CE));
+        verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+                eq(StorageManager.FLAG_STORAGE_CE));
+
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                systemCeDir)));
+        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
+                miscCeDir)));
+    }
+
+    @Test
+    public void testReconcileUsers() throws Exception {
+        UserInfo u1 = new UserInfo(1, "u1", 0);
+        UserInfo u2 = new UserInfo(2, "u2", 0);
+        File testDir = mUserDataPreparer.testDir;
+        File dir1 = new File(testDir, "1");
+        dir1.mkdirs();
+        File dir2 = new File(testDir, "2");
+        dir2.mkdirs();
+        File dir3 = new File(testDir, "3");
+        dir3.mkdirs();
+
+        mUserDataPreparer
+                .reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL, Arrays.asList(u1, u2),
+                        Arrays.asList(dir1, dir2, dir3));
+        // Verify that user 3 data is removed
+        verify(mInstaller).destroyUserData(isNull(String.class), eq(3),
+                eq(StorageManager.FLAG_STORAGE_DE|StorageManager.FLAG_STORAGE_CE));
+    }
+
+    private static void writeFile(File file, String content) throws IOException {
+        try (FileOutputStream os = new FileOutputStream(file)) {
+            os.write(content.getBytes(Charset.defaultCharset()));
+        }
+    }
+
+    private static class TestUserDataPreparer extends UserDataPreparer {
+        File testDir;
+
+        TestUserDataPreparer(Installer installer, Object installLock, Context context,
+                boolean onlyCore, File testDir) {
+            super(installer, installLock, context, onlyCore);
+            this.testDir = testDir;
+        }
+
+        @Override
+        protected File getDataMiscCeDirectory(int userId) {
+            return new File(testDir, "misc_ce_" + userId);
+        }
+
+        @Override
+        protected File getDataSystemCeDirectory(int userId) {
+            return new File(testDir, "system_ce_" + userId);
+        }
+
+        @Override
+        protected File getDataMiscDeDirectory(int userId) {
+            return new File(testDir, "misc_de_" + userId);
+        }
+
+        @Override
+        protected File getUserSystemDirectory(int userId) {
+            return new File(testDir, "user_system_" + userId);
+        }
+
+        @Override
+        protected File getDataUserCeDirectory(String volumeUuid, int userId) {
+            return new File(testDir, "user_ce_" + userId);
+        }
+
+        @Override
+        protected File getDataSystemDeDirectory(int userId) {
+            return new File(testDir, "system_de_" + userId);
+        }
+
+        @Override
+        protected File getDataUserDeDirectory(String volumeUuid, int userId) {
+            return new File(testDir, "user_de_" + userId);
+        }
+
+        @Override
+        protected boolean isFileEncryptedEmulatedOnly() {
+            return false;
+        }
+    }
+
+}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index d0ccd55..6e10029 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -98,6 +98,7 @@
     private static final String SESSION_ADD_CS_ADAPTER = "CS.aCSA";
     private static final String SESSION_REMOVE_CS_ADAPTER = "CS.rCSA";
     private static final String SESSION_CREATE_CONN = "CS.crCo";
+    private static final String SESSION_CREATE_CONN_FAILED = "CS.crCoF";
     private static final String SESSION_ABORT = "CS.ab";
     private static final String SESSION_ANSWER = "CS.an";
     private static final String SESSION_ANSWER_VIDEO = "CS.anV";
@@ -142,6 +143,7 @@
     private static final int MSG_PULL_EXTERNAL_CALL = 22;
     private static final int MSG_SEND_CALL_EVENT = 23;
     private static final int MSG_ON_EXTRAS_CHANGED = 24;
+    private static final int MSG_CREATE_CONNECTION_FAILED = 25;
 
     private static Connection sNullConnection;
 
@@ -211,6 +213,25 @@
         }
 
         @Override
+        public void createConnectionFailed(
+                String callId,
+                ConnectionRequest request,
+                boolean isIncoming,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONN_FAILED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = request;
+                args.arg3 = Log.createSubsession();
+                args.argi1 = isIncoming ? 1 : 0;
+                mHandler.obtainMessage(MSG_CREATE_CONNECTION_FAILED, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void abort(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_ABORT);
             try {
@@ -552,6 +573,35 @@
                     }
                     break;
                 }
+                case MSG_CREATE_CONNECTION_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3, SESSION_HANDLER +
+                            SESSION_CREATE_CONN_FAILED);
+                    try {
+                        final String id = (String) args.arg1;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg2;
+                        final boolean isIncoming = args.argi1 == 1;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONN_FAILED + ".pICR",
+                                            null /*lock*/) {
+                                        @Override
+                                        public void loggedRun() {
+                                            createConnectionFailed(id, request, isIncoming);
+                                        }
+                                    }.prepare());
+                        } else {
+                            Log.i(this, "createConnectionFailed %s", id);
+                            createConnectionFailed(id, request, isIncoming);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 case MSG_ABORT: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ABORT);
@@ -1175,6 +1225,17 @@
         }
     }
 
+    private void createConnectionFailed(final String callId, final ConnectionRequest request,
+            boolean isIncoming) {
+
+        Log.i(this, "createConnectionFailed %s", callId);
+        if (isIncoming) {
+            onCreateIncomingConnectionFailed(request);
+        } else {
+            onCreateOutgoingConnectionFailed(request);
+        }
+    }
+
     private void abort(String callId) {
         Log.d(this, "abort %s", callId);
         findConnectionForAction(callId, "abort").onAbort();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ba7b6a1..96070b8 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1517,6 +1517,10 @@
      *      otherwise.
      */
     public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+        if (phoneAccountHandle == null) {
+            return false;
+        }
+
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 8a27675..20feba7 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -46,6 +46,9 @@
             boolean isUnknown,
             in Session.Info sessionInfo);
 
+    void createConnectionFailed(String callId, in ConnectionRequest request, boolean isIncoming,
+            in Session.Info sessionInfo);
+
     void abort(String callId, in Session.Info sessionInfo);
 
     void answerVideo(String callId, int videoState, in Session.Info sessionInfo);