Merge "Use PackageManagerInternal instead of PackageManager in PlatformCompat."
diff --git a/api/current.txt b/api/current.txt
index d69032d..c86506b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35911,6 +35911,7 @@
     method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor);
     method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException;
     method public boolean isCacheBehaviorTombstone(java.io.File) throws java.io.IOException;
+    method public boolean isCheckpointSupported();
     method public boolean isEncrypted(java.io.File);
     method public boolean isObbMounted(String);
     method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener);
diff --git a/api/system-current.txt b/api/system-current.txt
index f3cdac3..77aff8d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4853,6 +4853,10 @@
     field @Deprecated public static final int WPA2_PSK = 4; // 0x4
   }
 
+  public class WifiFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
   public class WifiInfo implements android.os.Parcelable {
     method @Nullable public String getAppPackageName();
     method public double getRxSuccessRate();
@@ -6230,6 +6234,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
     method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties);
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
     field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
     field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
@@ -6540,6 +6545,7 @@
     field public static final String SERIAL_NUMBER = "serial_number";
     field public static final String SERVICE_CATEGORY = "service_category";
     field public static final String SLOT_INDEX = "slot_index";
+    field public static final String SUB_ID = "sub_id";
   }
 
   public static final class Telephony.Sms.Intents {
@@ -8599,7 +8605,7 @@
   }
 
   public final class SmsCbMessage implements android.os.Parcelable {
-    ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo, int, @Nullable java.util.List<android.telephony.CbGeoUtils.Geometry>, long, int);
+    ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo, int, @Nullable java.util.List<android.telephony.CbGeoUtils.Geometry>, long, int, int);
     method @NonNull public static android.telephony.SmsCbMessage createFromCursor(@NonNull android.database.Cursor);
     method public int describeContents();
     method @Nullable public android.telephony.SmsCbCmasInfo getCmasWarningInfo();
@@ -8616,6 +8622,7 @@
     method public int getSerialNumber();
     method public int getServiceCategory();
     method public int getSlotIndex();
+    method public int getSubscriptionId();
     method public boolean isCmasMessage();
     method public boolean isEmergencyMessage();
     method public boolean isEtwsMessage();
diff --git a/api/test-current.txt b/api/test-current.txt
index a8bd9be..693b190 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2526,6 +2526,7 @@
     field public static final String SERIAL_NUMBER = "serial_number";
     field public static final String SERVICE_CATEGORY = "service_category";
     field public static final String SLOT_INDEX = "slot_index";
+    field public static final String SUB_ID = "sub_id";
   }
 
   public static final class Telephony.Sms.Intents {
diff --git a/cmds/svc/src/com/android/commands/svc/Svc.java b/cmds/svc/src/com/android/commands/svc/Svc.java
index 68fb8e6..e602e2a 100644
--- a/cmds/svc/src/com/android/commands/svc/Svc.java
+++ b/cmds/svc/src/com/android/commands/svc/Svc.java
@@ -94,7 +94,7 @@
             COMMAND_HELP,
             new PowerCommand(),
             new DataCommand(),
-            new WifiCommand(),
+            // `svc wifi` has been migrated to WifiShellCommand
             new UsbCommand(),
             new NfcCommand(),
             new BluetoothCommand(),
diff --git a/cmds/svc/src/com/android/commands/svc/WifiCommand.java b/cmds/svc/src/com/android/commands/svc/WifiCommand.java
deleted file mode 100644
index e31cb53..0000000
--- a/cmds/svc/src/com/android/commands/svc/WifiCommand.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.commands.svc;
-
-import android.os.ServiceManager;
-import android.os.RemoteException;
-import android.net.wifi.IWifiManager;
-import android.content.Context;
-
-public class WifiCommand extends Svc.Command {
-    public WifiCommand() {
-        super("wifi");
-    }
-
-    public String shortHelp() {
-        return "Control the Wi-Fi manager";
-    }
-
-    public String longHelp() {
-        return shortHelp() + "\n"
-                + "\n"
-                + "usage: svc wifi [enable|disable]\n"
-                + "         Turn Wi-Fi on or off.\n\n";
-    }
-
-    public void run(String[] args) {
-        boolean validCommand = false;
-        if (args.length >= 2) {
-            boolean flag = false;
-            if ("enable".equals(args[1])) {
-                flag = true;
-                validCommand = true;
-            } else if ("disable".equals(args[1])) {
-                flag = false;
-                validCommand = true;
-            }
-            if (validCommand) {
-                IWifiManager wifiMgr
-                        = IWifiManager.Stub.asInterface(ServiceManager.getService(Context.WIFI_SERVICE));
-                if (wifiMgr == null) {
-                    System.err.println("Wi-Fi service is not ready");
-                    return;
-                }
-                try {
-                    wifiMgr.setWifiEnabled("com.android.shell", flag);
-                }
-                catch (RemoteException e) {
-                    System.err.println("Wi-Fi operation failed: " + e);
-                }
-                return;
-            }
-        }
-        System.err.println(longHelp());
-    }
-}
diff --git a/cmds/svc/svc b/cmds/svc/svc
index c122e98..60c95c7 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -1,3 +1,24 @@
 #!/system/bin/sh
+
+# `svc wifi` has been migrated to WifiShellCommand,
+# simply perform translation to `cmd wifi set-wifi-enabled` here.
+if [ "x$1" == "xwifi" ]; then
+    # `cmd wifi` by convention uses enabled/disabled
+    # instead of enable/disable
+    if [ "x$2" == "xenable" ]; then
+        exec cmd wifi set-wifi-enabled enabled
+    elif [ "x$2" == "xdisable" ]; then
+        exec cmd wifi set-wifi-enabled disabled
+    else
+        echo "Control the Wi-Fi manager"
+        echo ""
+        echo "usage: svc wifi [enable|disable]"
+        echo "         Turn Wi-Fi on or off."
+        echo ""
+    fi
+    exit 1
+fi
+
 export CLASSPATH=/system/framework/svc.jar
 exec app_process /system/bin com.android.commands.svc.Svc "$@"
+
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index afb7871..5032f77 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1213,7 +1213,6 @@
             OP_START_FOREGROUND,
             OP_SMS_FINANCIAL_TRANSACTIONS,
             OP_MANAGE_IPSEC_TUNNELS,
-            OP_GET_USAGE_STATS,
             OP_INSTANT_APP_START_FOREGROUND
     };
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 5effa1b..0bfd2c6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -116,16 +116,7 @@
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
 import android.net.nsd.NsdManager;
-import android.net.wifi.IWifiScanner;
-import android.net.wifi.RttManager;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiScanner;
-import android.net.wifi.aware.IWifiAwareManager;
-import android.net.wifi.aware.WifiAwareManager;
-import android.net.wifi.p2p.IWifiP2pManager;
-import android.net.wifi.p2p.WifiP2pManager;
-import android.net.wifi.rtt.IWifiRttManager;
-import android.net.wifi.rtt.WifiRttManager;
+import android.net.wifi.WifiFrameworkInitializer;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -691,66 +682,6 @@
                         ConnectivityThread.getInstanceLooper());
             }});
 
-        registerService(Context.WIFI_SERVICE, WifiManager.class,
-                new CachedServiceFetcher<WifiManager>() {
-            @Override
-            public WifiManager createService(ContextImpl ctx) {
-                return new WifiManager(ctx.getOuterContext(),
-                        ConnectivityThread.getInstanceLooper());
-            }});
-
-        registerService(Context.WIFI_P2P_SERVICE, WifiP2pManager.class,
-                new StaticServiceFetcher<WifiP2pManager>() {
-            @Override
-            public WifiP2pManager createService() throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_P2P_SERVICE);
-                IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(b);
-                return new WifiP2pManager(service);
-            }});
-
-        registerService(Context.WIFI_AWARE_SERVICE, WifiAwareManager.class,
-                new CachedServiceFetcher<WifiAwareManager>() {
-            @Override
-            public WifiAwareManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_AWARE_SERVICE);
-                IWifiAwareManager service = IWifiAwareManager.Stub.asInterface(b);
-                if (service == null) {
-                    return null;
-                }
-                return new WifiAwareManager(ctx.getOuterContext(), service);
-            }});
-
-        registerService(Context.WIFI_SCANNING_SERVICE, WifiScanner.class,
-                new CachedServiceFetcher<WifiScanner>() {
-            @Override
-            public WifiScanner createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_SCANNING_SERVICE);
-                IWifiScanner service = IWifiScanner.Stub.asInterface(b);
-                return new WifiScanner(ctx.getOuterContext(), service,
-                        ConnectivityThread.getInstanceLooper());
-            }});
-
-        registerService(Context.WIFI_RTT_SERVICE, RttManager.class,
-                new CachedServiceFetcher<RttManager>() {
-                @Override
-                public RttManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                    IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT_RANGING_SERVICE);
-                    IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
-                    return new RttManager(ctx.getOuterContext(),
-                            new WifiRttManager(ctx.getOuterContext(), service));
-                }});
-
-        registerService(Context.WIFI_RTT_RANGING_SERVICE, WifiRttManager.class,
-                new CachedServiceFetcher<WifiRttManager>() {
-                    @Override
-                    public WifiRttManager createService(ContextImpl ctx)
-                            throws ServiceNotFoundException {
-                        IBinder b = ServiceManager.getServiceOrThrow(
-                                Context.WIFI_RTT_RANGING_SERVICE);
-                        IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
-                        return new WifiRttManager(ctx.getOuterContext(), service);
-                    }});
-
         registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
                 new CachedServiceFetcher<EthernetManager>() {
             @Override
@@ -1265,6 +1196,7 @@
             BlobStoreManagerFrameworkInitializer.initialize();
             TelephonyFrameworkInitializer.registerServiceWrappers();
             AppSearchManagerFrameworkInitializer.initialize();
+            WifiFrameworkInitializer.registerServiceWrappers();
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index deeeddc..2d70986d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2362,6 +2362,19 @@
         }
     }
 
+    /**
+     * Check whether the device supports filesystem checkpoint.
+     *
+     * @return true if the device supports filesystem checkpoint, false otherwise.
+     */
+    public boolean isCheckpointSupported() {
+        try {
+            return mStorageManager.supportsCheckpoint();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private final Object mFuseAppLoopLock = new Object();
 
     @GuardedBy("mFuseAppLoopLock")
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 1d7e338..abf34ca 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -391,8 +391,9 @@
      * Look up the values of multiple properties for a particular namespace. The lookup is atomic,
      * such that the values of these properties cannot change between the time when the first is
      * fetched and the time when the last is fetched.
-     *
-     * TODO: reference setProperties when it is added.
+     * <p>
+     * Each call to {@link #setProperties(Properties)} is also atomic and ensures that either none
+     * or all of the change is picked up here, but never only part of it.
      *
      * @param namespace The namespace containing the properties to look up.
      * @param names     The names of properties to look up, or empty to fetch all properties for the
@@ -560,6 +561,27 @@
     }
 
     /**
+     * Set all of the properties for a specific namespace. Pre-existing properties will be updated
+     * and new properties will be added if necessary. Any pre-existing properties for the specific
+     * namespace which are not part of the provided {@link Properties} object will be deleted from
+     * the namespace. These changes are all applied atomically, such that no calls to read or reset
+     * these properties can happen in the middle of this update.
+     * <p>
+     * Each call to {@link #getProperties(String, String...)} is also atomic and ensures that either
+     * none or all of this update is picked up, but never only part of it.
+     *
+     * @param properties the complete set of properties to set for a specific namespace.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(WRITE_DEVICE_CONFIG)
+    public static boolean setProperties(@NonNull Properties properties) {
+        ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+        return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
+                properties.mMap);
+    }
+
+    /**
      * Reset properties to their default values.
      * <p>
      * The method accepts an optional namespace parameter. If provided, only properties set within
@@ -703,23 +725,26 @@
         List<String> pathSegments = uri.getPathSegments();
         // pathSegments(0) is "config"
         final String namespace = pathSegments.get(1);
-        final String name = pathSegments.get(2);
-        final String value;
+        Map<String, String> propertyMap = new ArrayMap<>();
         try {
-            value = getProperty(namespace, name);
+            Properties allProperties = getProperties(namespace);
+            for (int i = 2; i < pathSegments.size(); ++i) {
+                String key = pathSegments.get(i);
+                propertyMap.put(key, allProperties.getString(key, null));
+            }
         } catch (SecurityException e) {
             // Silently failing to not crash binder or listener threads.
             Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
             return;
         }
+        Properties properties = new Properties(namespace, propertyMap);
+
         synchronized (sLock) {
             for (int i = 0; i < sListeners.size(); i++) {
                 if (namespace.equals(sListeners.valueAt(i).first)) {
                     final OnPropertiesChangedListener listener = sListeners.keyAt(i);
                     sListeners.valueAt(i).second.execute(() -> {
-                        Map<String, String> propertyMap = new ArrayMap<>(1);
-                        propertyMap.put(name, value);
-                        listener.onPropertiesChanged(new Properties(namespace, propertyMap));
+                        listener.onPropertiesChanged(properties);
                     });
                 }
             }
@@ -741,7 +766,11 @@
     }
 
     /**
-     * Interface for monitoring changes to properties.
+     * Interface for monitoring changes to properties. Implementations will receive callbacks when
+     * properties change, including a {@link Properties} object which contains a single namespace
+     * and all of the properties which changed for that namespace. This includes properties which
+     * were added, updated, or deleted. This is not necessarily a complete list of all properties
+     * belonging to the namespace, as properties which don't change are omitted.
      * <p>
      * Override {@link #onPropertiesChanged(Properties)} to handle callbacks for changes.
      *
@@ -751,10 +780,13 @@
     @TestApi
     public interface OnPropertiesChangedListener {
         /**
-         * Called when one or more properties have changed.
+         * Called when one or more properties have changed, providing a Properties object with all
+         * of the changed properties. This object will contain only properties which have changed,
+         * not the complete set of all properties belonging to the namespace.
          *
          * @param properties Contains the complete collection of properties which have changed for a
-         *                   single namespace.
+         *                   single namespace. This includes only those which were added, updated,
+         *                   or deleted.
          */
         void onPropertiesChanged(@NonNull Properties properties);
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3ac7deb..cce0d4f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1960,6 +1960,11 @@
      */
     public static final String CALL_METHOD_PREFIX_KEY = "_prefix";
 
+    /**
+     * @hide - String argument extra to the fast-path call()-based requests
+     */
+    public static final String CALL_METHOD_FLAGS_KEY = "_flags";
+
     /** @hide - Private call() method to write to 'system' table */
     public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
 
@@ -1972,6 +1977,9 @@
     /** @hide - Private call() method to write to 'configuration' table */
     public static final String CALL_METHOD_PUT_CONFIG = "PUT_config";
 
+    /** @hide - Private call() method to write to and delete from the 'configuration' table */
+    public static final String CALL_METHOD_SET_ALL_CONFIG = "SET_ALL_config";
+
     /** @hide - Private call() method to delete from the 'system' table */
     public static final String CALL_METHOD_DELETE_SYSTEM = "DELETE_system";
 
@@ -2304,21 +2312,23 @@
         private final String mCallGetCommand;
         private final String mCallSetCommand;
         private final String mCallListCommand;
+        private final String mCallSetAllCommand;
 
         @GuardedBy("this")
         private GenerationTracker mGenerationTracker;
 
         public NameValueCache(Uri uri, String getCommand, String setCommand,
                 ContentProviderHolder providerHolder) {
-            this(uri, getCommand, setCommand, null, providerHolder);
+            this(uri, getCommand, setCommand, null, null, providerHolder);
         }
 
         NameValueCache(Uri uri, String getCommand, String setCommand, String listCommand,
-                ContentProviderHolder providerHolder) {
+                String setAllCommand, ContentProviderHolder providerHolder) {
             mUri = uri;
             mCallGetCommand = getCommand;
             mCallSetCommand = setCommand;
             mCallListCommand = listCommand;
+            mCallSetAllCommand = setAllCommand;
             mProviderHolder = providerHolder;
         }
 
@@ -2344,6 +2354,26 @@
             return true;
         }
 
+        public boolean setStringsForPrefix(ContentResolver cr, String prefix,
+                HashMap<String, String> keyValues) {
+            if (mCallSetAllCommand == null) {
+                // This NameValueCache does not support atomically setting multiple flags
+                return false;
+            }
+            try {
+                Bundle args = new Bundle();
+                args.putString(CALL_METHOD_PREFIX_KEY, prefix);
+                args.putSerializable(CALL_METHOD_FLAGS_KEY, keyValues);
+                IContentProvider cp = mProviderHolder.getProvider(cr);
+                cp.call(cr.getPackageName(), cr.getFeatureId(), mProviderHolder.mUri.getAuthority(),
+                        mCallSetAllCommand, null, args);
+            } catch (RemoteException e) {
+                // Not supported by the remote side
+                return false;
+            }
+            return true;
+        }
+
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
             final boolean isSelf = (userHandle == UserHandle.myUserId());
@@ -13718,6 +13748,7 @@
                 CALL_METHOD_GET_CONFIG,
                 CALL_METHOD_PUT_CONFIG,
                 CALL_METHOD_LIST_CONFIG,
+                CALL_METHOD_SET_ALL_CONFIG,
                 sProviderHolder);
 
         /**
@@ -13792,6 +13823,29 @@
         }
 
         /**
+         * Clear all name/value pairs for the provided namespace and save new name/value pairs in
+         * their place.
+         *
+         * @param resolver to access the database with.
+         * @param namespace to which the names should be set.
+         * @param keyValues map of key names (without the prefix) to values.
+         * @return
+         *
+         * @hide
+         */
+        @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+        static boolean setStrings(@NonNull ContentResolver resolver, @NonNull String namespace,
+                @NonNull Map<String, String> keyValues) {
+            HashMap<String, String> compositeKeyValueMap = new HashMap<>(keyValues.keySet().size());
+            for (Map.Entry<String, String> entry : keyValues.entrySet()) {
+                compositeKeyValueMap.put(
+                        createCompositeName(namespace, entry.getKey()), entry.getValue());
+            }
+            return sNameValueCache.setStringsForPrefix(resolver, createPrefix(namespace),
+                    compositeKeyValueMap);
+        }
+
+        /**
          * Reset the values to their defaults.
          * <p>
          * The method accepts an optional prefix parameter. If provided, only pairs with a name that
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 0c83390..8c1c3b5 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -33,6 +33,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /** Tests that ensure appropriate settings are backed up. */
 @Presubmit
 @RunWith(AndroidJUnit4.class)
@@ -484,12 +487,74 @@
         assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
     }
 
-    // TODO(mpape): resolve b/142727848 and re-enable this test
+    @Test
+    public void setProperties() {
+        Map<String, String> keyValues = new HashMap<>();
+        keyValues.put(KEY, VALUE);
+        keyValues.put(KEY2, VALUE2);
+
+        DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
+        Properties properties = DeviceConfig.getProperties(NAMESPACE);
+        assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+        assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+        assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+
+        Map<String, String> newKeyValues = new HashMap<>();
+        newKeyValues.put(KEY, VALUE2);
+        newKeyValues.put(KEY3, VALUE3);
+
+        DeviceConfig.setProperties(new Properties(NAMESPACE, newKeyValues));
+        properties = DeviceConfig.getProperties(NAMESPACE);
+        assertThat(properties.getKeyset()).containsExactly(KEY, KEY3);
+        assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE2);
+        assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE3);
+
+        assertThat(properties.getKeyset()).doesNotContain(KEY2);
+        assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+    }
+
+    @Test
+    public void setProperties_multipleNamespaces() {
+        Map<String, String> keyValues = new HashMap<>();
+        keyValues.put(KEY, VALUE);
+        keyValues.put(KEY2, VALUE2);
+
+        Map<String, String> keyValues2 = new HashMap<>();
+        keyValues2.put(KEY2, VALUE);
+        keyValues2.put(KEY3, VALUE2);
+
+        final String namespace2 = "namespace2";
+        DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
+        DeviceConfig.setProperties(new Properties(namespace2, keyValues2));
+
+        Properties properties = DeviceConfig.getProperties(NAMESPACE);
+        assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+        assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+        assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+
+        assertThat(properties.getKeyset()).doesNotContain(KEY3);
+        assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+
+        properties = DeviceConfig.getProperties(namespace2);
+        assertThat(properties.getKeyset()).containsExactly(KEY2, KEY3);
+        assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE);
+        assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE2);
+
+        assertThat(properties.getKeyset()).doesNotContain(KEY);
+        assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+
+        // clean up
+        deleteViaContentProvider(namespace2, KEY);
+        deleteViaContentProvider(namespace2, KEY2);
+        deleteViaContentProvider(namespace2, KEY3);
+    }
+
+    // TODO(mpape): resolve b/142727848 and re-enable listener tests
 //    @Test
-//    public void testOnPropertiesChangedListener() throws InterruptedException {
+//    public void onPropertiesChangedListener_setPropertyCallback() throws InterruptedException {
 //        final CountDownLatch countDownLatch = new CountDownLatch(1);
 //
-//        OnPropertiesChangedListener changeListener = (properties) -> {
+//        DeviceConfig.OnPropertiesChangedListener changeListener = (properties) -> {
 //            assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
 //            assertThat(properties.getKeyset()).contains(KEY);
 //            assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE);
@@ -508,6 +573,42 @@
 //            DeviceConfig.removeOnPropertiesChangedListener(changeListener);
 //        }
 //    }
+//
+//    @Test
+//    public void onPropertiesChangedListener_setPropertiesCallback() throws InterruptedException {
+//        final CountDownLatch countDownLatch = new CountDownLatch(1);
+//        DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+//        DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
+//
+//        Map<String, String> keyValues = new HashMap<>(2);
+//        keyValues.put(KEY, VALUE2);
+//        keyValues.put(KEY3, VALUE3);
+//        Properties setProperties = new Properties(NAMESPACE, keyValues);
+//
+//        DeviceConfig.OnPropertiesChangedListener changeListener = (properties) -> {
+//            assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
+//            assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3);
+//            // KEY updated from VALUE to VALUE2
+//            assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE2);
+//            // KEY2 deleted (returns default_value)
+//            assertThat(properties.getString(KEY2, "default_value")).isEqualTo("default_value");
+//            //KEY3 added with VALUE3
+//            assertThat(properties.getString(KEY3, "default_value")).isEqualTo(VALUE3);
+//            countDownLatch.countDown();
+//        };
+//
+//        try {
+//            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE,
+//                    ActivityThread.currentApplication().getMainExecutor(), changeListener);
+//            DeviceConfig.setProperties(setProperties);
+//            assertThat(countDownLatch.await(
+//                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+//        } catch (InterruptedException e) {
+//            Assert.fail(e.getMessage());
+//        } finally {
+//            DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+//        }
+//    }
 
     private static boolean deleteViaContentProvider(String namespace, String key) {
         ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index b188dc3..4543019 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -50,9 +50,6 @@
 @Singleton
 public class FullscreenUserSwitcher {
     private static final String TAG = FullscreenUserSwitcher.class.getSimpleName();
-    // Because user 0 is headless, user count for single user is 2
-    private static final int NUMBER_OF_BACKGROUND_USERS = 1;
-
     private final Context mContext;
     private final Resources mResources;
     private final UserManager mUserManager;
@@ -137,27 +134,18 @@
                 /* isStartGuestSession= */ false,
                 /* isAddUser= */ false,
                 /* isForeground= */ true);
-        // For single user without trusted device, hide the user switcher.
-        if (!hasMultipleUsers() && !hasTrustedDevice(initialUser)) {
-            dismissUserSwitcher();
-            return;
-        }
-        // Show unlock dialog for initial user
+
+        // If the initial user has trusted device, display the unlock dialog on the keyguard.
         if (hasTrustedDevice(initialUser)) {
             mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
                     mOnHideListener);
+        } else {
+            // If no trusted device, dismiss the keyguard.
+            dismissUserSwitcher();
         }
     }
 
     /**
-     * Check if there is only one possible user to login in.
-     * In a Multi-User system there is always one background user (user 0)
-     */
-    private boolean hasMultipleUsers() {
-        return mUserManager.getUserCount() > NUMBER_OF_BACKGROUND_USERS + 1;
-    }
-
-    /**
      * Makes user grid visible.
      */
     public void show() {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f5d1ccf..0959de9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -101,6 +101,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -383,6 +384,13 @@
                 break;
             }
 
+            case Settings.CALL_METHOD_SET_ALL_CONFIG: {
+                String prefix = getSettingPrefix(args);
+                Map<String, String> flags = getSettingFlags(args);
+                setAllConfigSettings(prefix, flags);
+                break;
+            }
+
             case Settings.CALL_METHOD_RESET_CONFIG: {
                 final int mode = getResetModeEnforcingPermission(args);
                 String prefix = getSettingPrefix(args);
@@ -1030,6 +1038,19 @@
                 MUTATION_OPERATION_INSERT, 0);
     }
 
+    private boolean setAllConfigSettings(String prefix, Map<String, String> keyValues) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "setAllConfigSettings for prefix: " + prefix);
+        }
+
+        enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
+
+        synchronized (mLock) {
+            return mSettingsRegistry.setSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM,
+                    prefix, keyValues, resolveCallingPackage());
+        }
+    }
+
     private boolean deleteConfigSetting(String name) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ")");
@@ -2117,6 +2138,11 @@
         return (args != null) ? args.getString(Settings.CALL_METHOD_PREFIX_KEY) : null;
     }
 
+    private static Map<String, String> getSettingFlags(Bundle args) {
+        return (args != null) ? (HashMap) args.getSerializable(Settings.CALL_METHOD_FLAGS_KEY)
+                : Collections.emptyMap();
+    }
+
     private static boolean getSettingMakeDefault(Bundle args) {
         return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
     }
@@ -2485,7 +2511,7 @@
             final int key = makeKey(type, userId);
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState == null) {
-                return new ArrayList<String>();
+                return new ArrayList<>();
             }
             return settingsState.getSettingNamesLocked();
         }
@@ -2645,6 +2671,22 @@
             return success;
         }
 
+        public boolean setSettingsLocked(int type, int userId, String prefix,
+                Map<String, String> keyValues, String packageName) {
+            final int key = makeKey(type, userId);
+
+            SettingsState settingsState = peekSettingsStateLocked(key);
+            if (settingsState != null) {
+                List<String> changedSettings =
+                        settingsState.setSettingsLocked(prefix, keyValues, packageName);
+                if (!changedSettings.isEmpty()) {
+                    notifyForConfigSettingsChangeLocked(key, prefix, changedSettings);
+                }
+            }
+
+            return settingsState != null;
+        }
+
         public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
                 Set<String> criticalSettings) {
             final int key = makeKey(type, userId);
@@ -3043,6 +3085,28 @@
             mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
         }
 
+        private void notifyForConfigSettingsChangeLocked(int key, String prefix,
+                List<String> changedSettings) {
+
+            // Increment the generation first, so observers always see the new value
+            mGenerationRegistry.incrementGeneration(key);
+
+            StringBuilder stringBuilder = new StringBuilder(prefix);
+            for (int i = 0; i < changedSettings.size(); ++i) {
+                stringBuilder.append(changedSettings.get(i).split("/")[1]).append("/");
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                notifySettingChangeForRunningUsers(key, stringBuilder.toString());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+
+            // Always notify that our data changed
+            mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+        }
+
         private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
                 Collection<String> keysCloned) {
             if (keysCloned.contains(name)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index de6a3a8..4731e68 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -64,6 +64,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -416,6 +417,57 @@
     }
 
     // The settings provider must hold its lock when calling here.
+    // Returns the list of keys which changed (added, updated, or deleted).
+    @GuardedBy("mLock")
+    public List<String> setSettingsLocked(String prefix, Map<String, String> keyValues,
+            String packageName) {
+        List<String> changedKeys = new ArrayList<>();
+        // Delete old keys with the prefix that are not part of the new set.
+        for (int i = 0; i < mSettings.keySet().size(); ++i) {
+            String key = mSettings.keyAt(i);
+            if (key.startsWith(prefix) && !keyValues.containsKey(key)) {
+                Setting oldState = mSettings.remove(key);
+
+                StatsLog.write(StatsLog.SETTING_CHANGED, key, /* value= */ "", /* newValue= */ "",
+                        oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
+                        StatsLog.SETTING_CHANGED__REASON__DELETED);
+                addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
+                changedKeys.add(key); // key was removed
+            }
+        }
+
+        // Update/add new keys
+        for (String key : keyValues.keySet()) {
+            String value = keyValues.get(key);
+            String oldValue = null;
+            Setting state = mSettings.get(key);
+            if (state == null) {
+                state = new Setting(key, value, false, packageName, null);
+                mSettings.put(key, state);
+                changedKeys.add(key); // key was added
+            } else if (state.value != value) {
+                oldValue = state.value;
+                state.update(value, false, packageName, null, true);
+                changedKeys.add(key); // key was updated
+            } else {
+                // this key/value already exists, no change and no logging necessary
+                continue;
+            }
+
+            StatsLog.write(StatsLog.SETTING_CHANGED, key, value, state.value, oldValue,
+                    /* tag */ null, /* make default */ false,
+                    getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED);
+            addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state);
+        }
+
+        if (!changedKeys.isEmpty()) {
+            scheduleWriteIfNeededLocked();
+        }
+
+        return changedKeys;
+    }
+
+    // The settings provider must hold its lock when calling here.
     public void persistSyncLocked() {
         mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
         doWriteState();
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index dcc690f..4a925aa 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2771,11 +2771,6 @@
      */
     @Override
     public boolean supportsCheckpoint() throws RemoteException {
-        // Only the system process is permitted to start checkpoints
-        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
-            throw new SecurityException("no permission to check filesystem checkpoint support");
-        }
-
         return mVold.supportsCheckpoint();
     }
 
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 3390ca7..5bb42d4 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -45,6 +45,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.storage.StorageManager;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -75,6 +76,7 @@
     private final PackageInstallerService mPi;
     private final ApexManager mApexManager;
     private final PowerManager mPowerManager;
+    private final Context mContext;
     private final PreRebootVerificationHandler mPreRebootVerificationHandler;
 
     @GuardedBy("mStagedSessions")
@@ -83,6 +85,7 @@
     StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
         mPi = pi;
         mApexManager = am;
+        mContext = context;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mPreRebootVerificationHandler = new PreRebootVerificationHandler(
                 BackgroundThread.get().getLooper());
@@ -539,6 +542,10 @@
         mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
     }
 
+    private int parentOrOwnSessionId(PackageInstallerSession session) {
+        return session.hasParentSessionId() ? session.getParentSessionId() : session.sessionId;
+    }
+
     /**
      * <p> Check if the session provided is non-overlapping with the active staged sessions.
      *
@@ -561,6 +568,9 @@
                     "Cannot stage session " + session.sessionId + " with package name null");
         }
 
+        boolean supportsCheckpoint = ((StorageManager) mContext.getSystemService(
+                Context.STORAGE_SERVICE)).isCheckpointSupported();
+
         synchronized (mStagedSessions) {
             for (int i = 0; i < mStagedSessions.size(); i++) {
                 final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
@@ -601,7 +611,17 @@
                                     + stagedSession.sessionId, null);
                 }
 
-                // TODO(b/141843321): Add support for staging multiple sessions in apexd
+                // Staging multiple root sessions is not allowed if device doesn't support
+                // checkpoint. If session and stagedSession do not have common ancestor, they are
+                // from two different root sessions.
+                if (!supportsCheckpoint
+                        && parentOrOwnSessionId(session) != parentOrOwnSessionId(stagedSession)) {
+                    throw new PackageManagerException(
+                            PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
+                            "Cannot stage multiple sessions without checkpoint support", null);
+                }
+
+                // TODO:b/141843321 Add support for staging multiple sessions in apexd
                 // Since apexd doesn't support multiple staged sessions yet, we have to careful how
                 // we handle apex sessions. We want to allow a set of apex sessions under the same
                 // parent to be staged when there is no previously staged apex sessions.
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 15bb002..add0316 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -4090,9 +4090,7 @@
 
         /**
          * The subscription which received this cell broadcast message.
-         * @deprecated use {@link #SLOT_INDEX} instead.
          * <P>Type: INTEGER</P>
-         * @hide
          */
         public static final String SUB_ID = "sub_id";
 
@@ -4366,6 +4364,7 @@
         public static final String[] QUERY_COLUMNS_FWK = {
                 _ID,
                 SLOT_INDEX,
+                SUB_ID,
                 GEOGRAPHICAL_SCOPE,
                 PLMN,
                 LAC,
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 737ead1..fad70d2 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -205,7 +205,9 @@
     /** CMAS warning area coordinates. */
     private final List<Geometry> mGeometries;
 
-    private int mSlotIndex = 0;
+    private final int mSlotIndex;
+
+    private final int mSubId;
 
     /**
      * Create a new SmsCbMessage with the specified data.
@@ -214,11 +216,11 @@
     public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
             @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language,
             @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
-            @Nullable SmsCbCmasInfo cmasWarningInfo, int slotIndex) {
+            @Nullable SmsCbCmasInfo cmasWarningInfo, int slotIndex, int subId) {
 
         this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
                 body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */,
-                null /* geometries */, System.currentTimeMillis(), slotIndex);
+                null /* geometries */, System.currentTimeMillis(), slotIndex, subId);
     }
 
     /**
@@ -226,10 +228,12 @@
      * coordinates information.
      */
     public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
-            @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language,
-            @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
-            @Nullable SmsCbCmasInfo cmasWarningInfo, int maximumWaitTimeSec,
-            @Nullable List<Geometry> geometries, long receivedTimeMillis, int slotIndex) {
+                        @NonNull SmsCbLocation location, int serviceCategory,
+                        @Nullable String language, @Nullable String body, int priority,
+                        @Nullable SmsCbEtwsInfo etwsWarningInfo,
+                        @Nullable SmsCbCmasInfo cmasWarningInfo, int maximumWaitTimeSec,
+                        @Nullable List<Geometry> geometries, long receivedTimeMillis, int slotIndex,
+                        int subId) {
         mMessageFormat = messageFormat;
         mGeographicalScope = geographicalScope;
         mSerialNumber = serialNumber;
@@ -244,6 +248,7 @@
         mGeometries = geometries;
         mMaximumWaitTimeSec = maximumWaitTimeSec;
         mSlotIndex = slotIndex;
+        mSubId = subId;
     }
 
     /**
@@ -282,6 +287,7 @@
         mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
         mMaximumWaitTimeSec = in.readInt();
         mSlotIndex = in.readInt();
+        mSubId = in.readInt();
     }
 
     /**
@@ -317,6 +323,7 @@
                 mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
         dest.writeInt(mMaximumWaitTimeSec);
         dest.writeInt(mSlotIndex);
+        dest.writeInt(mSubId);
     }
 
     @NonNull
@@ -427,14 +434,22 @@
     }
 
     /**
-     * Get the slotIndex associated with this message.
-     * @return the slotIndex associated with this message
+     * Get the slot index associated with this message.
+     * @return the slot index associated with this message
      */
     public int getSlotIndex() {
         return mSlotIndex;
     }
 
     /**
+     * Get the subscription id associated with this message.
+     * @return the subscription id associated with this message
+     */
+    public int getSubscriptionId() {
+        return mSubId;
+    }
+
+    /**
      * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
      * @return an integer representing 3GPP or 3GPP2 message format
      */
@@ -536,6 +551,7 @@
     public ContentValues getContentValues() {
         ContentValues cv = new ContentValues(16);
         cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex);
+        cv.put(CellBroadcasts.SUB_ID, mSubId);
         cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
         if (mLocation.getPlmn() != null) {
             cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
@@ -577,7 +593,6 @@
         }
 
         cv.put(CellBroadcasts.MAXIMUM_WAIT_TIME, mMaximumWaitTimeSec);
-        cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex);
 
         return cv;
     }
@@ -600,6 +615,7 @@
         int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
         int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
         int slotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX));
+        int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUB_ID));
 
         String plmn;
         int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
@@ -697,7 +713,7 @@
 
         return new SmsCbMessage(format, geoScope, serialNum, location, category,
                 language, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries,
-                receivedTimeMillis, slotIndex);
+                receivedTimeMillis, slotIndex, subId);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 6fc0228..1f7715b 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -875,9 +875,10 @@
      * Parses a broadcast SMS, possibly containing a CMAS alert.
      *
      * @param plmn the PLMN for a broadcast SMS
-     * @param subId
+     * @param slotIndex SIM slot index
+     * @param subId Subscription id
      */
-    public SmsCbMessage parseBroadcastSms(String plmn, int subId) {
+    public SmsCbMessage parseBroadcastSms(String plmn, int slotIndex, int subId) {
         BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
         if (bData == null) {
             Rlog.w(LOG_TAG, "BearerData.decode() returned null");
@@ -893,7 +894,7 @@
         return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
                 SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
                 mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
-                bData.priority, null, bData.cmasWarningInfo, subId);
+                bData.priority, null, bData.cmasWarningInfo, slotIndex, subId);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index d03419d..c16eafb 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -33,6 +33,7 @@
 import android.telephony.Rlog;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
+import android.telephony.SubscriptionManager;
 import android.util.Pair;
 
 import com.android.internal.R;
@@ -95,6 +96,14 @@
     public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
             SmsCbLocation location, byte[][] pdus, int slotIndex)
             throws IllegalArgumentException {
+        SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        int[] subIds = sm.getSubscriptionIds(slotIndex);
+        if (subIds != null && subIds.length > 0) {
+            subId = subIds[0];
+        }
+
         long receivedTimeMillis = System.currentTimeMillis();
         if (header.isEtwsPrimaryNotification()) {
             // ETSI TS 23.041 ETWS Primary Notification message
@@ -105,7 +114,8 @@
                     header.getSerialNumber(), location, header.getServiceCategory(), null,
                     getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
                     SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
-                    header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex);
+                    header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex,
+                    subId);
         } else if (header.isUmtsFormat()) {
             // UMTS format has only 1 PDU
             byte[] pdu = pdus[0];
@@ -139,7 +149,7 @@
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, body, priority,
                     header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
-                    receivedTimeMillis, slotIndex);
+                    receivedTimeMillis, slotIndex, subId);
         } else {
             String language = null;
             StringBuilder sb = new StringBuilder();
@@ -155,7 +165,7 @@
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, sb.toString(), priority,
                     header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
-                    receivedTimeMillis, slotIndex);
+                    receivedTimeMillis, slotIndex, subId);
         }
     }
 
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 6a03c73..73c52ab 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -1212,7 +1212,7 @@
      *
      * @hide
      */
-    public RttManager(Context context, WifiRttManager service) {
+    public RttManager(@NonNull Context context, @NonNull WifiRttManager service) {
         mNewService = service;
         mContext = context;
 
diff --git a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
new file mode 100644
index 0000000..775043a
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.p2p.IWifiP2pManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.WifiRttManager;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * Class for performing registration for all Wifi services.
+ *
+ * @hide
+ */
+@SystemApi
+public class WifiFrameworkInitializer {
+
+    /**
+     * A class implementing the lazy holder idiom: the unique static instance
+     * of {@link #INSTANCE} is instantiated in a thread-safe way (guaranteed by
+     * the language specs) the first time that NoPreloadHolder is referenced in getInstanceLooper().
+     *
+     * This is necessary because we can't spawn a new thread in {@link #registerServiceWrappers()}.
+     * {@link #registerServiceWrappers()} is called during the Zygote phase, which disallows
+     * spawning new threads. Naming the class "NoPreloadHolder" ensures that the classloader will
+     * not preload this class, inadvertently spawning the thread too early.
+     */
+    private static class NoPreloadHolder {
+        private static final HandlerThread INSTANCE = createInstance();
+
+        private static HandlerThread createInstance() {
+            HandlerThread thread = new HandlerThread("WifiManagerThread");
+            thread.start();
+            return thread;
+        }
+    }
+
+    private static Looper getInstanceLooper() {
+        return NoPreloadHolder.INSTANCE.getLooper();
+    }
+
+    private WifiFrameworkInitializer() {}
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers all Wifi services
+     * to {@link Context}, so that {@link Context#getSystemService} can return them.
+     *
+     * @throws IllegalStateException if this is called from anywhere besides
+     * {@link SystemServiceRegistry}
+     */
+    public static void registerServiceWrappers() {
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_SERVICE,
+                WifiManager.class,
+                context -> new WifiManager(context, getInstanceLooper())
+        );
+        SystemServiceRegistry.registerStaticService(
+                Context.WIFI_P2P_SERVICE,
+                WifiP2pManager.class,
+                serviceBinder -> {
+                    IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(serviceBinder);
+                    return new WifiP2pManager(service);
+                }
+        );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_AWARE_SERVICE,
+                WifiAwareManager.class,
+                (context, serviceBinder) -> {
+                    IWifiAwareManager service = IWifiAwareManager.Stub.asInterface(serviceBinder);
+                    return new WifiAwareManager(context, service);
+                }
+        );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_SCANNING_SERVICE,
+                WifiScanner.class,
+                (context, serviceBinder) -> {
+                    IWifiScanner service = IWifiScanner.Stub.asInterface(serviceBinder);
+                    return new WifiScanner(context, service, getInstanceLooper());
+                }
+        );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_RTT_SERVICE,
+                RttManager.class,
+                (context, serviceBinder) -> {
+                    IWifiRttManager service = IWifiRttManager.Stub.asInterface(serviceBinder);
+                    WifiRttManager wifiRttManager = new WifiRttManager(context, service);
+                    return new RttManager(context, wifiRttManager);
+                }
+        );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_RTT_RANGING_SERVICE,
+                WifiRttManager.class,
+                (context, serviceBinder) -> {
+                    IWifiRttManager service = IWifiRttManager.Stub.asInterface(serviceBinder);
+                    return new WifiRttManager(context, service);
+                }
+        );
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 77666b0..942d795 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1213,11 +1213,13 @@
      * Applications will almost always want to use
      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
+     *
      * @param context the application context
-     * @hide - hide this because it takes in a parameter of type IWifiManager, which
-     * is a system private class.
+     * @param looper the Looper used to deliver callbacks
+     *
+     * @hide
      */
-    public WifiManager(Context context, Looper looper) {
+    public WifiManager(@NonNull Context context, @NonNull Looper looper) {
         mContext = context;
         mLooper = looper;
         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 0d36718..771f3b8 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -1291,12 +1291,15 @@
      * Applications will almost always want to use
      * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
      * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
+     *
      * @param context the application context
-     * @param service the Binder interface
+     * @param service the Binder interface for {@link Context#WIFI_SCANNING_SERVICE}
      * @param looper the Looper used to deliver callbacks
+     *
      * @hide
      */
-    public WifiScanner(Context context, IWifiScanner service, Looper looper) {
+    public WifiScanner(@NonNull Context context, @NonNull IWifiScanner service,
+            @NonNull Looper looper) {
         mContext = context;
         mService = service;
 
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 5aab347..7b37d65 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -158,7 +158,7 @@
     private final Object mLock = new Object(); // lock access to the following vars
 
     /** @hide */
-    public WifiAwareManager(Context context, IWifiAwareManager service) {
+    public WifiAwareManager(@NonNull Context context, @NonNull IWifiAwareManager service) {
         mContext = context;
         mService = service;
     }
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
index 770a120..cb0c5d4 100644
--- a/wifi/java/android/net/wifi/rtt/WifiRttManager.java
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -77,7 +77,7 @@
             "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
 
     /** @hide */
-    public WifiRttManager(Context context, IWifiRttManager service) {
+    public WifiRttManager(@NonNull Context context, @NonNull IWifiRttManager service) {
         mContext = context;
         mService = service;
     }