Merge "AudioSystem: fix "keep in sync" comments"
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index bccbb38..2a854b2 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -37,8 +37,6 @@
 import android.os.PowerExemptionManager.ReasonCode;
 import android.os.PowerExemptionManager.TempAllowListType;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
@@ -61,7 +59,8 @@
     private long mRequireCompatChangeId = CHANGE_INVALID;
     private long mIdForResponseEvent;
     private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
-    private @Nullable String mDeliveryGroupMatchingKey;
+    private @Nullable String mDeliveryGroupMatchingNamespaceFragment;
+    private @Nullable String mDeliveryGroupMatchingKeyFragment;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
     private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
     private @DeferralPolicy int mDeferralPolicy;
@@ -209,7 +208,13 @@
             "android:broadcast.deliveryGroupPolicy";
 
     /**
-     * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * Corresponds to namespace fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
+     */
+    private static final String KEY_DELIVERY_GROUP_NAMESPACE =
+            "android:broadcast.deliveryGroupMatchingNamespace";
+
+    /**
+     * Corresponds to key fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     private static final String KEY_DELIVERY_GROUP_KEY =
             "android:broadcast.deliveryGroupMatchingKey";
@@ -350,7 +355,8 @@
         mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
         mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
                 DELIVERY_GROUP_POLICY_ALL);
-        mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+        mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
+        mDeliveryGroupMatchingKeyFragment = opts.getString(KEY_DELIVERY_GROUP_KEY);
         mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
                 BundleMerger.class);
         mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
@@ -864,11 +870,8 @@
     @NonNull
     public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
             @NonNull String key) {
-        Preconditions.checkArgument(!namespace.contains(":"),
-                "namespace should not contain ':'");
-        Preconditions.checkArgument(!key.contains(":"),
-                "key should not contain ':'");
-        mDeliveryGroupMatchingKey = namespace + ":" + key;
+        mDeliveryGroupMatchingNamespaceFragment = Objects.requireNonNull(namespace);
+        mDeliveryGroupMatchingKeyFragment = Objects.requireNonNull(key);
         return this;
     }
 
@@ -881,7 +884,38 @@
      */
     @Nullable
     public String getDeliveryGroupMatchingKey() {
-        return mDeliveryGroupMatchingKey;
+        if (mDeliveryGroupMatchingNamespaceFragment == null
+                || mDeliveryGroupMatchingKeyFragment == null) {
+            return null;
+        }
+        return String.join(":", mDeliveryGroupMatchingNamespaceFragment,
+                mDeliveryGroupMatchingKeyFragment);
+    }
+
+    /**
+     * Return the namespace fragment that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group namespace fragment that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * @hide
+     */
+    @Nullable
+    public String getDeliveryGroupMatchingNamespaceFragment() {
+        return mDeliveryGroupMatchingNamespaceFragment;
+    }
+
+    /**
+     * Return the key fragment that is used to identify the delivery group that this
+     * broadcast belongs to.
+     *
+     * @return the delivery group key fragment that was previously set using
+     *         {@link #setDeliveryGroupMatchingKey(String, String)}.
+     * @hide
+     */
+    @Nullable
+    public String getDeliveryGroupMatchingKeyFragment() {
+        return mDeliveryGroupMatchingKeyFragment;
     }
 
     /**
@@ -889,7 +923,8 @@
      * {@link #setDeliveryGroupMatchingKey(String, String)}.
      */
     public void clearDeliveryGroupMatchingKey() {
-        mDeliveryGroupMatchingKey = null;
+        mDeliveryGroupMatchingNamespaceFragment = null;
+        mDeliveryGroupMatchingKeyFragment = null;
     }
 
     /**
@@ -1101,8 +1136,11 @@
         if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
             b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
         }
-        if (mDeliveryGroupMatchingKey != null) {
-            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
+        if (mDeliveryGroupMatchingNamespaceFragment != null) {
+            b.putString(KEY_DELIVERY_GROUP_NAMESPACE, mDeliveryGroupMatchingNamespaceFragment);
+        }
+        if (mDeliveryGroupMatchingKeyFragment != null) {
+            b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKeyFragment);
         }
         if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
             if (mDeliveryGroupExtrasMerger != null) {
diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS
index 01b2cb9..1e41886 100644
--- a/core/java/android/hardware/soundtrigger/OWNERS
+++ b/core/java/android/hardware/soundtrigger/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index 65fbb03..2fe784a 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -54,6 +54,19 @@
                 /*count*/ 1, binIndex);
     }
 
+    /**
+     * Logs increment sample count for automatically calculated bin
+     *
+     * @param uid used as a dimension for the count metric
+     * @param sample value
+     * @hide
+     */
+    public void logSampleWithUid(int uid, float sample) {
+        final int binIndex = mBinOptions.getBinForSample(sample);
+        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
+                mMetricIdHash, /*count*/ 1, binIndex, uid);
+    }
+
     /** Used by Histogram to map data sample to corresponding bin */
     public interface BinOptions {
         /**
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index a7c7d0b..b24dc8a 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -413,9 +413,13 @@
 
         if (env->ExceptionCheck()) {
             ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
-            binder_report_exception(env, excep.get(),
-                                    "*** Uncaught remote exception!  "
-                                    "(Exceptions are not yet supported across processes.)");
+
+            auto state = IPCThreadState::self();
+            String8 msg;
+            msg.appendFormat("*** Uncaught remote exception! Exceptions are not yet supported "
+                             "across processes. Client PID %d UID %d.",
+                             state->getCallingPid(), state->getCallingUid());
+            binder_report_exception(env, excep.get(), msg.c_str());
             res = JNI_FALSE;
         }
 
@@ -431,6 +435,7 @@
             ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
             binder_report_exception(env, excep.get(),
                                     "*** Uncaught exception in onBinderStrictModePolicyChange");
+            // TODO: should turn this to fatal?
         }
 
         // Need to always call through the native implementation of
diff --git a/media/OWNERS b/media/OWNERS
index 5f50137..4a6648e 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 1344
+atneya@google.com
 elaurent@google.com
 essick@google.com
 etalvala@google.com
diff --git a/media/aidl/android/media/soundtrigger_middleware/OWNERS b/media/aidl/android/media/soundtrigger_middleware/OWNERS
index 01b2cb9..1e41886 100644
--- a/media/aidl/android/media/soundtrigger_middleware/OWNERS
+++ b/media/aidl/android/media/soundtrigger_middleware/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS
index 01b2cb9..85f7a4d 100644
--- a/media/java/android/media/soundtrigger/OWNERS
+++ b/media/java/android/media/soundtrigger/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 48436
 atneya@google.com
 elaurent@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d48723a..99f1863 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8415,13 +8415,16 @@
             }
         }
 
+        boolean recoverable = eventType.equals("native_recoverable_crash");
+
         EventLogTags.writeAmCrash(Binder.getCallingPid(),
                 UserHandle.getUserId(Binder.getCallingUid()), processName,
                 r == null ? -1 : r.info.flags,
                 crashInfo.exceptionClassName,
                 crashInfo.exceptionMessage,
                 crashInfo.throwFileName,
-                crashInfo.throwLineNumber);
+                crashInfo.throwLineNumber,
+                recoverable ? 1 : 0);
 
         int processClassEnum = processName.equals("system_server") ? ServerProtoEnums.SYSTEM_SERVER
                 : (r != null) ? r.getProcessClassEnum()
@@ -8489,7 +8492,13 @@
                 eventType, r, processName, null, null, null, null, null, null, crashInfo,
                 new Float(loadingProgress), incrementalMetrics, null);
 
-        mAppErrors.crashApplication(r, crashInfo);
+        // For GWP-ASan recoverable crashes, don't make the app crash (the whole point of
+        // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
+        // debuggerd will terminate the process, but there's a backup where ActivityManager will
+        // also kill it. Avoid that.
+        if (!recoverable) {
+            mAppErrors.crashApplication(r, crashInfo);
+        }
     }
 
     public void handleApplicationStrictModeViolation(
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index d080036..6ce70a1 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -53,7 +53,7 @@
 30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
 
 # Unhandled exception
-30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
+30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5),(Recoverable|1|5)
 # Log.wtf() called
 30040 am_wtf (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
 
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 94eb076..cd119e7 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -64,12 +64,15 @@
     class NativeCrashReporter extends Thread {
         ProcessRecord mApp;
         int mSignal;
+        boolean mGwpAsanRecoverableCrash;
         String mCrashReport;
 
-        NativeCrashReporter(ProcessRecord app, int signal, String report) {
+        NativeCrashReporter(ProcessRecord app, int signal, boolean gwpAsanRecoverableCrash,
+                            String report) {
             super("NativeCrashReport");
             mApp = app;
             mSignal = signal;
+            mGwpAsanRecoverableCrash = gwpAsanRecoverableCrash;
             mCrashReport = report;
         }
 
@@ -85,7 +88,9 @@
                 ci.stackTrace = mCrashReport;
 
                 if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
-                mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
+                mAm.handleApplicationCrashInner(
+                        mGwpAsanRecoverableCrash ? "native_recoverable_crash" : "native_crash",
+                        mApp, mApp.processName, ci);
                 if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
             } catch (Exception e) {
                 Slog.e(TAG, "Unable to report native crash", e);
@@ -207,9 +212,14 @@
             // permits crash_dump to connect to it. This allows us to trust the
             // received values.
 
-            // first, the pid and signal number
-            int headerBytes = readExactly(fd, buf, 0, 8);
-            if (headerBytes != 8) {
+            // Activity Manager protocol:
+            //  - 32-bit network-byte-order: pid
+            //  - 32-bit network-byte-order: signal number
+            //  - byte: gwpAsanRecoverableCrash
+            //  - bytes: raw text of the dump
+            //  - null terminator
+            int headerBytes = readExactly(fd, buf, 0, 9);
+            if (headerBytes != 9) {
                 // protocol failure; give up
                 Slog.e(TAG, "Unable to read from debuggerd");
                 return;
@@ -217,69 +227,76 @@
 
             int pid = unpackInt(buf, 0);
             int signal = unpackInt(buf, 4);
+            boolean gwpAsanRecoverableCrash = buf[8] != 0;
             if (DEBUG) {
-                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
+                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal
+                        + " recoverable=" + gwpAsanRecoverableCrash);
+            }
+            if (pid < 0) {
+                Slog.e(TAG, "Bogus pid!");
+                return;
             }
 
             // now the text of the dump
-            if (pid > 0) {
-                final ProcessRecord pr;
-                synchronized (mAm.mPidsSelfLocked) {
-                    pr = mAm.mPidsSelfLocked.get(pid);
-                }
-                if (pr != null) {
-                    // Don't attempt crash reporting for persistent apps
-                    if (pr.isPersistent()) {
-                        if (DEBUG) {
-                            Slog.v(TAG, "Skipping report for persistent app " + pr);
-                        }
-                        return;
-                    }
-
-                    int bytes;
-                    do {
-                        // get some data
-                        bytes = Os.read(fd, buf, 0, buf.length);
-                        if (bytes > 0) {
-                            if (MORE_DEBUG) {
-                                String s = new String(buf, 0, bytes, "UTF-8");
-                                Slog.v(TAG, "READ=" + bytes + "> " + s);
-                            }
-                            // did we just get the EOD null byte?
-                            if (buf[bytes-1] == 0) {
-                                os.write(buf, 0, bytes-1);  // exclude the EOD token
-                                break;
-                            }
-                            // no EOD, so collect it and read more
-                            os.write(buf, 0, bytes);
-                        }
-                    } while (bytes > 0);
-
-                    // Okay, we've got the report.
-                    if (DEBUG) Slog.v(TAG, "processing");
-
-                    // Mark the process record as being a native crash so that the
-                    // cleanup mechanism knows we're still submitting the report
-                    // even though the process will vanish as soon as we let
-                    // debuggerd proceed.
-                    synchronized (mAm) {
-                        synchronized (mAm.mProcLock) {
-                            pr.mErrorState.setCrashing(true);
-                            pr.mErrorState.setForceCrashReport(true);
-                        }
-                    }
-
-                    // Crash reporting is synchronous but we want to let debuggerd
-                    // go about it business right away, so we spin off the actual
-                    // reporting logic on a thread and let it take it's time.
-                    final String reportString = new String(os.toByteArray(), "UTF-8");
-                    (new NativeCrashReporter(pr, signal, reportString)).start();
-                } else {
-                    Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
-                }
-            } else {
-                Slog.e(TAG, "Bogus pid!");
+            final ProcessRecord pr;
+            synchronized (mAm.mPidsSelfLocked) {
+                pr = mAm.mPidsSelfLocked.get(pid);
             }
+            if (pr == null) {
+                Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
+                return;
+            }
+
+            // Don't attempt crash reporting for persistent apps
+            if (pr.isPersistent()) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Skipping report for persistent app " + pr);
+                }
+                return;
+            }
+
+            int bytes;
+            do {
+                // get some data
+                bytes = Os.read(fd, buf, 0, buf.length);
+                if (bytes > 0) {
+                    if (MORE_DEBUG) {
+                        String s = new String(buf, 0, bytes, "UTF-8");
+                        Slog.v(TAG, "READ=" + bytes + "> " + s);
+                    }
+                    // did we just get the EOD null byte?
+                    if (buf[bytes - 1] == 0) {
+                        os.write(buf, 0, bytes - 1); // exclude the EOD token
+                        break;
+                    }
+                    // no EOD, so collect it and read more
+                    os.write(buf, 0, bytes);
+                }
+            } while (bytes > 0);
+
+            // Okay, we've got the report.
+            if (DEBUG) Slog.v(TAG, "processing");
+
+            // Mark the process record as being a native crash so that the
+            // cleanup mechanism knows we're still submitting the report even
+            // though the process will vanish as soon as we let debuggerd
+            // proceed. This isn't relevant for recoverable crashes, as we don't
+            // show the user an "app crashed" dialogue because the app (by
+            // design) didn't crash.
+            if (!gwpAsanRecoverableCrash) {
+                synchronized (mAm) {
+                    synchronized (mAm.mProcLock) {
+                        pr.mErrorState.setCrashing(true);
+                        pr.mErrorState.setForceCrashReport(true);
+                    }
+                }
+            }
+
+            // Crash reporting is synchronous but we want to let debuggerd
+            // go about it business right away, so we spin off the actual
+            // reporting logic on a thread and let it take it's time.
+            final String reportString = new String(os.toByteArray(), "UTF-8");
+            (new NativeCrashReporter(pr, signal, gwpAsanRecoverableCrash, reportString)).start();
         } catch (Exception e) {
             Slog.e(TAG, "Exception dealing with report", e);
             // ugh, fail.
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 46ab330..df9a750 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4087,13 +4087,14 @@
                 return;
         }
 
-        // Forcefully set LE audio volume as a workaround, since in some cases
-        // (like the outgoing call) the value of 'device' is not DEVICE_OUT_BLE_*
-        // even when BLE is connected.
+        // In some cases (like the outgoing or rejected call) the value of 'device' is not
+        // DEVICE_OUT_BLE_* even when BLE is connected. Changing the volume level in such case
+        // may cuase the other devices volume level leaking into the LeAudio device settings.
         if (!AudioSystem.isLeAudioDeviceType(device)) {
-            Log.w(TAG, "setLeAudioVolumeOnModeUpdate got unexpected device=" + device
-                    + ", forcing to device=" + AudioSystem.DEVICE_OUT_BLE_HEADSET);
-            device = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+            Log.w(TAG, "setLeAudioVolumeOnModeUpdate ignoring invalid device="
+                    + device + ", mode=" + mode + ", index=" + index + " maxIndex=" + maxIndex
+                    + " streamType=" + streamType);
+            return;
         }
 
         if (DEBUG_VOL) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/OWNERS b/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
index 01b2cb9..1e41886 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
+++ b/services/core/java/com/android/server/soundtrigger_middleware/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
index 33385af..1e41886 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS
@@ -1 +1 @@
-include /media/aidl/android/media/soundtrigger_middleware/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/voiceinteraction/OWNERS b/services/voiceinteraction/OWNERS
index ef1061b..40e8d26 100644
--- a/services/voiceinteraction/OWNERS
+++ b/services/voiceinteraction/OWNERS
@@ -1 +1,2 @@
 include /core/java/android/service/voice/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
index 01b2cb9..1e41886 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/OWNERS
@@ -1,2 +1 @@
-atneya@google.com
-elaurent@google.com
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 33e6fa7..03e019d 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1214,8 +1214,13 @@
 
     /**
      * Initialize the service state. Set everything to the default value.
+     *
+     * @param legacyMode {@code true} if the device is on IWLAN legacy mode, where IWLAN is
+     * considered as a RAT on WWAN {@link NetworkRegistrationInfo}. {@code false} if the device
+     * is on AP-assisted mode, where IWLAN should be reported through WLAN.
+     * {@link NetworkRegistrationInfo}.
      */
-    private void init() {
+    private void init(boolean legacyMode) {
         if (DBG) Rlog.d(LOG_TAG, "init");
         mVoiceRegState = STATE_OUT_OF_SERVICE;
         mDataRegState = STATE_OUT_OF_SERVICE;
@@ -1247,11 +1252,13 @@
                     .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                     .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
                     .build());
-            addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                    .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                    .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                    .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
-                    .build());
+            if (!legacyMode) {
+                addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                        .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                        .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
+                        .build());
+            }
         }
         mOperatorAlphaLongRaw = null;
         mOperatorAlphaShortRaw = null;
@@ -1260,11 +1267,11 @@
     }
 
     public void setStateOutOfService() {
-        init();
+        init(true);
     }
 
     public void setStateOff() {
-        init();
+        init(true);
         mVoiceRegState = STATE_POWER_OFF;
         mDataRegState = STATE_POWER_OFF;
     }
@@ -1272,11 +1279,14 @@
     /**
      * Set the service state to out-of-service
      *
+     * @param legacyMode {@code true} if the device is on IWLAN legacy mode, where IWLAN is
+     * considered as a RAT on WWAN {@link NetworkRegistrationInfo}. {@code false} if the device
+     * is on AP-assisted mode, where IWLAN should be reported through WLAN.
      * @param powerOff {@code true} if this is a power off case (i.e. Airplane mode on).
      * @hide
      */
-    public void setOutOfService(boolean powerOff) {
-        init();
+    public void setOutOfService(boolean legacyMode, boolean powerOff) {
+        init(legacyMode);
         if (powerOff) {
             mVoiceRegState = STATE_POWER_OFF;
             mDataRegState = STATE_POWER_OFF;
diff --git a/tests/SoundTriggerTestApp/OWNERS b/tests/SoundTriggerTestApp/OWNERS
index 9db19a3..a0fcfc5 100644
--- a/tests/SoundTriggerTestApp/OWNERS
+++ b/tests/SoundTriggerTestApp/OWNERS
@@ -1,2 +1,2 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
 mdooley@google.com
diff --git a/tests/SoundTriggerTests/OWNERS b/tests/SoundTriggerTests/OWNERS
index 816bc6b..1e41886 100644
--- a/tests/SoundTriggerTests/OWNERS
+++ b/tests/SoundTriggerTests/OWNERS
@@ -1 +1 @@
-include /core/java/android/media/soundtrigger/OWNERS
+include /media/java/android/media/soundtrigger/OWNERS
diff --git a/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
new file mode 100644
index 0000000..b94bb41
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java
@@ -0,0 +1,64 @@
+/*
+ * 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 android.os.test;
+
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+import static android.permission.PermissionManager.PERMISSION_HARD_DENIED;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.os.PermissionEnforcer;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Fake for {@link PermissionEnforcer}. Useful for tests wanting to mock the
+ * permission checks of an AIDL service. FakePermissionEnforcer may be passed
+ * to the constructor of the AIDL-generated Stub class.
+ *
+ */
+public class FakePermissionEnforcer extends PermissionEnforcer {
+    private Set<String> mGranted;
+
+    public FakePermissionEnforcer() {
+        mGranted = new HashSet();
+    }
+
+    public void grant(String permission) {
+        mGranted.add(permission);
+    }
+
+    public void revoke(String permission) {
+        mGranted.remove(permission);
+    }
+
+    private boolean granted(String permission) {
+        return mGranted.contains(permission);
+    }
+
+    @Override
+    protected int checkPermission(@NonNull String permission,
+              @NonNull AttributionSource source) {
+        return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+    }
+
+    @Override
+    protected int checkPermission(@NonNull String permission, int pid, int uid) {
+        return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED;
+    }
+}
diff --git a/tests/utils/testutils/java/android/os/test/OWNERS b/tests/utils/testutils/java/android/os/test/OWNERS
new file mode 100644
index 0000000..3a9129e
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/OWNERS
@@ -0,0 +1 @@
+per-file FakePermissionEnforcer.java = file:/tests/EnforcePermission/OWNERS