Merge "Pass in correct user to AccountManagerBackupHelper"
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
index 0ec8253..270cb18 100644
--- a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -1,5 +1,4 @@
 rubinxu@google.com
-acjohnston@google.com
 pgrafov@google.com
 ayushsha@google.com
 alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
index fb00fe5..6ce25cc 100644
--- a/core/java/android/app/admin/EnterprisePlatform_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -1,2 +1,5 @@
+# Assign bugs to android-enterprise-triage@google.com
 file:WorkDeviceExperience_OWNERS
+file:Provisioning_OWNERS
+file:WorkProfile_OWNERS
 file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
new file mode 100644
index 0000000..c59a9dc
--- /dev/null
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -0,0 +1,5 @@
+# Assign bugs to android-enterprise-triage@google.com
+petuska@google.com
+nupursn@google.com
+shreyacsingh@google.com
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
index 82afddd..2033343 100644
--- a/core/java/android/app/admin/WorkDeviceExperience_OWNERS
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -1,5 +1,7 @@
+# Assign bugs to android-enterprise-triage@google.com
 work-device-experience+reviews@google.com
-scottjonathan@google.com
-kholoudm@google.com
-eliselliott@google.com
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
+eliselliott@google.com #{LAST_RESORT_SUGGESTION}
+kholoudm@google.com #{LAST_RESORT_SUGGESTION}
+acjohnston@google.com #{LAST_RESORT_SUGGESTION}
 alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/admin/WorkProfile_OWNERS b/core/java/android/app/admin/WorkProfile_OWNERS
new file mode 100644
index 0000000..260b672
--- /dev/null
+++ b/core/java/android/app/admin/WorkProfile_OWNERS
@@ -0,0 +1,5 @@
+# Assign bugs to android-enterprise-triage@google.com
+liahav@google.com
+olit@google.com
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
+alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 58f7759..0311da4 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -23,6 +23,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Describes a keyboard layout.
@@ -30,6 +31,37 @@
  * @hide
  */
 public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> {
+
+    /** Undefined keyboard layout */
+    public static final String LAYOUT_TYPE_UNDEFINED = "undefined";
+
+    /** Qwerty-based keyboard layout */
+    public static final String LAYOUT_TYPE_QWERTY = "qwerty";
+
+    /** Qwertz-based keyboard layout */
+    public static final String LAYOUT_TYPE_QWERTZ = "qwertz";
+
+    /** Azerty-based keyboard layout */
+    public static final String LAYOUT_TYPE_AZERTY = "azerty";
+
+    /** Dvorak keyboard layout */
+    public static final String LAYOUT_TYPE_DVORAK = "dvorak";
+
+    /** Colemak keyboard layout */
+    public static final String LAYOUT_TYPE_COLEMAK = "colemak";
+
+    /** Workman keyboard layout */
+    public static final String LAYOUT_TYPE_WORKMAN = "workman";
+
+    /** Turkish-F keyboard layout */
+    public static final String LAYOUT_TYPE_TURKISH_F = "turkish_f";
+
+    /** Turkish-Q keyboard layout */
+    public static final String LAYOUT_TYPE_TURKISH_Q = "turkish_q";
+
+    /** Keyboard layout that has been enhanced with a large number of extra characters */
+    public static final String LAYOUT_TYPE_EXTENDED = "extended";
+
     private final String mDescriptor;
     private final String mLabel;
     private final String mCollection;
@@ -42,31 +74,24 @@
 
     /** Currently supported Layout types in the KCM files */
     private enum LayoutType {
-        UNDEFINED(0, "undefined"),
-        QWERTY(1, "qwerty"),
-        QWERTZ(2, "qwertz"),
-        AZERTY(3, "azerty"),
-        DVORAK(4, "dvorak"),
-        COLEMAK(5, "colemak"),
-        WORKMAN(6, "workman"),
-        TURKISH_F(7, "turkish_f"),
-        TURKISH_Q(8, "turkish_q"),
-        EXTENDED(9, "extended");
+        UNDEFINED(0, LAYOUT_TYPE_UNDEFINED),
+        QWERTY(1, LAYOUT_TYPE_QWERTY),
+        QWERTZ(2, LAYOUT_TYPE_QWERTZ),
+        AZERTY(3, LAYOUT_TYPE_AZERTY),
+        DVORAK(4, LAYOUT_TYPE_DVORAK),
+        COLEMAK(5, LAYOUT_TYPE_COLEMAK),
+        WORKMAN(6, LAYOUT_TYPE_WORKMAN),
+        TURKISH_F(7, LAYOUT_TYPE_TURKISH_F),
+        TURKISH_Q(8, LAYOUT_TYPE_TURKISH_Q),
+        EXTENDED(9, LAYOUT_TYPE_EXTENDED);
 
         private final int mValue;
         private final String mName;
         private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
         static {
-            VALUE_TO_ENUM_MAP.put(UNDEFINED.mValue, UNDEFINED);
-            VALUE_TO_ENUM_MAP.put(QWERTY.mValue, QWERTY);
-            VALUE_TO_ENUM_MAP.put(QWERTZ.mValue, QWERTZ);
-            VALUE_TO_ENUM_MAP.put(AZERTY.mValue, AZERTY);
-            VALUE_TO_ENUM_MAP.put(DVORAK.mValue, DVORAK);
-            VALUE_TO_ENUM_MAP.put(COLEMAK.mValue, COLEMAK);
-            VALUE_TO_ENUM_MAP.put(WORKMAN.mValue, WORKMAN);
-            VALUE_TO_ENUM_MAP.put(TURKISH_F.mValue, TURKISH_F);
-            VALUE_TO_ENUM_MAP.put(TURKISH_Q.mValue, TURKISH_Q);
-            VALUE_TO_ENUM_MAP.put(EXTENDED.mValue, EXTENDED);
+            for (LayoutType type : LayoutType.values()) {
+                VALUE_TO_ENUM_MAP.put(type.mValue, type);
+            }
         }
 
         private static LayoutType of(int value) {
@@ -207,6 +232,9 @@
         // keyboards to be listed before lower priority keyboards.
         int result = Integer.compare(another.mPriority, mPriority);
         if (result == 0) {
+            result = Integer.compare(mLayoutType.mValue, another.mLayoutType.mValue);
+        }
+        if (result == 0) {
             result = mLabel.compareToIgnoreCase(another.mLabel);
         }
         if (result == 0) {
@@ -226,4 +254,21 @@
                 + ", vendorId: " + mVendorId
                 + ", productId: " + mProductId;
     }
+
+    /**
+     * Check if the provided layout type is supported/valid.
+     *
+     * @param layoutName name of layout type
+     * @return {@code true} if the provided layout type is supported/valid.
+     */
+    public static boolean isLayoutTypeValid(@NonNull String layoutName) {
+        Objects.requireNonNull(layoutName, "Provided layout name should not be null");
+        for (LayoutType layoutType : LayoutType.values()) {
+            if (layoutName.equals(layoutType.getName())) {
+                return true;
+            }
+        }
+        // Layout doesn't match any supported layout types
+        return false;
+    }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 34aa7ef..547d406 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1142,6 +1142,16 @@
                 @BatteryConsumer.ProcessState int processState);
 
 
+
+        /**
+         * Returns the battery consumption (in microcoulombs) of UID's camera usage, derived from
+         * on-device power measurement data.
+         * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+         *
+         * {@hide}
+         */
+        public abstract long getCameraEnergyConsumptionUC();
+
         /**
          * Returns the battery consumption (in microcoulombs) used by this uid for each
          * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
@@ -2921,6 +2931,15 @@
     public abstract long getWifiEnergyConsumptionUC();
 
     /**
+     * Returns the battery consumption (in microcoulombs) of camera, derived from on
+     * device power measurement data.
+     * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+     *
+     * {@hide}
+     */
+    public abstract long getCameraEnergyConsumptionUC();
+
+    /**
      * Returns the battery consumption (in microcoulombs) that each
      * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer
      * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}) consumed.
diff --git a/core/java/com/android/internal/power/EnergyConsumerStats.java b/core/java/com/android/internal/power/EnergyConsumerStats.java
index 325df57..8cf17cda 100644
--- a/core/java/com/android/internal/power/EnergyConsumerStats.java
+++ b/core/java/com/android/internal/power/EnergyConsumerStats.java
@@ -58,7 +58,8 @@
     public static final int POWER_BUCKET_BLUETOOTH = 5;
     public static final int POWER_BUCKET_GNSS = 6;
     public static final int POWER_BUCKET_MOBILE_RADIO = 7;
-    public static final int NUMBER_STANDARD_POWER_BUCKETS = 8; // Buckets above this are custom.
+    public static final int POWER_BUCKET_CAMERA = 8;
+    public static final int NUMBER_STANDARD_POWER_BUCKETS = 9; // Buckets above this are custom.
 
     @IntDef(prefix = {"POWER_BUCKET_"}, value = {
             POWER_BUCKET_UNKNOWN,
@@ -70,6 +71,7 @@
             POWER_BUCKET_BLUETOOTH,
             POWER_BUCKET_GNSS,
             POWER_BUCKET_MOBILE_RADIO,
+            POWER_BUCKET_CAMERA,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StandardPowerBucket {
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
index a1a6b51..a9ea6d3 100644
--- a/media/java/android/media/tv/TableRequest.java
+++ b/media/java/android/media/tv/TableRequest.java
@@ -33,11 +33,54 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT})
+    @IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT, TABLE_NAME_CAT})
     public @interface TableName {}
 
+    /** Program Association Table */
     public static final int TABLE_NAME_PAT = 0;
+    /** Program Mapping Table */
     public static final int TABLE_NAME_PMT = 1;
+    /**
+     * Conditional Access Table
+     * @hide
+     */
+    public static final int TABLE_NAME_CAT = 2;
+    /**
+     * Network Information Table
+     * @hide
+     */
+    public static final int TABLE_NAME_NIT = 3;
+    /**
+     * Bouquet Association Table
+     * @hide
+     */
+    public static final int TABLE_NAME_BAT = 4;
+    /**
+     * Service Description Table
+     * @hide
+     */
+    public static final int TABLE_NAME_SDT = 5;
+    /**
+     * Event Information Table
+     * @hide
+     */
+    public static final int TABLE_NAME_EIT = 6;
+    /**
+     * Time and Date Table
+     * @hide
+     */
+    public static final int TABLE_NAME_TDT = 7;
+    /**
+     * Time Offset Table
+     * @hide
+     */
+    public static final int TABLE_NAME_TOT = 8;
+    /**
+     * Selection Information Table
+     * @hide
+     */
+    public static final int TABLE_NAME_SIT = 9;
+
 
     public static final @NonNull Parcelable.Creator<TableRequest> CREATOR =
             new Parcelable.Creator<TableRequest>() {
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index afc9bee..1c314b0 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SharedMemory;
 
 /**
  * A response for Table from broadcast signal.
@@ -46,6 +47,8 @@
     private final Uri mTableUri;
     private final int mVersion;
     private final int mSize;
+    private final byte[] mTableByteArray;
+    private final SharedMemory mTableSharedMemory;
 
     static TableResponse createFromParcelBody(Parcel in) {
         return new TableResponse(in);
@@ -54,9 +57,33 @@
     public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
             @Nullable Uri tableUri, int version, int size) {
         super(RESPONSE_TYPE, requestId, sequence, responseResult);
-        mTableUri = tableUri;
         mVersion = version;
         mSize = size;
+        mTableUri = tableUri;
+        mTableByteArray = null;
+        mTableSharedMemory = null;
+    }
+
+    /** @hide */
+    public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
+            @NonNull byte[] tableByteArray, int version, int size) {
+        super(RESPONSE_TYPE, requestId, sequence, responseResult);
+        mVersion = version;
+        mSize = size;
+        mTableUri = null;
+        mTableByteArray = tableByteArray;
+        mTableSharedMemory = null;
+    }
+
+    /** @hide */
+    public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
+            @NonNull SharedMemory tableSharedMemory, int version, int size) {
+        super(RESPONSE_TYPE, requestId, sequence, responseResult);
+        mVersion = version;
+        mSize = size;
+        mTableUri = null;
+        mTableByteArray = null;
+        mTableSharedMemory = tableSharedMemory;
     }
 
     TableResponse(Parcel source) {
@@ -65,6 +92,14 @@
         mTableUri = uriString == null ? null : Uri.parse(uriString);
         mVersion = source.readInt();
         mSize = source.readInt();
+        int arrayLength = source.readInt();
+        if (arrayLength >= 0) {
+            mTableByteArray = new byte[arrayLength];
+            source.readByteArray(mTableByteArray);
+        } else {
+            mTableByteArray = null;
+        }
+        mTableSharedMemory = (SharedMemory) source.readTypedObject(SharedMemory.CREATOR);
     }
 
     /**
@@ -76,6 +111,30 @@
     }
 
     /**
+     * Gets the data of the table as a byte array.
+     *
+     * @return the table data as a byte array, or {@code null} if the data is not stored as a byte
+     *         array.
+     * @hide
+     */
+    @Nullable
+    public byte[] getTableByteArray() {
+        return mTableByteArray;
+    }
+
+    /**
+     * Gets the data of the table as a {@link SharedMemory} object.
+     *
+     * @return the table data as a {@link SharedMemory} object, or {@code null} if the data is not
+     *         stored in shared memory.
+     * @hide
+     */
+    @Nullable
+    public SharedMemory getTableSharedMemory() {
+        return mTableSharedMemory;
+    }
+
+    /**
      * Gets the version number of requested table. If it is null, value will be -1.
      * <p>The consistency of version numbers between request and response depends on
      * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value
@@ -106,5 +165,12 @@
         dest.writeString(uriString);
         dest.writeInt(mVersion);
         dest.writeInt(mSize);
+        if (mTableByteArray != null) {
+            dest.writeInt(mTableByteArray.length);
+            dest.writeByteArray(mTableByteArray);
+        } else {
+            dest.writeInt(-1);
+        }
+        dest.writeTypedObject(mTableSharedMemory, flags);
     }
 }
diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java
index 03c62f0..d04c58a 100644
--- a/media/java/android/media/tv/TimelineRequest.java
+++ b/media/java/android/media/tv/TimelineRequest.java
@@ -42,6 +42,7 @@
             };
 
     private final int mIntervalMillis;
+    private final String mSelector;
 
     static TimelineRequest createFromParcelBody(Parcel in) {
         return new TimelineRequest(in);
@@ -50,11 +51,21 @@
     public TimelineRequest(int requestId, @RequestOption int option, int intervalMillis) {
         super(REQUEST_TYPE, requestId, option);
         mIntervalMillis = intervalMillis;
+        mSelector = null;
+    }
+
+    /** @hide */
+    public TimelineRequest(int requestId, @RequestOption int option, int intervalMillis,
+            @NonNull String selector) {
+        super(REQUEST_TYPE, requestId, option);
+        mIntervalMillis = intervalMillis;
+        mSelector = selector;
     }
 
     TimelineRequest(Parcel source) {
         super(REQUEST_TYPE, source);
         mIntervalMillis = source.readInt();
+        mSelector = source.readString();
     }
 
     /**
@@ -64,6 +75,18 @@
         return mIntervalMillis;
     }
 
+    /**
+     * Gets the timeline selector.
+     * <p>The selector describes the type and location of timeline signalling. For example
+     * {@code urn:dvb:css:timeline:pts} is a selector in DVB standard.
+     *
+     * @return the selector if it's set; {@code null} otherwise.
+     * @hide
+     */
+    public String getSelector() {
+        return mSelector;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -73,5 +96,6 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeInt(mIntervalMillis);
+        dest.writeString(mSelector);
     }
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 494571f..ad9312f 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -43,6 +43,7 @@
     void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
     void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters, int seq);
     void onSetVideoBounds(in Rect rect, int seq);
+    void onRequestCurrentVideoBounds(int seq);
     void onRequestCurrentChannelUri(int seq);
     void onRequestCurrentChannelLcn(int seq);
     void onRequestStreamVolume(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 599922c..c0723f7 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -48,6 +48,7 @@
             in IBinder sessionToken, in Uri biIAppUri, in Bundle params, int userId);
     void destroyBiInteractiveApp(in IBinder sessionToken, in String biIAppId, int userId);
     void setTeletextAppEnabled(in IBinder sessionToken, boolean enable, int userId);
+    void sendCurrentVideoBounds(in IBinder sessionToken, in Rect bounds, int userId);
     void sendCurrentChannelUri(in IBinder sessionToken, in Uri channelUri, int userId);
     void sendCurrentChannelLcn(in IBinder sessionToken, int lcn, int userId);
     void sendStreamVolume(in IBinder sessionToken, float volume, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 17a70d1..9ae9ca7 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -40,6 +40,7 @@
     void createBiInteractiveApp(in Uri biIAppUri, in Bundle params);
     void destroyBiInteractiveApp(in String biIAppId);
     void setTeletextAppEnabled(boolean enable);
+    void sendCurrentVideoBounds(in Rect bounds);
     void sendCurrentChannelUri(in Uri channelUri);
     void sendCurrentChannelLcn(int lcn);
     void sendStreamVolume(float volume);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 0565742..d84affd 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -42,6 +42,7 @@
     void onCommandRequest(in String cmdType, in Bundle parameters);
     void onTimeShiftCommandRequest(in String cmdType, in Bundle parameters);
     void onSetVideoBounds(in Rect rect);
+    void onRequestCurrentVideoBounds();
     void onRequestCurrentChannelUri();
     void onRequestCurrentChannelLcn();
     void onRequestStreamVolume();
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index b8158cd..8a23e65 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -94,6 +94,7 @@
     private static final int DO_NOTIFY_TIME_SHIFT_STATUS_CHANGED = 37;
     private static final int DO_NOTIFY_TIME_SHIFT_START_POSITION_CHANGED = 38;
     private static final int DO_NOTIFY_TIME_SHIFT_CURRENT_POSITION_CHANGED = 39;
+    private static final int DO_SEND_CURRENT_VIDEO_BOUNDS = 40;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -157,6 +158,10 @@
                 mSessionImpl.setTeletextAppEnabled((Boolean) msg.obj);
                 break;
             }
+            case DO_SEND_CURRENT_VIDEO_BOUNDS: {
+                mSessionImpl.sendCurrentVideoBounds((Rect) msg.obj);
+                break;
+            }
             case DO_SEND_CURRENT_CHANNEL_URI: {
                 mSessionImpl.sendCurrentChannelUri((Uri) msg.obj);
                 break;
@@ -355,6 +360,12 @@
     }
 
     @Override
+    public void sendCurrentVideoBounds(@Nullable Rect bounds) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageO(DO_SEND_CURRENT_VIDEO_BOUNDS, bounds));
+    }
+
+    @Override
     public void sendCurrentChannelUri(@Nullable Uri channelUri) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageO(DO_SEND_CURRENT_CHANNEL_URI, channelUri));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7c91844..fd3c29b 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -445,6 +445,18 @@
             }
 
             @Override
+            public void onRequestCurrentVideoBounds(int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postRequestCurrentVideoBounds();
+                }
+            }
+
+            @Override
             public void onRequestCurrentChannelUri(int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1084,6 +1096,18 @@
             }
         }
 
+        void sendCurrentVideoBounds(@NonNull Rect bounds) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         void sendCurrentChannelUri(@Nullable Uri channelUri) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
@@ -1898,6 +1922,15 @@
             });
         }
 
+        void postRequestCurrentVideoBounds() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onRequestCurrentVideoBounds(mSession);
+                }
+            });
+        }
+
         void postRequestCurrentChannelUri() {
             mHandler.post(new Runnable() {
                 @Override
@@ -2119,6 +2152,15 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#RequestCurrentVideoBounds} is
+         * called.
+         *
+         * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+         */
+        public void onRequestCurrentVideoBounds(Session session) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#RequestCurrentChannelUri} is
          * called.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 949e017..be2c16c 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.annotation.CallSuper;
+import android.annotation.IntDef;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -139,6 +140,38 @@
      * Playback command type: select the given track.
      */
     public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
+
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "PLAYBACK_COMMAND_STOP_MODE_", value = {
+            PLAYBACK_COMMAND_STOP_MODE_BLANK,
+            PLAYBACK_COMMAND_STOP_MODE_FREEZE
+    })
+    public @interface PlaybackCommandStopMode {}
+
+    /**
+     * Playback command stop mode: show a blank screen.
+     * @hide
+     */
+    public static final int PLAYBACK_COMMAND_STOP_MODE_BLANK = 1;
+
+    /**
+     * Playback command stop mode: freeze the video.
+     * @hide
+     */
+    public static final int PLAYBACK_COMMAND_STOP_MODE_FREEZE = 2;
+
+    /**
+     * Playback command parameter: stop mode.
+     * <p>Type: int
+     *
+     * @see #PLAYBACK_COMMAND_TYPE_STOP
+     * @hide
+     */
+    public static final String COMMAND_PARAMETER_KEY_STOP_MODE = "command_stop_mode";
+
     /**
      * Playback command parameter: channel URI.
      * <p>Type: android.net.Uri
@@ -498,6 +531,13 @@
         }
 
         /**
+         * Receives current video bounds.
+         * @hide
+         */
+        public void onCurrentVideoBounds(@NonNull Rect bounds) {
+        }
+
+        /**
          * Receives current channel URI.
          */
         public void onCurrentChannelUri(@Nullable Uri channelUri) {
@@ -983,6 +1023,30 @@
         }
 
         /**
+         * Requests the bounds of the current video.
+         * @hide
+         */
+        @CallSuper
+        public void requestCurrentVideoBounds() {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestCurrentVideoBounds");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onRequestCurrentVideoBounds();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestCurrentVideoBounds", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Requests the URI of the current channel.
          */
         @CallSuper
@@ -1309,6 +1373,10 @@
             onSetTeletextAppEnabled(enable);
         }
 
+        void sendCurrentVideoBounds(@NonNull Rect bounds) {
+            onCurrentVideoBounds(bounds);
+        }
+
         void sendCurrentChannelUri(@Nullable Uri channelUri) {
             onCurrentChannelUri(channelUri);
         }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 9211a12..af03bb8 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -514,6 +514,19 @@
     }
 
     /**
+     * Sends current video bounds to related TV interactive app.
+     * @hide
+     */
+    public void sendCurrentVideoBounds(@NonNull Rect bounds) {
+        if (DEBUG) {
+            Log.d(TAG, "sendCurrentVideoBounds");
+        }
+        if (mSession != null) {
+            mSession.sendCurrentVideoBounds(bounds);
+        }
+    }
+
+    /**
      * Sends current channel URI to related TV interactive app.
      *
      * @param channelUri The current channel URI; {@code null} if there is no currently tuned
@@ -944,6 +957,16 @@
         }
 
         /**
+         * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds()}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @hide
+         */
+        public void onRequestCurrentVideoBounds(@NonNull String iAppServiceId) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri()} is
          * called.
          *
@@ -1265,6 +1288,28 @@
         }
 
         @Override
+        public void onRequestCurrentVideoBounds(Session session) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestCurrentVideoBounds");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestCurrentVideoBounds - session not created");
+                return;
+            }
+            synchronized (mCallbackLock) {
+                if (mCallbackExecutor != null) {
+                    mCallbackExecutor.execute(() -> {
+                        synchronized (mCallbackLock) {
+                            if (mCallback != null) {
+                                mCallback.onRequestCurrentVideoBounds(mIAppServiceId);
+                            }
+                        }
+                    });
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentChannelUri(Session session) {
             if (DEBUG) {
                 Log.d(TAG, "onRequestCurrentChannelUri");
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 17b6f5d..ec09acd 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -170,7 +170,7 @@
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.soundtrigger-V2.3-java",
-        "android.hardware.power.stats-V1-java",
+        "android.hardware.power.stats-V2-java",
         "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ea7f0bb..e86632c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -70,6 +70,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.vibrator.StepSegment;
@@ -158,6 +159,11 @@
     private static final AdditionalDisplayInputProperties
             DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties();
 
+    // To disable Keyboard backlight control via Framework, run:
+    // 'adb shell setprop persist.input.keyboard_backlight_control.enabled false' (requires restart)
+    private static final boolean KEYBOARD_BACKLIGHT_CONTROL_ENABLED = SystemProperties.getBoolean(
+            "persist.input.keyboard.backlight_control.enabled", true);
+
     private final NativeInputManagerService mNative;
 
     private final Context mContext;
@@ -305,7 +311,7 @@
     private final BatteryController mBatteryController;
 
     // Manages Keyboard backlight
-    private final KeyboardBacklightController mKeyboardBacklightController;
+    private final KeyboardBacklightControllerInterface mKeyboardBacklightController;
 
     // Manages Keyboard modifier keys remapping
     private final KeyRemapper mKeyRemapper;
@@ -422,8 +428,10 @@
         mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
                 injector.getLooper());
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
-        mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
-                mDataStore, injector.getLooper());
+        mKeyboardBacklightController =
+                KEYBOARD_BACKLIGHT_CONTROL_ENABLED ? new KeyboardBacklightController(mContext,
+                        mNative, mDataStore, injector.getLooper())
+                        : new KeyboardBacklightControllerInterface() {};
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
@@ -3346,10 +3354,7 @@
         public void onInputMethodSubtypeChangedForKeyboardLayoutMapping(@UserIdInt int userId,
                 @Nullable InputMethodSubtypeHandle subtypeHandle,
                 @Nullable InputMethodSubtype subtype) {
-            if (DEBUG) {
-                Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
-                        + " subtypeHandle=" + subtypeHandle);
-            }
+            mKeyboardLayoutManager.onInputMethodSubtypeChanged(userId, subtypeHandle, subtype);
         }
 
         @Override
@@ -3478,4 +3483,13 @@
             applyAdditionalDisplayInputPropertiesLocked(properties);
         }
     }
+
+    interface KeyboardBacklightControllerInterface {
+        default void incrementKeyboardBacklight(int deviceId) {}
+        default void decrementKeyboardBacklight(int deviceId) {}
+        default void registerKeyboardBacklightListener(IKeyboardBacklightListener l, int pid) {}
+        default void unregisterKeyboardBacklightListener(IKeyboardBacklightListener l, int pid) {}
+        default void systemRunning() {}
+        default void dump(PrintWriter pw) {}
+    }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 77b0d4f..653a821 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -47,7 +47,8 @@
  * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
  * backlight for supported keyboards.
  */
-final class KeyboardBacklightController implements InputManager.InputDeviceListener {
+final class KeyboardBacklightController implements
+        InputManagerService.KeyboardBacklightControllerInterface, InputManager.InputDeviceListener {
 
     private static final String TAG = "KbdBacklightController";
 
@@ -96,7 +97,8 @@
         mHandler = new Handler(looper, this::handleMessage);
     }
 
-    void systemRunning() {
+    @Override
+    public void systemRunning() {
         InputManager inputManager = Objects.requireNonNull(
                 mContext.getSystemService(InputManager.class));
         inputManager.registerInputDeviceListener(this, mHandler);
@@ -106,11 +108,13 @@
         }
     }
 
+    @Override
     public void incrementKeyboardBacklight(int deviceId) {
         Message msg = Message.obtain(mHandler, MSG_INCREMENT_KEYBOARD_BACKLIGHT, deviceId);
         mHandler.sendMessage(msg);
     }
 
+    @Override
     public void decrementKeyboardBacklight(int deviceId) {
         Message msg = Message.obtain(mHandler, MSG_DECREMENT_KEYBOARD_BACKLIGHT, deviceId);
         mHandler.sendMessage(msg);
@@ -232,6 +236,7 @@
 
     /** Register the keyboard backlight listener for a process. */
     @BinderThread
+    @Override
     public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener,
             int pid) {
         synchronized (mKeyboardBacklightListenerRecords) {
@@ -252,6 +257,7 @@
 
     /** Unregister the keyboard backlight listener for a process. */
     @BinderThread
+    @Override
     public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener,
             int pid) {
         synchronized (mKeyboardBacklightListenerRecords) {
@@ -286,7 +292,8 @@
         }
     }
 
-    void dump(PrintWriter pw) {
+    @Override
+    public void dump(PrintWriter pw) {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
         ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index b8eb901..9e8b9f1 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -37,6 +37,8 @@
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
+import android.icu.lang.UScript;
+import android.icu.util.ULocale;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.LocaleList;
@@ -45,6 +47,8 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.view.InputDevice;
@@ -54,6 +58,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodSubtypeHandle;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.XmlUtils;
@@ -63,10 +68,12 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Stream;
 
@@ -94,10 +101,18 @@
     @GuardedBy("mDataStore")
     private final PersistentDataStore mDataStore;
     private final Handler mHandler;
+
     private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
     private boolean mKeyboardLayoutNotificationShown = false;
     private Toast mSwitchedKeyboardLayoutToast;
 
+    // This cache stores "best-matched" layouts so that we don't need to run the matching
+    // algorithm repeatedly.
+    @GuardedBy("mKeyboardLayoutCache")
+    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
+    @Nullable
+    private ImeInfo mCurrentImeInfo;
+
     KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
             PersistentDataStore dataStore, Looper looper) {
         mContext = context;
@@ -139,8 +154,10 @@
 
     @Override
     public void onInputDeviceRemoved(int deviceId) {
-        mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
-        maybeUpdateNotification();
+        if (!useNewSettingsUi()) {
+            mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
+            maybeUpdateNotification();
+        }
     }
 
     @Override
@@ -149,18 +166,21 @@
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
-        synchronized (mDataStore) {
-            String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
-            if (layout == null) {
-                layout = getDefaultKeyboardLayout(inputDevice);
-                if (layout != null) {
-                    setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
-                } else {
-                    mKeyboardsWithMissingLayouts.add(inputDevice);
+        if (!useNewSettingsUi()) {
+            synchronized (mDataStore) {
+                String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
+                if (layout == null) {
+                    layout = getDefaultKeyboardLayout(inputDevice);
+                    if (layout != null) {
+                        setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
+                    } else {
+                        mKeyboardsWithMissingLayouts.add(inputDevice);
+                    }
                 }
+                maybeUpdateNotification();
             }
-            maybeUpdateNotification();
         }
+        // TODO(b/259530132): Show notification for new Settings UI
     }
 
     private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -244,6 +264,12 @@
             }
         }
 
+        synchronized (mKeyboardLayoutCache) {
+            // Invalidate the cache: With packages being installed/removed, existing cache of
+            // auto-selected layout might not be the best layouts anymore.
+            mKeyboardLayoutCache.clear();
+        }
+
         // Reload keyboard layouts.
         reloadKeyboardLayouts();
     }
@@ -256,6 +282,9 @@
 
     public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
             final InputDeviceIdentifier identifier) {
+        if (useNewSettingsUi()) {
+            return new KeyboardLayout[0];
+        }
         final String[] enabledLayoutDescriptors =
                 getEnabledKeyboardLayoutsForInputDevice(identifier);
         final ArrayList<KeyboardLayout> enabledLayouts =
@@ -296,6 +325,7 @@
                 KeyboardLayout[]::new);
     }
 
+    @Nullable
     public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
@@ -434,21 +464,25 @@
         return LocaleList.forLanguageTags(languageTags.replace('|', ','));
     }
 
-    /**
-     * Builds a layout descriptor for the vendor/product. This returns the
-     * descriptor for ids that aren't useful (such as the default 0, 0).
-     */
-    private String getLayoutDescriptor(InputDeviceIdentifier identifier) {
+    private static String getLayoutDescriptor(@NonNull InputDeviceIdentifier identifier) {
         Objects.requireNonNull(identifier, "identifier must not be null");
         Objects.requireNonNull(identifier.getDescriptor(), "descriptor must not be null");
 
         if (identifier.getVendorId() == 0 && identifier.getProductId() == 0) {
             return identifier.getDescriptor();
         }
+        // If vendor id and product id is available, use it as keys. This allows us to have the
+        // same setup for all keyboards with same product and vendor id. i.e. User can swap 2
+        // identical keyboards and still get the same setup.
         return "vendor:" + identifier.getVendorId() + ",product:" + identifier.getProductId();
     }
 
+    @Nullable
     public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported");
+            return null;
+        }
         String key = getLayoutDescriptor(identifier);
         synchronized (mDataStore) {
             String layout;
@@ -468,9 +502,13 @@
 
     public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported");
+            return;
+        }
+
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
-
         String key = getLayoutDescriptor(identifier);
         synchronized (mDataStore) {
             try {
@@ -489,6 +527,10 @@
     }
 
     public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
+            return new String[0];
+        }
         String key = getLayoutDescriptor(identifier);
         synchronized (mDataStore) {
             String[] layouts = mDataStore.getKeyboardLayouts(key);
@@ -502,6 +544,10 @@
 
     public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported");
+            return;
+        }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
 
@@ -525,6 +571,10 @@
 
     public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
             String keyboardLayoutDescriptor) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported");
+            return;
+        }
         Objects.requireNonNull(keyboardLayoutDescriptor,
                 "keyboardLayoutDescriptor must not be null");
 
@@ -551,31 +601,11 @@
         }
     }
 
-    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
-        // TODO(b/259530132): Implement the new keyboard layout API: Returning non-IME specific
-        //  layout for now.
-        return getCurrentKeyboardLayoutForInputDevice(identifier);
-    }
-
-    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
-        // TODO(b/259530132): Implement the new keyboard layout API: setting non-IME specific
-        //  layout for now.
-        setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
-    }
-
-    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
-        // TODO(b/259530132): Implement the new keyboard layout API: Returning list of all
-        //  layouts for now.
-        return getKeyboardLayouts();
-    }
-
     public void switchKeyboardLayout(int deviceId, int direction) {
+        if (useNewSettingsUi()) {
+            Slog.e(TAG, "switchKeyboardLayout API not supported");
+            return;
+        }
         mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
     }
 
@@ -616,8 +646,21 @@
         }
     }
 
+    @Nullable
     public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
-        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        String keyboardLayoutDescriptor;
+        if (useNewSettingsUi()) {
+            if (mCurrentImeInfo == null) {
+                // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
+                // keyboard layouts once we receive the callback.
+                return null;
+            }
+
+            keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
+                    mCurrentImeInfo);
+        } else {
+            keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+        }
         if (keyboardLayoutDescriptor == null) {
             return null;
         }
@@ -640,6 +683,287 @@
         return result;
     }
 
+    @Nullable
+    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @Nullable InputMethodSubtype imeSubtype) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
+            return null;
+        }
+        InputMethodSubtypeHandle subtypeHandle = InputMethodSubtypeHandle.of(imeInfo, imeSubtype);
+        String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
+                new ImeInfo(userId, subtypeHandle, imeSubtype));
+        if (DEBUG) {
+            Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
+                    + userId + ", subtypeHandle = " + subtypeHandle + " -> " + layout);
+        }
+        return layout;
+    }
+
+    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @Nullable InputMethodSubtype imeSubtype,
+            String keyboardLayoutDescriptor) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported");
+            return;
+        }
+        Objects.requireNonNull(keyboardLayoutDescriptor,
+                "keyboardLayoutDescriptor must not be null");
+        String key = createLayoutKey(identifier, userId,
+                InputMethodSubtypeHandle.of(imeInfo, imeSubtype));
+        synchronized (mDataStore) {
+            try {
+                // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
+                if (mDataStore.setKeyboardLayout(getLayoutDescriptor(identifier), key,
+                        keyboardLayoutDescriptor)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "setKeyboardLayoutForInputDevice() " + identifier
+                                + " key: " + key
+                                + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
+                    }
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+            @Nullable InputMethodSubtype imeSubtype) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported");
+            return new KeyboardLayout[0];
+        }
+        return getKeyboardLayoutListForInputDeviceInternal(identifier, new ImeInfo(userId,
+                InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype));
+    }
+
+    private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
+            InputDeviceIdentifier identifier, ImeInfo imeInfo) {
+        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+
+        // Fetch user selected layout and always include it in layout list.
+        String userSelectedLayout;
+        synchronized (mDataStore) {
+            userSelectedLayout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
+        }
+
+        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
+        String imeLanguageTag;
+        if (imeInfo.mImeSubtype == null) {
+            imeLanguageTag = "";
+        } else {
+            ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
+            imeLanguageTag = imeLocale != null ? imeLocale.toLanguageTag()
+                    : imeInfo.mImeSubtype.getCanonicalizedLanguageTag();
+        }
+
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            boolean mDeviceSpecificLayoutAvailable;
+
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    int keyboardLayoutResId, KeyboardLayout layout) {
+                // Next find any potential layouts that aren't yet enabled for the device. For
+                // devices that have special layouts we assume there's a reason that the generic
+                // layouts don't work for them, so we don't want to return them since it's likely
+                // to result in a poor user experience.
+                if (layout.getVendorId() == identifier.getVendorId()
+                        && layout.getProductId() == identifier.getProductId()) {
+                    if (!mDeviceSpecificLayoutAvailable) {
+                        mDeviceSpecificLayoutAvailable = true;
+                        potentialLayouts.clear();
+                    }
+                    potentialLayouts.add(layout);
+                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
+                        && !mDeviceSpecificLayoutAvailable && isLayoutCompatibleWithLanguageTag(
+                        layout, imeLanguageTag)) {
+                    potentialLayouts.add(layout);
+                } else if (layout.getDescriptor().equals(userSelectedLayout)) {
+                    potentialLayouts.add(layout);
+                }
+            }
+        });
+        // Sort the Keyboard layouts. This is done first by priority then by label. So, system
+        // layouts will come above 3rd party layouts.
+        Collections.sort(potentialLayouts);
+        return potentialLayouts.toArray(new KeyboardLayout[0]);
+    }
+
+    public void onInputMethodSubtypeChanged(@UserIdInt int userId,
+            @Nullable InputMethodSubtypeHandle subtypeHandle,
+            @Nullable InputMethodSubtype subtype) {
+        if (!useNewSettingsUi()) {
+            Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported");
+            return;
+        }
+        if (subtypeHandle == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "No InputMethod is running, ignoring change");
+            }
+            return;
+        }
+        if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
+                || mCurrentImeInfo.mUserId != userId) {
+            mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
+            mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+            if (DEBUG) {
+                Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
+                        + " subtypeHandle=" + subtypeHandle);
+            }
+        }
+    }
+
+    @Nullable
+    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
+            ImeInfo imeInfo) {
+        InputDevice inputDevice = getInputDevice(identifier);
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return null;
+        }
+        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
+        String layout;
+        synchronized (mDataStore) {
+            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
+        }
+        if (layout == null) {
+            synchronized (mKeyboardLayoutCache) {
+                // Check Auto-selected layout cache to see if layout had been previously selected
+                if (mKeyboardLayoutCache.containsKey(key)) {
+                    layout = mKeyboardLayoutCache.get(key);
+                } else {
+                    // NOTE: This list is already filtered based on IME Script code
+                    KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
+                            identifier, imeInfo);
+                    // Call auto-matching algorithm to find the best matching layout
+                    layout = getDefaultKeyboardLayoutBasedOnImeInfo(inputDevice, imeInfo,
+                            layoutList);
+                    mKeyboardLayoutCache.put(key, layout);
+                }
+            }
+        }
+        return layout;
+    }
+
+    @Nullable
+    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
+            ImeInfo imeInfo, KeyboardLayout[] layoutList) {
+        if (imeInfo.mImeSubtypeHandle == null) {
+            return null;
+        }
+
+        Arrays.sort(layoutList);
+
+        // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
+        for (KeyboardLayout layout : layoutList) {
+            if (layout.getVendorId() == inputDevice.getVendorId()
+                    && layout.getProductId() == inputDevice.getProductId()) {
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "getDefaultKeyboardLayoutBasedOnImeInfo() : Layout found based on "
+                                    + "vendor and product Ids. " + inputDevice.getIdentifier()
+                                    + " : " + layout.getDescriptor());
+                }
+                return layout.getDescriptor();
+            }
+        }
+
+        // Check layout type, language tag information from InputDevice for matching
+        String inputLanguageTag = inputDevice.getKeyboardLanguageTag();
+        if (inputLanguageTag != null) {
+            String layoutDesc = getMatchingLayoutForProvidedLanguageTagAndLayoutType(layoutList,
+                    inputLanguageTag, inputDevice.getKeyboardLayoutType());
+
+            if (layoutDesc != null) {
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "getDefaultKeyboardLayoutBasedOnImeInfo() : Layout found based on "
+                                    + "HW information (Language tag and Layout type). "
+                                    + inputDevice.getIdentifier() + " : " + layoutDesc);
+                }
+                return layoutDesc;
+            }
+        }
+
+        InputMethodSubtype subtype = imeInfo.mImeSubtype;
+        // Can't auto select layout based on IME if subtype or language tag is null
+        if (subtype == null) {
+            return null;
+        }
+
+        // Check layout type, language tag information from IME for matching
+        ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
+        String pkLanguageTag =
+                pkLocale != null ? pkLocale.toLanguageTag() : subtype.getCanonicalizedLanguageTag();
+        String layoutDesc = getMatchingLayoutForProvidedLanguageTagAndLayoutType(layoutList,
+                pkLanguageTag, subtype.getPhysicalKeyboardHintLayoutType());
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "getDefaultKeyboardLayoutBasedOnImeInfo() : Layout found based on "
+                            + "IME locale matching. " + inputDevice.getIdentifier() + " : "
+                            + layoutDesc);
+        }
+        return layoutDesc;
+    }
+
+    @Nullable
+    private static String getMatchingLayoutForProvidedLanguageTagAndLayoutType(
+            KeyboardLayout[] layoutList, @NonNull String languageTag, @Nullable String layoutType) {
+        if (layoutType == null || !KeyboardLayout.isLayoutTypeValid(layoutType)) {
+            layoutType = KeyboardLayout.LAYOUT_TYPE_UNDEFINED;
+        }
+        List<KeyboardLayout> layoutsFilteredByLayoutType = new ArrayList<>();
+        for (KeyboardLayout layout : layoutList) {
+            if (layout.getLayoutType().equals(layoutType)) {
+                layoutsFilteredByLayoutType.add(layout);
+            }
+        }
+        String layoutDesc = getMatchingLayoutForProvidedLanguageTag(layoutsFilteredByLayoutType,
+                languageTag);
+        if (layoutDesc != null) {
+            return layoutDesc;
+        }
+
+        return getMatchingLayoutForProvidedLanguageTag(Arrays.asList(layoutList), languageTag);
+    }
+
+    @Nullable
+    private static String getMatchingLayoutForProvidedLanguageTag(List<KeyboardLayout> layoutList,
+            @NonNull String languageTag) {
+        Locale locale = Locale.forLanguageTag(languageTag);
+        String layoutMatchingLanguage = null;
+        String layoutMatchingLanguageAndCountry = null;
+
+        for (KeyboardLayout layout : layoutList) {
+            final LocaleList locales = layout.getLocales();
+            for (int i = 0; i < locales.size(); i++) {
+                final Locale l = locales.get(i);
+                if (l == null) {
+                    continue;
+                }
+                if (l.getLanguage().equals(locale.getLanguage())) {
+                    if (layoutMatchingLanguage == null) {
+                        layoutMatchingLanguage = layout.getDescriptor();
+                    }
+                    if (l.getCountry().equals(locale.getCountry())) {
+                        if (layoutMatchingLanguageAndCountry == null) {
+                            layoutMatchingLanguageAndCountry = layout.getDescriptor();
+                        }
+                        if (l.getVariant().equals(locale.getVariant())) {
+                            return layout.getDescriptor();
+                        }
+                    }
+                }
+            }
+        }
+        return layoutMatchingLanguageAndCountry != null
+                    ? layoutMatchingLanguageAndCountry : layoutMatchingLanguage;
+    }
+
     private void reloadKeyboardLayouts() {
         if (DEBUG) {
             Slog.d(TAG, "Reloading keyboard layouts.");
@@ -734,11 +1058,65 @@
         }
     }
 
+    private boolean useNewSettingsUi() {
+        return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
+    }
+
+    @Nullable
     private InputDevice getInputDevice(int deviceId) {
         InputManager inputManager = mContext.getSystemService(InputManager.class);
         return inputManager != null ? inputManager.getInputDevice(deviceId) : null;
     }
 
+    @Nullable
+    private InputDevice getInputDevice(InputDeviceIdentifier identifier) {
+        InputManager inputManager = mContext.getSystemService(InputManager.class);
+        return inputManager != null ? inputManager.getInputDeviceByDescriptor(
+                identifier.getDescriptor()) : null;
+    }
+
+    private static String createLayoutKey(InputDeviceIdentifier identifier, int userId,
+            @NonNull InputMethodSubtypeHandle subtypeHandle) {
+        Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
+        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
+                + ",subtypeHandle:" + subtypeHandle.toStringHandle();
+    }
+
+    private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
+            @NonNull String languageTag) {
+        final int[] scriptsFromLanguageTag = UScript.getCode(Locale.forLanguageTag(languageTag));
+        if (scriptsFromLanguageTag.length == 0) {
+            // If no scripts inferred from languageTag then allowing the layout
+            return true;
+        }
+        LocaleList locales = layout.getLocales();
+        if (locales.isEmpty()) {
+            // KCM file doesn't have an associated language tag. This can be from
+            // a 3rd party app so need to include it as a potential layout.
+            return true;
+        }
+        for (int i = 0; i < locales.size(); i++) {
+            final Locale locale = locales.get(i);
+            if (locale == null) {
+                continue;
+            }
+            int[] scripts = UScript.getCode(locale);
+            if (scripts != null && haveCommonValue(scripts, scriptsFromLanguageTag)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean haveCommonValue(int[] arr1, int[] arr2) {
+        for (int a1 : arr1) {
+            for (int a2 : arr2) {
+                if (a1 == a2) return true;
+            }
+        }
+        return false;
+    }
+
     private static final class KeyboardLayoutDescriptor {
         public String packageName;
         public String receiverName;
@@ -767,6 +1145,19 @@
         }
     }
 
+    private static class ImeInfo {
+        @UserIdInt int mUserId;
+        @NonNull InputMethodSubtypeHandle mImeSubtypeHandle;
+        @Nullable InputMethodSubtype mImeSubtype;
+
+        ImeInfo(@UserIdInt int userId, @NonNull InputMethodSubtypeHandle imeSubtypeHandle,
+                @Nullable InputMethodSubtype imeSubtype) {
+            mUserId = userId;
+            mImeSubtypeHandle = imeSubtypeHandle;
+            mImeSubtype = imeSubtype;
+        }
+    }
+
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 375377a7..a2b18362 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.hardware.input.TouchCalibration;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseIntArray;
@@ -42,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
@@ -121,6 +123,7 @@
         return false;
     }
 
+    @Nullable
     public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         return state != null ? state.getCurrentKeyboardLayout() : null;
@@ -136,6 +139,22 @@
         return false;
     }
 
+    @Nullable
+    public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
+        return state != null ? state.getKeyboardLayout(key) : null;
+    }
+
+    public boolean setKeyboardLayout(String inputDeviceDescriptor, String key,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
+        if (state.setKeyboardLayout(key, keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
@@ -387,6 +406,8 @@
         private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
         private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
 
+        private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
+
         public TouchCalibration getTouchCalibration(int surfaceRotation) {
             try {
                 return mTouchCalibration[surfaceRotation];
@@ -410,6 +431,15 @@
         }
 
         @Nullable
+        public String getKeyboardLayout(String key) {
+            return mKeyboardLayoutMap.get(key);
+        }
+
+        public boolean setKeyboardLayout(String key, String keyboardLayout) {
+            return !Objects.equals(mKeyboardLayoutMap.put(key, keyboardLayout), keyboardLayout);
+        }
+
+        @Nullable
         public String getCurrentKeyboardLayout() {
             return mCurrentKeyboardLayout;
         }
@@ -507,6 +537,18 @@
                     changed = true;
                 }
             }
+            List<String> removedEntries = new ArrayList<>();
+            for (String key : mKeyboardLayoutMap.keySet()) {
+                if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
+                    removedEntries.add(key);
+                }
+            }
+            if (!removedEntries.isEmpty()) {
+                for (String key : removedEntries) {
+                    mKeyboardLayoutMap.remove(key);
+                }
+                changed = true;
+            }
             return changed;
         }
 
@@ -534,6 +576,18 @@
                         }
                         mCurrentKeyboardLayout = descriptor;
                     }
+                } else if (parser.getName().equals("keyed-keyboard-layout")) {
+                    String key = parser.getAttributeValue(null, "key");
+                    if (key == null) {
+                        throw new XmlPullParserException(
+                                "Missing key attribute on keyed-keyboard-layout.");
+                    }
+                    String layout = parser.getAttributeValue(null, "layout");
+                    if (layout == null) {
+                        throw new XmlPullParserException(
+                                "Missing layout attribute on keyed-keyboard-layout.");
+                    }
+                    mKeyboardLayoutMap.put(key, layout);
                 } else if (parser.getName().equals("light-info")) {
                     int lightId = parser.getAttributeInt(null, "light-id");
                     int lightBrightness = parser.getAttributeInt(null, "light-brightness");
@@ -607,6 +661,13 @@
                 serializer.endTag(null, "keyboard-layout");
             }
 
+            for (String key : mKeyboardLayoutMap.keySet()) {
+                serializer.startTag(null, "keyed-keyboard-layout");
+                serializer.attribute(null, "key", key);
+                serializer.attribute(null, "layout", mKeyboardLayoutMap.get(key));
+                serializer.endTag(null, "keyed-keyboard-layout");
+            }
+
             for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
                 serializer.startTag(null, "light-info");
                 serializer.attributeInt(null, "light-id", mKeyboardBacklightBrightnessMap.keyAt(i));
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index c90554d..8182fe9 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -70,11 +70,13 @@
     private final BluetoothAdapter mBluetoothAdapter;
     private final BluetoothRoutesUpdatedListener mListener;
     private final AudioManager mAudioManager;
-    private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
-    private final IntentFilter mIntentFilter = new IntentFilter();
-    private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
     private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
 
+    private final AdapterStateChangedReceiver mAdapterStateChangedReceiver =
+            new AdapterStateChangedReceiver();
+    private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
+            new DeviceStateChangedReceiver();
+
     private BluetoothA2dp mA2dpProfile;
     private BluetoothHearingAid mHearingAidProfile;
     private BluetoothLeAudio mLeAudioProfile;
@@ -105,32 +107,45 @@
         buildBluetoothRoutes();
     }
 
+    /**
+     * Registers listener to bluetooth status changes as the provided user.
+     *
+     * The registered receiver listens to {@link BluetoothA2dp#ACTION_ACTIVE_DEVICE_CHANGED} and
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED } events for {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#HEARING_AID}, and {@link BluetoothProfile#LE_AUDIO} bluetooth profiles.
+     *
+     * @param user {@code UserHandle} as which receiver is registered
+     */
     void start(UserHandle user) {
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
         mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.LE_AUDIO);
 
-        // Bluetooth on/off broadcasts
-        addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+        IntentFilter adapterStateChangedIntentFilter = new IntentFilter();
 
-        DeviceStateChangedReceiver deviceStateChangedReceiver = new DeviceStateChangedReceiver();
-        addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
-        addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
-        addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
-                deviceStateChangedReceiver);
-        addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
-                deviceStateChangedReceiver);
-        addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED,
-                deviceStateChangedReceiver);
-        addEventReceiver(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED,
-                deviceStateChangedReceiver);
+        adapterStateChangedIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        mContext.registerReceiverAsUser(mAdapterStateChangedReceiver, user,
+                adapterStateChangedIntentFilter, null, null);
 
-        mContext.registerReceiverAsUser(mBroadcastReceiver, user,
-                mIntentFilter, null, null);
+        IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
+
+        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
+        deviceStateChangedIntentFilter.addAction(
+                BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+
+        mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
+                deviceStateChangedIntentFilter, null, null);
     }
 
     void stop() {
-        mContext.unregisterReceiver(mBroadcastReceiver);
+        mContext.unregisterReceiver(mAdapterStateChangedReceiver);
+        mContext.unregisterReceiver(mDeviceStateChangedReceiver);
     }
 
     /**
@@ -167,11 +182,6 @@
         }
     }
 
-    private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
-        mEventReceiverMap.put(action, eventReceiver);
-        mIntentFilter.addAction(action);
-    }
-
     private void buildBluetoothRoutes() {
         mBluetoothRoutes.clear();
         Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
@@ -495,26 +505,9 @@
         }
     }
 
-    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+    private class AdapterStateChangedReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
-
-            BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
-            if (receiver != null) {
-                receiver.onReceive(context, intent, device);
-            }
-        }
-    }
-
-    private interface BluetoothEventReceiver {
-        void onReceive(Context context, Intent intent, BluetoothDevice device);
-    }
-
-    private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
             if (state == BluetoothAdapter.STATE_OFF
                     || state == BluetoothAdapter.STATE_TURNING_OFF) {
@@ -529,9 +522,12 @@
         }
     }
 
-    private class DeviceStateChangedReceiver implements BluetoothEventReceiver {
+    private class DeviceStateChangedReceiver extends BroadcastReceiver {
         @Override
-        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+        public void onReceive(Context context, Intent intent) {
+            BluetoothDevice device = intent.getParcelableExtra(
+                    BluetoothDevice.EXTRA_DEVICE, android.bluetooth.BluetoothDevice.class);
+
             switch (intent.getAction()) {
                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                     clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 60dbbdd..3fcb08a 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -710,6 +710,11 @@
                 if (gnssChargeUC != EnergyConsumerSnapshot.UNAVAILABLE) {
                     mStats.updateGnssEnergyConsumerStatsLocked(gnssChargeUC, elapsedRealtime);
                 }
+
+                final long cameraChargeUC = energyConsumerDeltas.cameraChargeUC;
+                if (cameraChargeUC != EnergyConsumerSnapshot.UNAVAILABLE) {
+                    mStats.updateCameraEnergyConsumerStatsLocked(cameraChargeUC, elapsedRealtime);
+                }
             }
             // Inform mStats about each applicable custom energy bucket.
             if (energyConsumerDeltas != null
@@ -904,6 +909,9 @@
                 case EnergyConsumerType.WIFI:
                     buckets[EnergyConsumerStats.POWER_BUCKET_WIFI] = true;
                     break;
+                case EnergyConsumerType.CAMERA:
+                    buckets[EnergyConsumerStats.POWER_BUCKET_CAMERA] = true;
+                    break;
             }
         }
         return buckets;
@@ -955,6 +963,9 @@
         if ((flags & UPDATE_WIFI) != 0) {
             addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.WIFI);
         }
+        if ((flags & UPDATE_CAMERA) != 0) {
+            addEnergyConsumerIdLocked(energyConsumerIds, EnergyConsumerType.CAMERA);
+        }
 
         if (energyConsumerIds.size() == 0) {
             return null;
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index c559436..d622fd7 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -178,7 +178,7 @@
     // TODO: remove "tcp" from network methods, since we measure total stats.
 
     // Current on-disk Parcel version. Must be updated when the format of the parcelable changes
-    public static final int VERSION = 210;
+    public static final int VERSION = 211;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -649,10 +649,12 @@
         int UPDATE_BT = 0x08;
         int UPDATE_RPM = 0x10;
         int UPDATE_DISPLAY = 0x20;
-        int RESET = 0x40;
+        int UPDATE_CAMERA = 0x40;
+        int RESET = 0x80;
 
         int UPDATE_ALL =
-                UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY;
+                UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY
+                        | UPDATE_CAMERA;
 
         int UPDATE_ON_PROC_STATE_CHANGE = UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
 
@@ -665,6 +667,7 @@
                 UPDATE_BT,
                 UPDATE_RPM,
                 UPDATE_DISPLAY,
+                UPDATE_CAMERA,
                 UPDATE_ALL,
         })
         @Retention(RetentionPolicy.SOURCE)
@@ -6244,6 +6247,8 @@
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteCameraTurnedOnLocked(elapsedRealtimeMs);
+
+        scheduleSyncExternalStatsLocked("camera-on", ExternalStatsSync.UPDATE_CAMERA);
     }
 
     @GuardedBy("this")
@@ -6259,6 +6264,8 @@
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteCameraTurnedOffLocked(elapsedRealtimeMs);
+
+        scheduleSyncExternalStatsLocked("camera-off", ExternalStatsSync.UPDATE_CAMERA);
     }
 
     @GuardedBy("this")
@@ -6273,6 +6280,8 @@
                 uid.noteResetCameraLocked(elapsedRealtimeMs);
             }
         }
+
+        scheduleSyncExternalStatsLocked("camera-reset", ExternalStatsSync.UPDATE_CAMERA);
     }
 
     @GuardedBy("this")
@@ -7414,6 +7423,12 @@
         return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_WIFI);
     }
 
+    @GuardedBy("this")
+    @Override
+    public long getCameraEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CAMERA);
+    }
+
     /**
      * Returns the consumption (in microcoulombs) that the given standard power bucket consumed.
      * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable
@@ -8427,6 +8442,12 @@
                     processState);
         }
 
+        @GuardedBy("mBsi")
+        @Override
+        public long getCameraEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CAMERA);
+        }
+
         /**
          * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time
          * since last marked. Also sets the mark time for both these timers.
@@ -8477,6 +8498,20 @@
             return gnssTimeUs;
         }
 
+        /**
+         * Gets the uid's time spent using the camera since last marked. Also sets the mark time for
+         * the camera timer.
+         */
+        private long markCameraTimeUs(long elapsedRealtimeMs) {
+            final StopwatchTimer timer = mCameraTurnedOnTimer;
+            if (timer == null) {
+                return 0;
+            }
+            final long cameraTimeUs = timer.getTimeSinceMarkLocked(elapsedRealtimeMs * 1000);
+            timer.setMark(elapsedRealtimeMs);
+            return cameraTimeUs;
+        }
+
         public StopwatchTimer createAudioTurnedOnTimerLocked() {
             if (mAudioTurnedOnTimer == null) {
                 mAudioTurnedOnTimer = new StopwatchTimer(mBsi.mClock, Uid.this, AUDIO_TURNED_ON,
@@ -12902,6 +12937,53 @@
     }
 
     /**
+     * Accumulate camera charge consumption and distribute it to the correct state and the apps.
+     *
+     * @param chargeUC amount of charge (microcoulombs) used by the camera since this was last
+     *         called.
+     */
+    @GuardedBy("this")
+    public void updateCameraEnergyConsumerStatsLocked(long chargeUC, long elapsedRealtimeMs) {
+        if (DEBUG_ENERGY) Slog.d(TAG, "Updating camera stats: " + chargeUC);
+        if (mGlobalEnergyConsumerStats == null) {
+            return;
+        }
+
+        if (!mOnBatteryInternal || chargeUC <= 0) {
+            // There's nothing further to update.
+            return;
+        }
+
+        if (mIgnoreNextExternalStats) {
+            // Although under ordinary resets we won't get here, and typically a new sync will
+            // happen right after the reset, strictly speaking we need to set all mark times to now.
+            final int uidStatsSize = mUidStats.size();
+            for (int i = 0; i < uidStatsSize; i++) {
+                final Uid uid = mUidStats.valueAt(i);
+                uid.markCameraTimeUs(elapsedRealtimeMs);
+            }
+            return;
+        }
+
+        mGlobalEnergyConsumerStats.updateStandardBucket(
+                EnergyConsumerStats.POWER_BUCKET_CAMERA, chargeUC);
+
+        // Collect the per uid time since mark so that we can normalize power.
+        final SparseDoubleArray cameraTimeUsArray = new SparseDoubleArray();
+
+        // Note: Iterating over all UIDs may be suboptimal.
+        final int uidStatsSize = mUidStats.size();
+        for (int i = 0; i < uidStatsSize; i++) {
+            final Uid uid = mUidStats.valueAt(i);
+            final long cameraTimeUs = uid.markCameraTimeUs(elapsedRealtimeMs);
+            if (cameraTimeUs == 0) continue;
+            cameraTimeUsArray.put(uid.getUid(), (double) cameraTimeUs);
+        }
+        distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_CAMERA, chargeUC,
+                cameraTimeUsArray, 0, elapsedRealtimeMs);
+    }
+
+    /**
      * Accumulate Custom power bucket charge, globally and for each app.
      *
      * @param totalChargeUC charge (microcoulombs) used for this bucket since this was last called.
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java b/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java
index 16892034..89991bf 100644
--- a/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/CameraPowerCalculator.java
@@ -48,27 +48,44 @@
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
 
-        final long durationMs = batteryStats.getCameraOnTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        long consumptionUc = batteryStats.getCameraEnergyConsumptionUC();
+        int powerModel = getPowerModel(consumptionUc, query);
+        long durationMs =
+                batteryStats.getCameraOnTime(
+                        rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        double powerMah;
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            powerMah = uCtoMah(consumptionUc);
+        } else {
+            powerMah = mPowerEstimator.calculatePower(durationMs);
+        }
+
         builder.getAggregateBatteryConsumerBuilder(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
         builder.getAggregateBatteryConsumerBuilder(
-                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
     }
 
     @Override
     protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        final long durationMs =
+        long consumptionUc = app.getBatteryStatsUid().getCameraEnergyConsumptionUC();
+        int powerModel = getPowerModel(consumptionUc, query);
+        long durationMs =
                 mPowerEstimator.calculateDuration(u.getCameraTurnedOnTimer(), rawRealtimeUs,
                         BatteryStats.STATS_SINCE_CHARGED);
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        double powerMah;
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            powerMah = uCtoMah(consumptionUc);
+        } else {
+            powerMah = mPowerEstimator.calculatePower(durationMs);
+        }
+
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah, powerModel);
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
index 18595ca..939a08b 100644
--- a/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
@@ -123,6 +123,9 @@
         /** The chargeUC for {@link EnergyConsumerType#WIFI}. */
         public long wifiChargeUC = UNAVAILABLE;
 
+        /** The chargeUC for {@link EnergyConsumerType#CAMERA}. */
+        public long cameraChargeUC = UNAVAILABLE;
+
         /** Map of {@link EnergyConsumerType#OTHER} ordinals to their total chargeUC. */
         public @Nullable long[] otherTotalChargeUC = null;
 
@@ -256,6 +259,10 @@
                     output.wifiChargeUC = deltaChargeUC;
                     break;
 
+                case EnergyConsumerType.CAMERA:
+                    output.cameraChargeUC = deltaChargeUC;
+                    break;
+
                 case EnergyConsumerType.OTHER:
                     if (output.otherTotalChargeUC == null) {
                         output.otherTotalChargeUC = new long[mNumOtherOrdinals];
@@ -458,6 +465,9 @@
                 case EnergyConsumerType.WIFI:
                     chargeUC[i] = delta.wifiChargeUC;
                     break;
+                case EnergyConsumerType.CAMERA:
+                    chargeUC[i] = delta.cameraChargeUC;
+                    break;
                 case EnergyConsumerType.OTHER:
                     if (delta.otherTotalChargeUC != null) {
                         chargeUC[i] = delta.otherTotalChargeUC[energyConsumer.ordinal];
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 50b7fd2..d4f2f2d 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1336,6 +1336,31 @@
         }
 
         @Override
+        public void sendCurrentVideoBounds(IBinder sessionToken, Rect bounds, int userId) {
+            if (DEBUG) {
+                Slogf.d(TAG, "sendCurrentVideoBounds(bounds=%s)", bounds.toString());
+            }
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "sendCurrentVideoBounds");
+            SessionState sessionState = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).sendCurrentVideoBounds(bounds);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in sendCurrentVideoBounds", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void sendCurrentChannelUri(IBinder sessionToken, Uri channelUri, int userId) {
             if (DEBUG) {
                 Slogf.d(TAG, "sendCurrentChannelUri(channelUri=%s)", channelUri.toString());
@@ -2475,6 +2500,23 @@
         }
 
         @Override
+        public void onRequestCurrentVideoBounds() {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRequestCurrentVideoBounds");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onRequestCurrentVideoBounds(mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onRequestCurrentVideoBounds", e);
+                }
+            }
+        }
+
+        @Override
         public void onRequestCurrentChannelUri() {
             synchronized (mLock) {
                 if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8c58e15..6edfebf 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2852,8 +2852,8 @@
         setWallpaperComponent(name, UserHandle.getCallingUserId(), FLAG_SYSTEM);
     }
 
-    private void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which,
-            int userId) {
+    @VisibleForTesting
+    void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which, int userId) {
         userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
                 false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 52ed3bc..c08f6bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -71,7 +71,6 @@
 import android.util.Xml;
 import android.view.Display;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -89,6 +88,7 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -110,7 +110,6 @@
  * atest FrameworksMockingServicesTests:WallpaperManagerServiceTests
  */
 @Presubmit
-@FlakyTest(bugId = 129797242)
 @RunWith(AndroidJUnit4.class)
 public class WallpaperManagerServiceTests {
 
@@ -273,8 +272,10 @@
 
     /**
      * Tests setWallpaperComponent and clearWallpaper should work as expected.
+     * TODO ignored since the assumption never passes. to be investigated.
      */
     @Test
+    @Ignore("b/264533465")
     public void testSetThenClearComponent() {
         // Skip if there is no pre-defined default wallpaper component.
         assumeThat(sDefaultWallpaperComponent,
@@ -285,7 +286,7 @@
         verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
         verifyCurrentSystemData(testUserId);
 
-        mService.setWallpaperComponent(sImageWallpaperComponentName);
+        mService.setWallpaperComponent(sImageWallpaperComponentName, FLAG_SYSTEM, testUserId);
         verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
         verifyCurrentSystemData(testUserId);
 
@@ -312,7 +313,7 @@
 
         WallpaperManagerService.WallpaperConnection.DisplayConnector connector =
                 mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
-        mService.setWallpaperComponent(sDefaultWallpaperComponent);
+        mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
 
         verify(connector.mEngine).dispatchWallpaperCommand(
                 eq(COMMAND_REAPPLY), anyInt(), anyInt(), anyInt(), any());
@@ -454,7 +455,7 @@
     public void testGetAdjustedWallpaperColorsOnDimming() throws RemoteException {
         final int testUserId = USER_SYSTEM;
         mService.switchUser(testUserId, null);
-        mService.setWallpaperComponent(sDefaultWallpaperComponent);
+        mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
         WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId);
 
         // Mock a wallpaper data with color hints that support dark text and dark theme
diff --git a/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm b/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm
new file mode 100644
index 0000000..ea6bc98
--- /dev/null
+++ b/services/tests/servicestests/res/raw/dummy_keyboard_layout.kcm
@@ -0,0 +1,311 @@
+# Copyright (C) 2023 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.
+
+#
+# English (US) keyboard layout.
+# Unlike the default (generic) keyboard layout, English (US) does not contain any
+# special ALT characters.
+#
+
+type OVERLAY
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '@'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '#'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              '$'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              '^'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key W {
+    label:                              'W'
+    base:                               'w'
+    shift, capslock:                    'W'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+}
+
+key I {
+    label:                              'I'
+    base:                               'i'
+    shift, capslock:                    'I'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    base:                               '['
+    shift:                              '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    base:                               ']'
+    shift:                              '}'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    base:                               ';'
+    shift:                              ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    base:                               '\''
+    shift:                              '"'
+}
+
+### ROW 4
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key COMMA {
+    label:                              ','
+    base:                               ','
+    shift:                              '<'
+}
+
+key PERIOD {
+    label:                              '.'
+    base:                               '.'
+    shift:                              '>'
+}
+
+key SLASH {
+    label:                              '/'
+    base:                               '/'
+    shift:                              '?'
+}
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/services/tests/servicestests/res/xml/keyboard_layouts.xml
new file mode 100644
index 0000000..b5a05fc
--- /dev/null
+++ b/services/tests/servicestests/res/xml/keyboard_layouts.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 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.
+  -->
+
+<keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <keyboard-layout
+        android:name="keyboard_layout_english_uk"
+        android:label="English (UK)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn-GB"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_us"
+        android:label="English (US)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn,en-Latn-US"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_intl"
+        android:label="English (International)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="extended" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_dvorak"
+        android:label="English (Dvorak)"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="dvorak" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_german"
+        android:label="German"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="de-Latn"
+        android:keyboardLayoutType="qwertz" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_french"
+        android:label="French"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="fr-Latn-FR"
+        android:keyboardLayoutType="azerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_russian_qwerty"
+        android:label="Russian"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="ru-Cyrl"
+        android:keyboardLayoutType="qwerty" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_russian"
+        android:label="Russian"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        android:keyboardLocale="ru-Cyrl" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_vendorId:1,productId:1"
+        android:label="vendorId:1,productId:1"
+        android:keyboardLayout="@raw/dummy_keyboard_layout"
+        androidprv:vendorId="1"
+        androidprv:productId="1" />
+</keyboard-layouts>
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
new file mode 100644
index 0000000..34540c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2023 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.hardware.input.KeyboardLayout
+import android.icu.lang.UScript
+import android.icu.util.ULocale
+import android.os.Bundle
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.view.InputDevice
+import android.view.inputmethod.InputMethodInfo
+import android.view.inputmethod.InputMethodSubtype
+import androidx.test.core.R
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.util.Locale
+
+private fun createKeyboard(
+    deviceId: Int,
+    vendorId: Int,
+    productId: Int,
+    languageTag: String,
+    layoutType: String
+): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .setVendorId(vendorId)
+        .setProductId(productId)
+        .setKeyboardLanguageTag(languageTag)
+        .setKeyboardLayoutType(layoutType)
+        .build()
+
+/**
+ * Tests for {@link Default UI} and {@link New UI}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:KeyboardLayoutManagerTests
+ */
+@Presubmit
+class KeyboardLayoutManagerTests {
+    companion object {
+        const val DEVICE_ID = 1
+        const val VENDOR_SPECIFIC_DEVICE_ID = 2
+        const val ENGLISH_DVORAK_DEVICE_ID = 3
+        const val USER_ID = 4
+        const val IME_ID = "ime_id"
+        const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
+        const val RECEIVER_NAME = "DummyReceiver"
+        private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us"
+        private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk"
+        private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1"
+    }
+
+    private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME)
+    private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME)
+    private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR =
+        createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME)
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+
+    @Mock
+    private lateinit var native: NativeInputManagerService
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+    private lateinit var keyboardLayoutManager: KeyboardLayoutManager
+
+    private lateinit var imeInfo: InputMethodInfo
+    private var nextImeSubtypeId = 0
+    private lateinit var context: Context
+    private lateinit var dataStore: PersistentDataStore
+    private lateinit var testLooper: TestLooper
+
+    // Devices
+    private lateinit var keyboardDevice: InputDevice
+    private lateinit var vendorSpecificKeyboardDevice: InputDevice
+    private lateinit var englishDvorakKeyboardDevice: InputDevice
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+            override fun openRead(): InputStream? {
+                throw FileNotFoundException()
+            }
+
+            override fun startWrite(): FileOutputStream? {
+                throw IOException()
+            }
+
+            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+        })
+        testLooper = TestLooper()
+        keyboardLayoutManager = KeyboardLayoutManager(context, native, dataStore, testLooper.looper)
+        setupInputDevices()
+        setupBroadcastReceiver()
+        setupIme()
+    }
+
+    private fun setupInputDevices() {
+        val inputManager = InputManager.resetInstance(iInputManager)
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+            .thenReturn(inputManager)
+
+        keyboardDevice = createKeyboard(DEVICE_ID, 0, 0, "", "")
+        vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
+        englishDvorakKeyboardDevice =
+            createKeyboard(ENGLISH_DVORAK_DEVICE_ID, 0, 0, "en", "dvorak")
+        Mockito.`when`(iInputManager.inputDeviceIds)
+            .thenReturn(intArrayOf(DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID))
+        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID))
+            .thenReturn(vendorSpecificKeyboardDevice)
+        Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID))
+            .thenReturn(englishDvorakKeyboardDevice)
+    }
+
+    private fun setupBroadcastReceiver() {
+        Mockito.`when`(context.packageManager).thenReturn(packageManager)
+
+        val info = createMockReceiver()
+        Mockito.`when`(packageManager.queryBroadcastReceivers(Mockito.any(), Mockito.anyInt()))
+            .thenReturn(listOf(info))
+        Mockito.`when`(packageManager.getReceiverInfo(Mockito.any(), Mockito.anyInt()))
+            .thenReturn(info.activityInfo)
+
+        val resources = context.resources
+        Mockito.`when`(
+            packageManager.getResourcesForApplication(
+                Mockito.any(
+                    ApplicationInfo::class.java
+                )
+            )
+        ).thenReturn(resources)
+    }
+
+    private fun setupIme() {
+        imeInfo = InputMethodInfo(PACKAGE_NAME, RECEIVER_NAME, "", "", 0)
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayouts() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                "Default UI: Keyboard layout API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "Default UI: Keyboard layout API should provide English(US) layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayouts() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+            assertNotEquals(
+                "New UI: Keyboard layout API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "New UI: Keyboard layout API should provide English(US) layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
+            assertNotEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
+                0,
+                keyboardLayouts.size
+            )
+            assertTrue(
+                "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
+                        "layout",
+                hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+            )
+
+            val vendorSpecificKeyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
+                    vendorSpecificKeyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
+                        "specific layout",
+                1,
+                vendorSpecificKeyboardLayouts.size
+            )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
+                        "layout",
+                VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
+                vendorSpecificKeyboardLayouts[0].descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
+            assertEquals(
+                "New UI: getKeyboardLayoutsForInputDevice API should always return empty array",
+                0,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            assertNull(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
+                        "nothing was set",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            val keyboardLayout =
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
+                        "layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertNull(
+                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
+                        "even after setCurrentKeyboardLayoutForInputDevice",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+
+            val keyboardLayouts =
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                )
+            assertEquals(
+                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
+                        "layout",
+                1,
+                keyboardLayouts.size
+            )
+            assertEquals(
+                "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
+                        "English(US) layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayouts[0]
+            )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                        "English(US) layout (Auto select the first enabled layout)",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
+                0,
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                ).size
+            )
+            assertNull(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
+                        "the enabled layout is removed",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+
+            assertEquals(
+                "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
+                        "an empty array",
+                0,
+                keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
+                    keyboardDevice.identifier
+                ).size
+            )
+            assertNull(
+                "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_switchKeyboardLayout() {
+        NewSettingsApiFlag(false).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                        "English(US) layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+
+            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
+
+            // Throws null pointer because trying to show toast using TestLooper
+            assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
+            assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
+                    "English(UK) layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_switchKeyboardLayout() {
+        NewSettingsApiFlag(true).use {
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayoutManager.addKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+
+            keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
+            testLooper.dispatchAll()
+
+            assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
+                    "null",
+                keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayout() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+            assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
+                    "available layouts",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout!!.descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayout() {
+        NewSettingsApiFlag(true).use {
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+            assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
+                    "available layouts",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayout!!.descriptor
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
+        NewSettingsApiFlag(false).use {
+            val imeSubtype = createImeSubtype()
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            val keyboardLayout =
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            assertNull(
+                "Default UI: getKeyboardLayoutForInputDevice API should always return null",
+                keyboardLayout
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
+        NewSettingsApiFlag(true).use {
+            val imeSubtype = createImeSubtype()
+
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            )
+
+            // This should replace previously set layout
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
+        NewSettingsApiFlag(false).use {
+            val keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtype()
+                )
+            assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
+                    "return empty array",
+                0,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getKeyboardLayoutListForInputDevice() {
+        NewSettingsApiFlag(true).use {
+            // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+            var keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("hi-Latn")
+                )
+            assertNotEquals(
+                "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+                        "supported layouts with matching script code",
+                0,
+                keyboardLayouts.size
+            )
+
+            val englishScripts = UScript.getCode(Locale.forLanguageTag("hi-Latn"))
+            for (kl in keyboardLayouts) {
+                var isCompatible = false
+                for (i in 0 until kl.locales.size()) {
+                    val locale: Locale = kl.locales.get(i) ?: continue
+                    val scripts = UScript.getCode(locale)
+                    if (scripts != null && areScriptsCompatible(scripts, englishScripts)) {
+                        isCompatible = true
+                        break
+                    }
+                }
+                assertTrue(
+                    "New UI: getKeyboardLayoutListForInputDevice API should only return " +
+                            "compatible layouts but found " + kl.descriptor,
+                    isCompatible
+                )
+            }
+
+            // Check Layouts for "hi" which by default uses 'Deva' script.
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("hi")
+                )
+            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
+                    "list if no supported layouts available",
+                0,
+                keyboardLayouts.size
+            )
+
+            // If user manually selected some layout, always provide it in the layout list
+            val imeSubtype = createImeSubtypeForLanguageTag("hi")
+            keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+                keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            keyboardLayouts =
+                keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    imeSubtype
+                )
+            assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
+                    "selected layout even if the script is incompatible with IME",
+                    1,
+                keyboardLayouts.size
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+        NewSettingsApiFlag(true).use {
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("en-US"),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("en-GB"),
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("de"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("fr-FR"),
+                createLayoutDescriptor("keyboard_layout_french")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTag("ru"),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+            assertNull(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
+                        "layout available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("it")
+                )
+            )
+            assertNull(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
+                        "layout for script code is available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTag("en-Deva")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+        NewSettingsApiFlag(true).use {
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+            // Try to match layout type even if country doesn't match
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+            // Choose layout based on layout type priority, if layout type is not provided by IME
+            // (Qwerty > Dvorak > Extended)
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+                ENGLISH_US_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+                ENGLISH_UK_LAYOUT_DESCRIPTOR
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            // Wrong layout type should match with language if provided layout type not available
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_german")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+                createLayoutDescriptor("keyboard_layout_french")
+            )
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_russian_qwerty")
+            )
+            // If layout type is empty then prioritize KCM with empty layout type
+            assertCorrectLayout(
+                keyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+            assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
+                    "no layout for script code is available",
+                keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                    keyboardDevice.identifier, USER_ID, imeInfo,
+                    createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
+                )
+            )
+        }
+    }
+
+    @Test
+    fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+        NewSettingsApiFlag(true).use {
+            // Should return English dvorak even if IME current layout is qwerty, since HW says the
+            // keyboard is a Dvorak keyboard
+            assertCorrectLayout(
+                englishDvorakKeyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("en", "qwerty"),
+                createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+            )
+
+            // Fallback to IME information if the HW provided layout script is incompatible with the
+            // provided IME subtype
+            assertCorrectLayout(
+                englishDvorakKeyboardDevice,
+                createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+                createLayoutDescriptor("keyboard_layout_russian")
+            )
+        }
+    }
+
+    private fun assertCorrectLayout(
+        device: InputDevice,
+        imeSubtype: InputMethodSubtype,
+        expectedLayout: String
+    ) {
+        assertEquals(
+            "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+            expectedLayout,
+            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+                device.identifier, USER_ID, imeInfo, imeSubtype
+            )
+        )
+    }
+
+    private fun createImeSubtype(): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++).build()
+
+    private fun createImeSubtypeForLanguageTag(languageTag: String): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
+            .setLanguageTag(languageTag).build()
+
+    private fun createImeSubtypeForLanguageTagAndLayoutType(
+        languageTag: String,
+        layoutType: String
+    ): InputMethodSubtype =
+        InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(nextImeSubtypeId++)
+            .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+
+    private fun hasLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+        for (kl in layoutList) {
+            if (kl.descriptor == layoutDesc) {
+                return true
+            }
+        }
+        return false
+    }
+
+    private fun createLayoutDescriptor(keyboardName: String): String =
+        "$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
+
+    private fun areScriptsCompatible(scriptList1: IntArray, scriptList2: IntArray): Boolean {
+        for (s1 in scriptList1) {
+            for (s2 in scriptList2) {
+                if (s1 == s2) return true
+            }
+        }
+        return false
+    }
+
+    private fun createMockReceiver(): ResolveInfo {
+        val info = ResolveInfo()
+        info.activityInfo = ActivityInfo()
+        info.activityInfo.packageName = PACKAGE_NAME
+        info.activityInfo.name = RECEIVER_NAME
+        info.activityInfo.applicationInfo = ApplicationInfo()
+        info.activityInfo.metaData = Bundle()
+        info.activityInfo.metaData.putInt(
+            InputManager.META_DATA_KEYBOARD_LAYOUTS,
+            R.xml.keyboard_layouts
+        )
+        info.serviceInfo = ServiceInfo()
+        info.serviceInfo.packageName = PACKAGE_NAME
+        info.serviceInfo.name = RECEIVER_NAME
+        return info
+    }
+
+    private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
+        init {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_ui", enabled.toString()
+            )
+        }
+
+        override fun close() {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_ui",
+                ""
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index 2ebe215..cff4cc7 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_ALL;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_BT;
+import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_CAMERA;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
 import static com.android.server.power.stats.BatteryStatsImpl.ExternalStatsSync.UPDATE_RADIO;
@@ -104,6 +105,11 @@
         tempAllIds.add(gnssId);
         mPowerStatsInternal.incrementEnergyConsumption(gnssId, 787878);
 
+        final int cameraId =
+                mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.CAMERA, 0, "camera");
+        tempAllIds.add(cameraId);
+        mPowerStatsInternal.incrementEnergyConsumption(cameraId, 901234);
+
         final int mobileRadioId = mPowerStatsInternal.addEnergyConsumer(
                 EnergyConsumerType.MOBILE_RADIO, 0, "mobile_radio");
         tempAllIds.add(mobileRadioId);
@@ -171,6 +177,12 @@
         Arrays.sort(receivedCpuIds);
         assertArrayEquals(cpuClusterIds, receivedCpuIds);
 
+        final EnergyConsumerResult[] cameraResults =
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_CAMERA).getNow(null);
+        // Results should only have the camera energy consumer
+        assertEquals(1, cameraResults.length);
+        assertEquals(cameraId, cameraResults[0].id);
+
         final EnergyConsumerResult[] allResults =
                 mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_ALL).getNow(null);
         // All energy consumer results should be available
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
index e4ab21b..5fce32f0 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
@@ -36,41 +36,113 @@
 public class CameraPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
 
-    private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP1_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP2_UID = Process.FIRST_APPLICATION_UID + 43;
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_CAMERA, 360.0);
+            .setAveragePower(PowerProfile.POWER_CAMERA, 360.0)
+            .initMeasuredEnergyStatsLocked();
 
     @Test
     public void testTimerBasedModel() {
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
-        stats.noteCameraOnLocked(APP_UID, 1000, 1000);
-        stats.noteCameraOffLocked(APP_UID, 2000, 2000);
+        synchronized (stats) { // To keep the GuardedBy check happy
+            stats.noteCameraOnLocked(APP1_UID, 1000, 1000);
+            stats.noteCameraOffLocked(APP1_UID, 2000, 2000);
+            stats.noteCameraOnLocked(APP2_UID, 3000, 3000);
+            stats.noteCameraOffLocked(APP2_UID, 5000, 5000);
+        }
+
+        CameraPowerCalculator calculator =
+                new CameraPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        UidBatteryConsumer app1Consumer = mStatsRule.getUidBatteryConsumer(APP1_UID);
+        assertThat(app1Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(1000);
+        assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.1);
+        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
+        assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(2000);
+        assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.2);
+        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(3000);
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.3);
+        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(3000);
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.3);
+        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testEnergyConsumptionBasedModel() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        synchronized (stats) { // To keep the GuardedBy check happy
+            stats.noteCameraOnLocked(APP1_UID, 1000, 1000);
+            stats.noteCameraOffLocked(APP1_UID, 2000, 2000);
+            stats.updateCameraEnergyConsumerStatsLocked(720_000, 2100); // 0.72C == 0.2mAh
+            stats.noteCameraOnLocked(APP2_UID, 3000, 3000);
+            stats.noteCameraOffLocked(APP2_UID, 5000, 5000);
+            stats.updateCameraEnergyConsumerStatsLocked(1_080_000, 5100); // 0.3mAh
+        }
 
         CameraPowerCalculator calculator =
                 new CameraPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(calculator);
 
-        UidBatteryConsumer consumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+        UidBatteryConsumer app1Consumer = mStatsRule.getUidBatteryConsumer(APP1_UID);
+        assertThat(app1Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isEqualTo(1000);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isWithin(PRECISION).of(0.1);
+        assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.2);
+        assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+
+        UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
+        assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(2000);
+        assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.3);
+        assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(1000);
+                .isEqualTo(3000);
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isWithin(PRECISION).of(0.1);
+                .isWithin(PRECISION).of(0.5);
+        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsBatteryConsumer
                 .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isEqualTo(1000);
+                .isEqualTo(3000);
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
-                .isWithin(PRECISION).of(0.1);
+                .isWithin(PRECISION).of(0.5);
+        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
index 558f396..28f4799 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
@@ -248,6 +248,32 @@
         assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0");
     }
 
+    @Test
+    public void testUpdateAndGetDelta_updatesCameraCharge() {
+        EnergyConsumer cameraConsumer =
+                createEnergyConsumer(7, 0, EnergyConsumerType.CAMERA, "CAMERA");
+        final EnergyConsumerSnapshot snapshot =
+                new EnergyConsumerSnapshot(createIdToConsumerMap(cameraConsumer));
+
+        // An initial result with only one energy consumer
+        EnergyConsumerResult[] result0 = new EnergyConsumerResult[]{
+                createEnergyConsumerResult(cameraConsumer.id, 60_000, null, null),
+        };
+        snapshot.updateAndGetDelta(result0, VOLTAGE_1);
+
+        // A subsequent result
+        EnergyConsumerResult[] result1 = new EnergyConsumerResult[]{
+                createEnergyConsumerResult(cameraConsumer.id, 90_000, null, null),
+        };
+        EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(result1, VOLTAGE_1);
+
+        // Verify that the delta between the two results is reported.
+        BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
+        assertThat(details.consumers).hasLength(1);
+        long expectedDeltaUC = calculateChargeConsumedUC(60_000, VOLTAGE_1, 90_000, VOLTAGE_1);
+        assertThat(details.chargeUC[0]).isEqualTo(expectedDeltaUC);
+    }
+
     private static EnergyConsumer createEnergyConsumer(int id, int ord, byte type, String name) {
         final EnergyConsumer ec = new EnergyConsumer();
         ec.id = id;