Merge "Swap orders of GAIA signin and lock setup during Private space setup" into main
diff --git a/Android.bp b/Android.bp
index b456a72..9b4cfde 100644
--- a/Android.bp
+++ b/Android.bp
@@ -107,13 +107,12 @@
plugins: ["androidx.room_room-compiler-plugin"],
- // TODO: b/307330031 - Re-enable the nullaway plugin when the issue is fixed.
- // errorprone: {
- // extra_check_modules: ["//external/nullaway:nullaway_plugin"],
- // javacflags: [
- // "-XepOpt:NullAway:AnnotatedPackages=com.android.settings",
- // ],
- // },
+ errorprone: {
+ extra_check_modules: ["//external/nullaway:nullaway_plugin"],
+ javacflags: [
+ "-XepOpt:NullAway:AnnotatedPackages=com.android.settings",
+ ],
+ },
libs: [
"telephony-common",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 095452c..85a443e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2875,6 +2875,8 @@
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.development.DevelopmentSettingsDashboardFragment" />
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_system"/>
</activity>
<activity
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 14a4a51..6ed0d82 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2946,9 +2946,9 @@
<string name="imei_multi_sim_primary">IMEI (sim slot <xliff:g id="imei_slot_id_primary">%1$d</xliff:g>) (primary)</string>
<!-- About phone screen, summary of the MAC address [CHAR LIMIT=80] -->
<string name="view_saved_network">To view, choose saved network</string>
- <!-- Do not translate. About phone, status item title -->
+ <!-- About phone, status item title. This translation is for specific language only. Like: fr-CA -->
<string name="status_imei">IMEI</string>
- <!-- Do not translate. About phone, status item title -->
+ <!-- About phone, status item title. This translation is for specific language only. Like: fr-CA -->
<string name="status_imei_sv">IMEI SV</string>
<!-- Do not translate. About phone, status item title -->
<string name="status_iccid">ICCID</string>
@@ -10927,6 +10927,9 @@
<!-- Title with application label for media output settings. [CHAR LIMIT=NONE] -->
<string name="media_output_label_title">Play <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
+ <!-- Title for media output settings without media is playing -->
+ <string name="media_output_title_without_playing">Audio will play on</string>
+
<!-- Summary for media output default settings. (this device) [CHAR LIMIT=30] -->
<string name="media_output_default_summary">This device</string>
diff --git a/res/xml/bluetooth_audio_streams.xml b/res/xml/bluetooth_audio_streams.xml
index 206fb34..b419eaa 100644
--- a/res/xml/bluetooth_audio_streams.xml
+++ b/res/xml/bluetooth_audio_streams.xml
@@ -24,7 +24,8 @@
android:key="audio_streams_scan_qr_code"
android:title="@string/bluetooth_find_broadcast_button_scan"
android:icon="@drawable/ic_add_24dp"
- android:summary="@string/audio_streams_qr_code_summary" />
+ android:summary="@string/audio_streams_qr_code_summary"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController" />
<Preference
android:key="audio_streams_active_device"
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index 630acbb..27001d6 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -28,9 +28,9 @@
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.connecteddevice.audiosharing.AudioSharingDevicePreferenceController;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.SettingsUIDeviceConfig;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.overlay.SurveyFeatureProvider;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -87,7 +87,7 @@
+ ", action : "
+ action);
}
- if (Flags.enableLeAudioSharing()) {
+ if (AudioSharingUtils.isFeatureEnabled()) {
use(AudioSharingDevicePreferenceController.class).init(this);
}
use(AvailableMediaDeviceGroupController.class).init(this);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
index 90cf779..8497c9d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
@@ -27,7 +27,6 @@
import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.utils.ThreadUtils;
@@ -51,7 +50,7 @@
@Override
public int getAvailabilityStatus() {
- return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index 7a2f26f..483a74b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -24,7 +24,6 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -39,7 +38,6 @@
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LeAudioProfile;
@@ -309,9 +307,7 @@
@Override
public int getAvailabilityStatus() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
- && Flags.enableLeAudioSharing()
- && mBluetoothDeviceUpdater != null
+ return AudioSharingUtils.isFeatureEnabled() && mBluetoothDeviceUpdater != null
? AVAILABLE_UNSEARCHABLE
: UNSUPPORTED_ON_DEVICE;
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index bdf7f03..5cd86b4 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -32,7 +32,6 @@
import com.android.internal.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
import java.util.ArrayList;
import java.util.Locale;
@@ -73,7 +72,7 @@
Fragment host,
ArrayList<AudioSharingDeviceItem> deviceItems,
DialogEventListener listener) {
- if (!Flags.enableLeAudioSharing()) return;
+ if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
index 1840f58..365aed5 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
@@ -32,7 +32,6 @@
import com.android.internal.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
import java.util.ArrayList;
@@ -66,13 +65,16 @@
* Display the {@link AudioSharingDisconnectDialogFragment} dialog.
*
* @param host The Fragment this dialog will be hosted.
+ * @param deviceItems The existing connected device items in audio sharing session.
+ * @param newDeviceName The name of the latest connected device triggered this dialog.
+ * @param listener The callback to handle the user action on this dialog.
*/
public static void show(
Fragment host,
ArrayList<AudioSharingDeviceItem> deviceItems,
String newDeviceName,
DialogEventListener listener) {
- if (!Flags.enableLeAudioSharing()) return;
+ if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
index b9646ac..589c955 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
@@ -30,7 +30,6 @@
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
import java.util.ArrayList;
import java.util.Locale;
@@ -69,7 +68,7 @@
ArrayList<AudioSharingDeviceItem> deviceItems,
String newDeviceName,
DialogEventListener listener) {
- if (!Flags.enableLeAudioSharing()) return;
+ if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
index b6b4568..294e8b2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPreferenceController.java
@@ -19,7 +19,6 @@
import android.content.Context;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.flags.Flags;
public class AudioSharingPreferenceController extends BasePreferenceController {
private static final String TAG = "AudioSharingPreferenceController";
@@ -33,6 +32,6 @@
@Override
public int getAvailabilityStatus() {
- return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
index 495fad3..13416aa 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingStopDialogFragment.java
@@ -25,7 +25,6 @@
import androidx.fragment.app.FragmentManager;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
public class AudioSharingStopDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioSharingStopDialog";
@@ -50,9 +49,11 @@
* Display the {@link AudioSharingStopDialogFragment} dialog.
*
* @param host The Fragment this dialog will be hosted.
+ * @param newDeviceName The name of the latest connected device triggered this dialog.
+ * @param listener The callback to handle the user action on this dialog.
*/
public static void show(Fragment host, String newDeviceName, DialogEventListener listener) {
- if (!Flags.enableLeAudioSharing()) return;
+ if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 469a387..dc04bee 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -38,7 +38,6 @@
import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
@@ -315,7 +314,7 @@
@Override
public int getAvailabilityStatus() {
- return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
/**
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index a43e098..10cac42 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -20,10 +20,12 @@
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
+import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -244,9 +246,8 @@
Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
return Optional.empty();
}
- var groupedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(manager);
- var leadDevices =
- AudioSharingUtils.buildOrderedConnectedLeadDevices(manager, groupedDevices, false);
+ var groupedDevices = fetchConnectedDevicesByGroupId(manager);
+ var leadDevices = buildOrderedConnectedLeadDevices(manager, groupedDevices, false);
if (!leadDevices.isEmpty() && AudioSharingUtils.isActiveLeAudioDevice(leadDevices.get(0))) {
return Optional.of(leadDevices.get(0));
@@ -262,6 +263,16 @@
() -> Toast.makeText(context, message, Toast.LENGTH_LONG).show());
}
+ /** Returns if the le audio sharing is enabled. */
+ public static boolean isFeatureEnabled() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return Flags.enableLeAudioSharing()
+ && adapter.isLeAudioBroadcastSourceSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED
+ && adapter.isLeAudioBroadcastAssistantSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED;
+ }
+
/** Automatically update active device if needed. */
public static void updateActiveDeviceIfNeeded(LocalBluetoothManager localBtManager) {
if (localBtManager == null) return;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
index 47f70c7..e47e141 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java
@@ -26,7 +26,6 @@
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.flags.Flags;
import java.util.ArrayList;
@@ -64,7 +63,7 @@
Fragment host,
ArrayList<AudioSharingDeviceItem> deviceItems,
DialogEventListener listener) {
- if (!Flags.enableLeAudioSharing()) return;
+ if (!AudioSharingUtils.isFeatureEnabled()) return;
final FragmentManager manager = host.getChildFragmentManager();
sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
index 562427f..a673cb4 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
@@ -17,6 +17,7 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import com.android.settings.R;
@@ -58,10 +59,17 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ use(AudioStreamsScanQrCodeController.class).setFragment(this);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ // TODO(chelseahao): implementation.
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
new file mode 100644
index 0000000..12b46e5
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
+import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+public class AudioStreamsScanQrCodeController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private static final String TAG = "AudioStreamsProgressCategoryController";
+ private static final boolean DEBUG = BluetoothUtils.D;
+ private static final String KEY = "audio_streams_scan_qr_code";
+ private static final int REQUEST_SCAN_BT_BROADCAST_QR_CODE = 0;
+ private final BluetoothCallback mBluetoothCallback =
+ new BluetoothCallback() {
+ @Override
+ public void onActiveDeviceChanged(
+ @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ updateVisibility();
+ }
+ }
+ };
+
+ private final LocalBluetoothManager mLocalBtManager;
+ private AudioStreamsDashboardFragment mFragment;
+ private Preference mPreference;
+
+ public AudioStreamsScanQrCodeController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mLocalBtManager = Utils.getLocalBtManager(mContext);
+ }
+
+ public void setFragment(AudioStreamsDashboardFragment fragment) {
+ mFragment = fragment;
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mLocalBtManager != null) {
+ mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
+ }
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mLocalBtManager != null) {
+ mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ if (mPreference == null) {
+ Log.w(TAG, "displayPreference() mPreference is null!");
+ return;
+ }
+ mPreference.setOnPreferenceClickListener(
+ preference -> {
+ if (mFragment == null) {
+ Log.w(TAG, "displayPreference() mFragment is null!");
+ return false;
+ }
+ if (preference.getKey().equals(KEY)) {
+ Intent intent = new Intent(mContext, QrCodeScanModeActivity.class);
+ intent.setAction(
+ BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER);
+ mFragment.startActivityForResult(intent, REQUEST_SCAN_BT_BROADCAST_QR_CODE);
+ if (DEBUG) {
+ Log.w(TAG, "displayPreference() sent intent : " + intent);
+ }
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private void updateVisibility() {
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ boolean hasActiveLe =
+ AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent();
+ ThreadUtils.postOnMainThread(() -> mPreference.setVisible(hasActiveLe));
+ });
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
index d6d0634..091ebcb 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
@@ -16,10 +16,6 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode;
-import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK;
-import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP;
-
-import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
@@ -44,9 +40,6 @@
private static final boolean DEBUG = BluetoothUtils.D;
private static final String TAG = "QrCodeScanModeActivity";
- private boolean mIsGroupOp;
- private BluetoothDevice mSink;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -88,8 +81,6 @@
Log.d(TAG, "showQrCodeScannerFragment");
}
- mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK);
- mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false);
if (DEBUG) {
Log.d(TAG, "get extra from intent");
}
diff --git a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
index c24ad22..dfe571a 100644
--- a/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
+++ b/src/com/android/settings/display/PeakRefreshRatePreferenceController.java
@@ -65,7 +65,7 @@
updateState(mPreference);
}
};
- mPeakRefreshRate = findHighestRefreshRateForDefaultDisplay(context);
+ mPeakRefreshRate = Math.round(findHighestRefreshRateForDefaultDisplay(context));
Log.d(
TAG,
"DEFAULT_REFRESH_RATE : "
diff --git a/src/com/android/settings/media/MediaOutputIndicatorSlice.java b/src/com/android/settings/media/MediaOutputIndicatorSlice.java
index e2232e7..bb075c2 100644
--- a/src/com/android/settings/media/MediaOutputIndicatorSlice.java
+++ b/src/com/android/settings/media/MediaOutputIndicatorSlice.java
@@ -17,6 +17,7 @@
package com.android.settings.media;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
import android.annotation.ColorInt;
import android.content.Context;
@@ -58,7 +59,12 @@
}
final IconCompat icon = IconCompat.createWithResource(mContext,
com.android.internal.R.drawable.ic_settings_bluetooth);
- final CharSequence title = mContext.getString(R.string.media_output_label_title,
+ final int stringRes = enableOutputSwitcherForSystemRouting()
+ ? (getWorker().getActiveLocalMediaController() != null
+ ? R.string.media_output_label_title
+ : R.string.media_output_title_without_playing)
+ : R.string.media_output_label_title;
+ final CharSequence title = mContext.getString(stringRes,
Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
final SliceAction primarySliceAction = SliceAction.create(
getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title);
@@ -117,28 +123,36 @@
// 2. worker is not null
// 3. Available devices are more than 0
// 4. The local media session is active and the state is playing.
+ // - if !enableOutputSwitcherForSystemRouting(), (4) will be bypass.
return getWorker() != null
&& !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)
&& getWorker().getMediaDevices().size() > 0
- && getWorker().getActiveLocalMediaController() != null;
+ && (enableOutputSwitcherForSystemRouting()
+ ? true : getWorker().getActiveLocalMediaController() != null);
}
@Override
public void onNotifyChange(Intent intent) {
final MediaController mediaController = getWorker().getActiveLocalMediaController();
- if (mediaController == null) {
+ // Launch media output dialog
+ if (enableOutputSwitcherForSystemRouting() && mediaController == null) {
+ mContext.sendBroadcast(new Intent()
+ .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
+ .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG));
+ } else if (mediaController != null) {
+ mContext.sendBroadcast(new Intent()
+ .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
+ .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
+ .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
+ mediaController.getSessionToken())
+ .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+ mediaController.getPackageName()));
+ } else {
Log.d(TAG, "No active local media controller");
return;
}
- // Launch media output dialog
- mContext.sendBroadcast(new Intent()
- .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
- .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
- .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
- mediaController.getSessionToken())
- .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
- mediaController.getPackageName()));
+
// Dismiss volume panel
mContext.sendBroadcast(new Intent()
.setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)
diff --git a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
index c874a5e..101364b 100644
--- a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
+++ b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
@@ -15,20 +15,22 @@
*/
package com.android.settings.security;
-
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
-import com.android.settings.Utils;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
/** Preference controller for content protection toggle switch bar. */
public class ContentProtectionTogglePreferenceController extends TogglePreferenceController
@@ -37,9 +39,9 @@
@VisibleForTesting
static final String KEY_CONTENT_PROTECTION_PREFERENCE = "content_protection_user_consent";
- private SettingsMainSwitchPreference mSwitchBar;
+ @Nullable private SettingsMainSwitchPreference mSwitchBar;
+ @Nullable private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
private final ContentResolver mContentResolver;
- private final boolean isFullyManagedDevice = Utils.getDeviceOwnerComponent(mContext) != null;
public ContentProtectionTogglePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -53,7 +55,7 @@
@Override
public boolean isChecked() {
- if (isFullyManagedDevice) {
+ if (mEnforcedAdmin != null) {
// If fully managed device, it should always unchecked
return false;
}
@@ -70,12 +72,25 @@
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
+ final Preference preference = screen.findPreference(getPreferenceKey());
- mSwitchBar = screen.findPreference(getPreferenceKey());
- mSwitchBar.addOnSwitchChangeListener(this);
- if (isFullyManagedDevice) {
- // If fully managed device, the switch bar is greyed out
- mSwitchBar.setEnabled(false);
+ if (preference instanceof SettingsMainSwitchPreference) {
+ mSwitchBar = (SettingsMainSwitchPreference) preference;
+ mSwitchBar.addOnSwitchChangeListener(this);
+ }
+ }
+
+ /**
+ * Temporary workaround for SettingsMainSwitchPreference.setDisabledByAdmin without user
+ * restriction.
+ */
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ // Assign the value to mEnforcedAdmin since it's needed in isChecked()
+ mEnforcedAdmin = getEnforcedAdmin();
+ if (mSwitchBar != null && mEnforcedAdmin != null) {
+ mSwitchBar.setDisabledByAdmin(mEnforcedAdmin);
}
}
@@ -90,4 +105,9 @@
public int getSliceHighlightMenuRes() {
return R.string.menu_key_security;
}
+
+ @VisibleForTesting
+ protected RestrictedLockUtils.EnforcedAdmin getEnforcedAdmin() {
+ return RestrictedLockUtilsInternal.getDeviceOwner(mContext);
+ }
}
diff --git a/src/com/android/settings/sound/AudioSwitchPreferenceController.java b/src/com/android/settings/sound/AudioSwitchPreferenceController.java
index 6475257..b570b2d 100644
--- a/src/com/android/settings/sound/AudioSwitchPreferenceController.java
+++ b/src/com/android/settings/sound/AudioSwitchPreferenceController.java
@@ -18,6 +18,9 @@
import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION;
+import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
+
+import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -28,6 +31,8 @@
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRouter;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
import android.os.Handler;
import android.os.Looper;
import android.util.FeatureFlagUtils;
@@ -79,6 +84,8 @@
private final WiredHeadsetBroadcastReceiver mReceiver;
private final Handler mHandler;
private LocalBluetoothManager mLocalBluetoothManager;
+ @Nullable private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener;
+ @Nullable private MediaSessionManager mMediaSessionManager;
public interface AudioSwitchCallback {
void onPreferenceDataChanged(ListPreference preference);
@@ -107,6 +114,14 @@
return;
}
mProfileManager = mLocalBluetoothManager.getProfileManager();
+
+ if (enableOutputSwitcherForSystemRouting()) {
+ mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+ mSessionListener = new SessionChangeListener();
+ } else {
+ mMediaSessionManager = null;
+ mSessionListener = null;
+ }
}
/**
@@ -329,13 +344,27 @@
// Register for misc other intent broadcasts.
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION);
- mContext.registerReceiver(mReceiver, intentFilter);
+
+ if (enableOutputSwitcherForSystemRouting()) {
+ mContext.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
+ if (mMediaSessionManager != null) {
+ mMediaSessionManager.addOnActiveSessionsChangedListener(
+ mSessionListener, null, mHandler);
+ }
+ } else {
+ mContext.registerReceiver(mReceiver, intentFilter);
+ }
}
private void unregister() {
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);
mContext.unregisterReceiver(mReceiver);
+ if (enableOutputSwitcherForSystemRouting()) {
+ if (mMediaSessionManager != null) {
+ mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionListener);
+ }
+ }
}
/** Notifications of audio device connection and disconnection events. */
@@ -362,4 +391,12 @@
}
}
}
+
+ private class SessionChangeListener
+ implements MediaSessionManager.OnActiveSessionsChangedListener {
+ @Override
+ public void onActiveSessionsChanged(List<MediaController> controllers) {
+ updateState(mPreference);
+ }
+ }
}
diff --git a/src/com/android/settings/sound/MediaOutputPreferenceController.java b/src/com/android/settings/sound/MediaOutputPreferenceController.java
index 758f7e3..47be7fe 100644
--- a/src/com/android/settings/sound/MediaOutputPreferenceController.java
+++ b/src/com/android/settings/sound/MediaOutputPreferenceController.java
@@ -16,6 +16,9 @@
package com.android.settings.sound;
+import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
+
+import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
@@ -46,21 +49,22 @@
*/
public class MediaOutputPreferenceController extends AudioSwitchPreferenceController {
- private MediaController mMediaController;
+ private static final String TAG = "MediaOutputPreferenceController";
+ @Nullable private MediaController mMediaController;
+ private MediaSessionManager mMediaSessionManager;
public MediaOutputPreferenceController(Context context, String key) {
super(context, key);
- mMediaController = MediaOutputUtils.getActiveLocalMediaController(context.getSystemService(
- MediaSessionManager.class));
+ mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
+ mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- if (!Utils.isAudioModeOngoingCall(mContext) && mMediaController != null) {
- mPreference.setVisible(true);
- }
+ mPreference.setVisible(!Utils.isAudioModeOngoingCall(mContext)
+ && (enableOutputSwitcherForSystemRouting() ? true : mMediaController != null));
}
@Override
@@ -70,11 +74,16 @@
return;
}
- if (mMediaController == null) {
- // No active local playback
- return;
+ if (enableOutputSwitcherForSystemRouting()) {
+ mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager);
+ } else {
+ if (mMediaController == null) {
+ // No active local playback
+ return;
+ }
}
+
if (Utils.isAudioModeOngoingCall(mContext)) {
// Ongoing call status, switch entry for media will be disabled.
mPreference.setVisible(false);
@@ -95,9 +104,14 @@
|| (connectedLeAudioDevices != null && !connectedLeAudioDevices.isEmpty()))) {
activeDevice = findActiveDevice();
}
- mPreference.setTitle(mContext.getString(R.string.media_output_label_title,
- com.android.settings.Utils.getApplicationLabel(mContext,
- mMediaController.getPackageName())));
+
+ if (mMediaController == null) {
+ mPreference.setTitle(mContext.getString(R.string.media_output_title_without_playing));
+ } else {
+ mPreference.setTitle(mContext.getString(R.string.media_output_label_title,
+ com.android.settings.Utils.getApplicationLabel(mContext,
+ mMediaController.getPackageName())));
+ }
mPreference.setSummary((activeDevice == null) ?
mContext.getText(R.string.media_output_default_summary) :
activeDevice.getAlias());
@@ -145,13 +159,19 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
- mContext.sendBroadcast(new Intent()
- .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
- .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
- .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
- mMediaController.getPackageName())
- .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
- mMediaController.getSessionToken()));
+ if (enableOutputSwitcherForSystemRouting() && mMediaController == null) {
+ mContext.sendBroadcast(new Intent()
+ .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG)
+ .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME));
+ } else if (mMediaController != null) {
+ mContext.sendBroadcast(new Intent()
+ .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
+ .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
+ .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
+ mMediaController.getPackageName())
+ .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
+ mMediaController.getSessionToken()));
+ }
return true;
}
return false;
diff --git a/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt b/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt
index 7615442..345d931 100644
--- a/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppForceStopButton.kt
@@ -23,7 +23,9 @@
import androidx.compose.material.icons.outlined.Report
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -35,6 +37,9 @@
import com.android.settingslib.spaprivileged.model.app.hasFlag
import com.android.settingslib.spaprivileged.model.app.isActiveAdmin
import com.android.settingslib.spaprivileged.model.app.userId
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
class AppForceStopButton(
private val packageInfoPresenter: PackageInfoPresenter,
@@ -47,9 +52,13 @@
fun getActionButton(app: ApplicationInfo): ActionButton {
val dialogPresenter = confirmDialogPresenter()
return ActionButton(
- text = context.getString(R.string.force_stop),
+ text = stringResource(R.string.force_stop),
imageVector = Icons.Outlined.Report,
- enabled = isForceStopButtonEnable(app),
+ enabled = remember(app) {
+ flow {
+ emit(isForceStopButtonEnable(app))
+ }.flowOn(Dispatchers.Default)
+ }.collectAsStateWithLifecycle(false).value,
) { onForceStopButtonClicked(app, dialogPresenter) }
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 3b7f579..6882963 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -32,10 +32,10 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavType
import androidx.navigation.navArgument
-import com.android.settings.flags.Flags
import com.android.settings.R
import com.android.settings.applications.AppInfoBase
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.flags.Flags
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.app.appcompat.UserAspectRatioAppPreference
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
@@ -45,7 +45,6 @@
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
@@ -75,7 +74,7 @@
PackageInfoPresenter(context, packageName, userId, coroutineScope)
}
AppInfoSettings(packageInfoPresenter)
- packageInfoPresenter.PackageRemoveDetector()
+ packageInfoPresenter.PackageFullyRemovedEffect()
}
@Composable
@@ -120,8 +119,7 @@
@Composable
private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
- LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
- val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
+ val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?:return
val app = checkNotNull(packageInfo.applicationInfo)
val featureFlags: FeatureFlags = FeatureFlagsImpl()
RegularScaffold(
@@ -131,7 +129,7 @@
AppInfoSettingsMoreOptions(packageInfoPresenter, app)
}
) {
- val appInfoProvider = remember { AppInfoProvider(packageInfo) }
+ val appInfoProvider = remember(packageInfo) { AppInfoProvider(packageInfo) }
appInfoProvider.AppInfo()
diff --git a/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
index 760d375..c8e8d35 100644
--- a/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/CloneAppInfoSettings.kt
@@ -28,7 +28,6 @@
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spaprivileged.model.app.toRoute
import com.android.settingslib.spaprivileged.template.app.AppInfoProvider
@@ -54,7 +53,7 @@
PackageInfoPresenter(context, packageName, userId, coroutineScope)
}
CloneAppInfoSettings(packageInfoPresenter)
- packageInfoPresenter.PackageRemoveDetector()
+ packageInfoPresenter.PackageFullyRemovedEffect()
}
@Composable
@@ -70,7 +69,6 @@
@Composable
private fun CloneAppInfoSettings(packageInfoPresenter: PackageInfoPresenter) {
- LifecycleEffect(onStart = { packageInfoPresenter.reloadPackageInfo() })
val packageInfo = packageInfoPresenter.flow.collectAsStateWithLifecycle().value ?: return
RegularScaffold(
title = stringResource(R.string.application_info_label),
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 6eee72e..8d0f0bb 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -32,14 +32,20 @@
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spaprivileged.framework.common.activityManager
import com.android.settingslib.spaprivileged.framework.common.asUser
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverAsUserFlow
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
private const val TAG = "PackageInfoPresenter"
@@ -58,34 +64,33 @@
private val userHandle = UserHandle.of(userId)
val userContext by lazy { context.asUser(userHandle) }
val userPackageManager: PackageManager by lazy { userContext.packageManager }
- private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
- val flow: StateFlow<PackageInfo?> = _flow
-
- fun reloadPackageInfo() {
- coroutineScope.launch(Dispatchers.IO) {
- _flow.value = getPackageInfo()
- }
- }
+ val flow: StateFlow<PackageInfo?> = merge(
+ flowOf(null), // kick an initial value
+ context.broadcastReceiverAsUserFlow(
+ intentFilter = IntentFilter().apply {
+ addAction(Intent.ACTION_PACKAGE_CHANGED)
+ addAction(Intent.ACTION_PACKAGE_REPLACED)
+ addAction(Intent.ACTION_PACKAGE_RESTARTED)
+ addDataScheme("package")
+ },
+ userHandle = userHandle,
+ ),
+ ).map { getPackageInfo() }
+ .stateIn(coroutineScope + Dispatchers.Default, SharingStarted.Eagerly, null)
/**
- * Detects the package removed event.
+ * Detects the package fully removed event, and close the current page.
*/
@Composable
- fun PackageRemoveDetector() {
- val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED).apply {
+ fun PackageFullyRemovedEffect() {
+ val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_FULLY_REMOVED).apply {
addDataScheme("package")
}
val navController = LocalNavController.current
DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
if (packageName == intent.data?.schemeSpecificPart) {
- val packageInfo = flow.value
- if (packageInfo != null && packageInfo.applicationInfo?.isSystemApp == true) {
- // System app still exists after uninstalling the updates, refresh the page.
- reloadPackageInfo()
- } else {
- navController.navigateBack()
- }
+ navController.navigateBack()
}
}
}
@@ -97,7 +102,6 @@
userPackageManager.setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
)
- reloadPackageInfo()
}
}
@@ -108,7 +112,6 @@
userPackageManager.setApplicationEnabledSetting(
packageName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
)
- reloadPackageInfo()
}
}
@@ -123,7 +126,6 @@
logAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
coroutineScope.launch(Dispatchers.IO) {
userPackageManager.deletePackageAsUser(packageName, null, 0, userId)
- reloadPackageInfo()
}
}
@@ -133,7 +135,6 @@
coroutineScope.launch(Dispatchers.Default) {
Log.d(TAG, "Stopping package $packageName")
context.activityManager.forceStopPackageAsUser(packageName, userId)
- reloadPackageInfo()
}
}
@@ -141,7 +142,7 @@
metricsFeatureProvider.action(context, category, packageName)
}
- private fun getPackageInfo() =
+ private fun getPackageInfo(): PackageInfo? =
packageManagers.getPackageInfoAsUser(
packageName = packageName,
flags = PackageManager.MATCH_ANY_USER.toLong() or
diff --git a/src/com/android/settings/system/DeveloperOptionsController.kt b/src/com/android/settings/system/DeveloperOptionsController.kt
index fe7fb4d..6874ec7 100644
--- a/src/com/android/settings/system/DeveloperOptionsController.kt
+++ b/src/com/android/settings/system/DeveloperOptionsController.kt
@@ -34,6 +34,7 @@
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsIcon
+import com.android.settingslib.spaprivileged.framework.common.userManager
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
@@ -41,7 +42,9 @@
class DeveloperOptionsController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
- override fun getAvailabilityStatus() = AVAILABLE
+ override fun getAvailabilityStatus() =
+ if (mContext.userManager.isAdminUser) AVAILABLE
+ else DISABLED_FOR_USER
private val isDevelopmentSettingsEnabledFlow = context.settingsGlobalBooleanFlow(
name = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
diff --git a/src/com/android/settings/widget/SettingsMainSwitchPreference.java b/src/com/android/settings/widget/SettingsMainSwitchPreference.java
index fc43e58..9f6d787 100644
--- a/src/com/android/settings/widget/SettingsMainSwitchPreference.java
+++ b/src/com/android/settings/widget/SettingsMainSwitchPreference.java
@@ -78,7 +78,7 @@
holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
- if (mRestrictedHelper != null) {
+ if (mEnforcedAdmin == null && mRestrictedHelper != null) {
mEnforcedAdmin = mRestrictedHelper.checkRestrictionEnforced();
}
mMainSwitchBar = (SettingsMainSwitchBar) holder.findViewById(R.id.main_switch_bar);
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index fa55f35..6d3c748 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -50,6 +50,8 @@
"src/**/*.kt",
],
+ test_suites: ["general-tests"],
+
static_libs: [
"Robolectric_shadows_androidx_fragment_upstream",
"Settings_robolectric_meta_service_file",
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
index a91f627..652bd03 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java
@@ -18,6 +18,7 @@
package com.android.settings.media;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
+import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +39,7 @@
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Process;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import androidx.slice.Slice;
@@ -55,6 +57,7 @@
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -96,6 +99,9 @@
@Mock
private Drawable mTestDrawable;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Context mContext;
private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice;
private AudioManager mAudioManager;
@@ -255,6 +261,34 @@
}
@Test
+ public void onNotifyChange_withoutMediaControllerFlagEnabled_verifyIntentExtra() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ doReturn(null).when(sMediaOutputIndicatorWorker)
+ .getActiveLocalMediaController();
+ ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
+
+ mMediaOutputIndicatorSlice.onNotifyChange(null);
+ verify(mContext, times(2)).sendBroadcast(argument.capture());
+ List<Intent> intentList = argument.getAllValues();
+ Intent intent = intentList.get(0);
+
+ assertThat(intent.getAction()).isEqualTo(
+ MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
+ assertThat(TextUtils.equals(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME,
+ intent.getPackage())).isTrue();
+ }
+
+ @Test
+ public void onNotifyChange_withoutMediaControllerFlagDisabled_doNothing() {
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ doReturn(null).when(sMediaOutputIndicatorWorker)
+ .getActiveLocalMediaController();
+
+ mMediaOutputIndicatorSlice.onNotifyChange(null);
+ }
+
+
+ @Test
public void isVisible_allConditionMatched_returnTrue() {
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mDevices.add(mDevice1);
@@ -268,6 +302,7 @@
@Test
public void isVisible_noActiveSession_returnFalse() {
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mDevices.add(mDevice1);
@@ -278,6 +313,19 @@
assertThat(mMediaOutputIndicatorSlice.isVisible()).isFalse();
}
+ @Test
+ public void isVisible_noActiveSession_returnTrue() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ mDevices.add(mDevice1);
+
+ when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
+ doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
+ .getActiveLocalMediaController();
+
+ assertThat(mMediaOutputIndicatorSlice.isVisible()).isTrue();
+ }
+
private void initPackage() {
mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
mAppInfo = new ApplicationInfo();
diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java
index 63b6c3e..50e9a5c 100644
--- a/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/ContentProtectionTogglePreferenceControllerTest.java
@@ -13,16 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.settings.security;
import static com.android.settings.security.ContentProtectionTogglePreferenceController.KEY_CONTENT_PROTECTION_PREFERENCE;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.ComponentName;
import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
@@ -32,6 +34,7 @@
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settings.widget.SettingsMainSwitchPreference;
+import com.android.settingslib.RestrictedLockUtils;
import org.junit.After;
import org.junit.Before;
@@ -55,8 +58,9 @@
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Mock private PreferenceScreen mScreen;
+ @Mock private PreferenceScreen mMockScreen;
+ private RestrictedLockUtils.EnforcedAdmin mAdmin;
private SettingsMainSwitchPreference mSwitchPreference;
private final Context mContext = ApplicationProvider.getApplicationContext();
private ContentProtectionTogglePreferenceController mController;
@@ -65,9 +69,10 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new ContentProtectionTogglePreferenceController(mContext, "key");
+ mController = new TestContentProtectionTogglePreferenceController();
mSwitchPreference = new SettingsMainSwitchPreference(mContext);
- when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mSwitchPreference);
+ when(mMockScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn(mSwitchPreference);
mSettingBackupValue = getContentProtectionGlobalSetting();
Settings.Global.putInt(mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 0);
}
@@ -78,6 +83,7 @@
mContext.getContentResolver(),
KEY_CONTENT_PROTECTION_PREFERENCE,
mSettingBackupValue);
+ ShadowUtils.reset();
}
@Test
@@ -86,6 +92,49 @@
}
@Test
+ public void displayPreference() {
+ setUpFullyManagedMode();
+ SettingsMainSwitchPreference mockSwitchPreference =
+ mock(SettingsMainSwitchPreference.class);
+ when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+ when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
+
+ mController = new TestContentProtectionTogglePreferenceController();
+ mController.displayPreference(mMockScreen);
+
+ assertThat(mockSwitchPreference).isNotNull();
+ }
+
+ @Test
+ public void updateState_notFullyManagedMode_enabled() {
+ SettingsMainSwitchPreference mockSwitchPreference =
+ mock(SettingsMainSwitchPreference.class);
+ when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+ when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
+
+ mController = new TestContentProtectionTogglePreferenceController();
+ mController.displayPreference(mMockScreen);
+ mController.updateState(mockSwitchPreference);
+
+ verify(mockSwitchPreference, never()).setDisabledByAdmin(any());
+ }
+
+ @Test
+ public void updateState_fullyManagedMode_disabled() {
+ setUpFullyManagedMode();
+ SettingsMainSwitchPreference mockSwitchPreference =
+ mock(SettingsMainSwitchPreference.class);
+ when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+ when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
+
+ mController = new TestContentProtectionTogglePreferenceController();
+ mController.displayPreference(mMockScreen);
+ mController.updateState(mockSwitchPreference);
+
+ verify(mockSwitchPreference).setDisabledByAdmin(mAdmin);
+ }
+
+ @Test
public void isChecked_settingTurnOn() {
Settings.Global.putInt(mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 1);
@@ -94,15 +143,18 @@
@Test
public void isChecked_fullyManagedMode_settingTurnOff() {
- final ComponentName componentName =
- ComponentName.unflattenFromString("com.android.test/.DeviceAdminReceiver");
- ShadowUtils.setDeviceOwnerComponent(componentName);
+ setUpFullyManagedMode();
Settings.Global.putInt(mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 1);
+ SettingsMainSwitchPreference mockSwitchPreference =
+ mock(SettingsMainSwitchPreference.class);
+ when(mMockScreen.findPreference(any())).thenReturn(mockSwitchPreference);
+ when(mockSwitchPreference.getKey()).thenReturn(mController.getPreferenceKey());
- ContentProtectionTogglePreferenceController controller =
- new ContentProtectionTogglePreferenceController(mContext, "key");
+ mController = new TestContentProtectionTogglePreferenceController();
+ mController.displayPreference(mMockScreen);
+ mController.updateState(mockSwitchPreference);
- assertThat(controller.isChecked()).isFalse();
+ assertThat(mController.isChecked()).isFalse();
}
@Test
@@ -122,7 +174,6 @@
@Test
public void onSwitchChanged_switchChecked_manuallyEnabled() {
- mController.displayPreference(mScreen);
mController.setChecked(false);
mController.onCheckedChanged(/* switchView= */ null, /* isChecked= */ true);
@@ -132,8 +183,6 @@
@Test
public void onSwitchChanged_switchUnchecked_manuallyDisabled() {
- mController.displayPreference(mScreen);
-
mController.onCheckedChanged(/* switchView= */ null, /* isChecked= */ false);
assertThat(getContentProtectionGlobalSetting()).isEqualTo(-1);
@@ -143,4 +192,21 @@
return Settings.Global.getInt(
mContext.getContentResolver(), KEY_CONTENT_PROTECTION_PREFERENCE, 0);
}
+
+ private void setUpFullyManagedMode() {
+ mAdmin = new RestrictedLockUtils.EnforcedAdmin();
+ }
+
+ private class TestContentProtectionTogglePreferenceController
+ extends ContentProtectionTogglePreferenceController {
+
+ TestContentProtectionTogglePreferenceController() {
+ super(ContentProtectionTogglePreferenceControllerTest.this.mContext, "key");
+ }
+
+ @Override
+ protected RestrictedLockUtils.EnforcedAdmin getEnforcedAdmin() {
+ return mAdmin;
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
index 151d1f2..a272d9c 100644
--- a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
@@ -21,10 +21,12 @@
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -38,6 +40,8 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.FeatureFlagUtils;
import androidx.preference.ListPreference;
@@ -61,6 +65,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -113,6 +118,9 @@
@Mock
private CachedBluetoothDevice mCachedBluetoothDeviceR;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Context mContext;
private PreferenceScreen mScreen;
private ListPreference mPreference;
@@ -238,6 +246,7 @@
@Test
public void onStart_shouldRegisterCallbackAndRegisterReceiver() {
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mController.onStart();
verify(mLocalBluetoothManager.getEventManager()).registerCallback(
@@ -248,6 +257,7 @@
@Test
public void onStop_shouldUnregisterCallbackAndUnregisterReceiver() {
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mController.onStart();
mController.onStop();
@@ -257,6 +267,45 @@
verify(mLocalBluetoothManager).setForegroundActivity(null);
}
+ @Test
+ public void onStart_shouldRegisterCallbackAndRegisterReceiverWithDefaultMediaOutput() {
+ MediaSessionManager mediaSessionManager =
+ spy(mContext.getSystemService(MediaSessionManager.class));
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager);
+ mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
+
+ mController.onStart();
+
+ verify(mLocalBluetoothManager.getEventManager()).registerCallback(
+ any(BluetoothCallback.class));
+ verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
+ eq(Context.RECEIVER_NOT_EXPORTED));
+ verify(mLocalBluetoothManager).setForegroundActivity(mContext);
+ verify(mediaSessionManager).addOnActiveSessionsChangedListener(
+ any(MediaSessionManager.OnActiveSessionsChangedListener.class), any(), any());
+ }
+
+
+ @Test
+ public void onStop_shouldUnregisterCallbackAndUnregisterReceiverWithDefaultMediaOutput() {
+ MediaSessionManager mediaSessionManager =
+ spy(mContext.getSystemService(MediaSessionManager.class));
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mediaSessionManager);
+ mController = new AudioSwitchPreferenceControllerTestable(mContext, TEST_KEY);
+ mController.onStart();
+
+ mController.onStop();
+
+ verify(mLocalBluetoothManager.getEventManager()).unregisterCallback(
+ any(BluetoothCallback.class));
+ verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
+ verify(mLocalBluetoothManager).setForegroundActivity(null);
+ verify(mediaSessionManager).removeOnActiveSessionsChangedListener(
+ any(MediaSessionManager.OnActiveSessionsChangedListener.class));
+ }
+
/**
* Audio stream output to bluetooth sco headset which is the subset of all sco device.
* isStreamFromOutputDevice should return true.
diff --git a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java
index 5a92a08..b9f9b16 100644
--- a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java
@@ -21,6 +21,8 @@
import static android.media.AudioSystem.DEVICE_OUT_EARPIECE;
import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
+import static com.android.settingslib.media.flags.Flags.FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -45,6 +47,7 @@
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -66,6 +69,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -103,6 +107,9 @@
private static final String TEST_PACKAGE_NAME = "com.test.packagename";
private static final String TEST_APPLICATION_LABEL = "APP Test Label";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private LocalBluetoothManager mLocalManager;
@Mock
@@ -227,6 +234,8 @@
mScreen.addPreference(mPreference);
mController.displayPreference(mScreen);
mController.setCallback(mAudioSwitchPreferenceCallback);
+
+ mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
}
@After
@@ -314,6 +323,7 @@
@Test
public void updateState_noActiveLocalPlayback_noTitle() {
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
mPlaybackState = new PlaybackState.Builder()
.setState(PlaybackState.STATE_NONE, 0, 1)
.build();
@@ -326,6 +336,48 @@
}
@Test
+ public void updateState_noActiveLocalPlayback_checkTitle() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ mPlaybackState = new PlaybackState.Builder()
+ .setState(PlaybackState.STATE_NONE, 0, 1)
+ .build();
+ when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+ mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+ mController.displayPreference(mScreen);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.media_output_title_without_playing,
+ TEST_APPLICATION_LABEL));
+ }
+
+ @Test
+ public void updateState_withNullMediaController_noTitle() {
+ mSetFlagsRule.disableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ mMediaControllers.clear();
+ mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.getTitle()).isNull();
+ }
+
+ @Test
+ public void updateState_withNullMediaController_checkTitle() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ mMediaControllers.clear();
+ mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+ mController.displayPreference(mScreen);
+
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.getTitle().toString()).isEqualTo(
+ mContext.getString(R.string.media_output_title_without_playing,
+ TEST_APPLICATION_LABEL));
+ }
+
+ @Test
public void updateState_withActiveLocalPlayback_checkTitle() {
initPackage();
mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
@@ -349,6 +401,39 @@
.isEqualTo(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
}
+ @Test
+ public void handlePreferenceTreeClick_WithNoLocalPlaybackFlagEnabled_verifyIntentExtra() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ mPlaybackState = new PlaybackState.Builder()
+ .setState(PlaybackState.STATE_NONE, 0, 1)
+ .build();
+ when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+ mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+ mPreference.setKey(TEST_KEY);
+
+ mController.handlePreferenceTreeClick(mPreference);
+
+ verify(mContext).sendBroadcast(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getAction())
+ .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
+ }
+
+ @Test
+ public void handlePreferenceTreeClick_WithNullControllerFlagEnabled_verifyIntentExtra() {
+ mSetFlagsRule.enableFlags(FLAG_ENABLE_OUTPUT_SWITCHER_FOR_SYSTEM_ROUTING);
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ mMediaControllers.clear();
+ mController = new MediaOutputPreferenceController(mContext, TEST_KEY);
+ mPreference.setKey(TEST_KEY);
+
+ mController.handlePreferenceTreeClick(mPreference);
+
+ verify(mContext).sendBroadcast(intentCaptor.capture());
+ assertThat(intentCaptor.getValue().getAction())
+ .isEqualTo(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG);
+ }
+
/**
* Default status
* Preference should be invisible
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt
index 6c5cb85..ecb540c 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/PackageInfoPresenterTest.kt
@@ -20,8 +20,6 @@
import android.app.settings.SettingsEnums
import android.content.Context
import android.content.Intent
-import android.content.pm.FakeFeatureFlagsImpl
-import android.content.pm.Flags
import android.content.pm.PackageManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,91 +28,79 @@
import com.android.settingslib.spaprivileged.framework.common.activityManager
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Rule
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.verify
-import org.mockito.Spy
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class PackageInfoPresenterTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
- @Spy
- private val context: Context = ApplicationProvider.getApplicationContext()
+ private val mockPackageManager = mock<PackageManager>()
- @Mock
- private lateinit var packageManager: PackageManager
+ private val mockActivityManager = mock<ActivityManager>()
- @Mock
- private lateinit var activityManager: ActivityManager
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { packageManager } doReturn mockPackageManager
+ on { activityManager } doReturn mockActivityManager
+ doNothing().whenever(mock).startActivityAsUser(any(), any())
+ mock.mockAsUser()
+ }
- @Mock
- private lateinit var packageManagers: IPackageManagers
+ private val packageManagers = mock<IPackageManagers>()
private val fakeFeatureFactory = FakeFeatureFactory()
private val metricsFeatureProvider = fakeFeatureFactory.metricsFeatureProvider
- @Before
- fun setUp() {
- context.mockAsUser()
- whenever(context.packageManager).thenReturn(packageManager)
- whenever(context.activityManager).thenReturn(activityManager)
- }
-
@Test
- fun enable() = runTest {
- coroutineScope {
- val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+ fun enable() = runBlocking {
+ val packageInfoPresenter =
+ PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
- packageInfoPresenter.enable()
- }
+ packageInfoPresenter.enable()
+ delay(100)
verifyAction(SettingsEnums.ACTION_SETTINGS_ENABLE_APP)
- verify(packageManager).setApplicationEnabledSetting(
+ verify(mockPackageManager).setApplicationEnabledSetting(
PACKAGE_NAME, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 0
)
}
@Test
- fun disable() = runTest {
- coroutineScope {
- val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+ fun disable() = runBlocking {
+ val packageInfoPresenter =
+ PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
- packageInfoPresenter.disable()
- }
+ packageInfoPresenter.disable()
+ delay(100)
verifyAction(SettingsEnums.ACTION_SETTINGS_DISABLE_APP)
- verify(packageManager).setApplicationEnabledSetting(
+ verify(mockPackageManager).setApplicationEnabledSetting(
PACKAGE_NAME, PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER, 0
)
}
@Test
- fun startUninstallActivity() = runTest {
- doNothing().`when`(context).startActivityAsUser(any(), any())
+ fun startUninstallActivity() = runBlocking {
val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+ PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
packageInfoPresenter.startUninstallActivity()
verifyAction(SettingsEnums.ACTION_SETTINGS_UNINSTALL_APP)
- val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
- verify(context).startActivityAsUser(intentCaptor.capture(), any())
- with(intentCaptor.value) {
+ val intent = argumentCaptor<Intent> {
+ verify(context).startActivityAsUser(capture(), any())
+ }.firstValue
+ with(intent) {
assertThat(action).isEqualTo(Intent.ACTION_UNINSTALL_PACKAGE)
assertThat(data?.schemeSpecificPart).isEqualTo(PACKAGE_NAME)
assertThat(getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, true)).isEqualTo(false)
@@ -122,76 +108,39 @@
}
@Test
- fun clearInstantApp() = runTest {
- coroutineScope {
- val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+ fun clearInstantApp() = runBlocking {
+ val packageInfoPresenter =
+ PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
- packageInfoPresenter.clearInstantApp()
- }
+ packageInfoPresenter.clearInstantApp()
+ delay(100)
verifyAction(SettingsEnums.ACTION_SETTINGS_CLEAR_INSTANT_APP)
- verify(packageManager).deletePackageAsUser(PACKAGE_NAME, null, 0, USER_ID)
+ verify(mockPackageManager).deletePackageAsUser(PACKAGE_NAME, null, 0, USER_ID)
}
@Test
- fun forceStop() = runTest {
- coroutineScope {
- val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+ fun forceStop() = runBlocking {
+ val packageInfoPresenter =
+ PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
- packageInfoPresenter.forceStop()
- }
+ packageInfoPresenter.forceStop()
+ delay(100)
verifyAction(SettingsEnums.ACTION_APP_FORCE_STOP)
- verify(activityManager).forceStopPackageAsUser(PACKAGE_NAME, USER_ID)
+ verify(mockActivityManager).forceStopPackageAsUser(PACKAGE_NAME, USER_ID)
}
@Test
- fun logAction() = runTest {
+ fun logAction() = runBlocking {
val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers)
+ PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, TestScope(), packageManagers)
packageInfoPresenter.logAction(123)
verifyAction(123)
}
- @Test
- fun reloadPackageInfo_archivingDisabled() = runTest {
- coroutineScope {
- val fakeFeatureFlags = FakeFeatureFlagsImpl()
- fakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, false)
- val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers, fakeFeatureFlags)
-
- packageInfoPresenter.reloadPackageInfo()
- }
-
- val flags = PackageManager.MATCH_ANY_USER.toLong() or
- PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
- PackageManager.GET_PERMISSIONS.toLong()
- verify(packageManagers).getPackageInfoAsUser(PACKAGE_NAME, flags, USER_ID)
- }
-
- @Test
- fun reloadPackageInfo_archivingEnabled() = runTest {
- coroutineScope {
- val fakeFeatureFlags = FakeFeatureFlagsImpl()
- fakeFeatureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
- val packageInfoPresenter =
- PackageInfoPresenter(context, PACKAGE_NAME, USER_ID, this, packageManagers, fakeFeatureFlags)
-
- packageInfoPresenter.reloadPackageInfo()
- }
-
- val flags = PackageManager.MATCH_ANY_USER.toLong() or
- PackageManager.MATCH_DISABLED_COMPONENTS.toLong() or
- PackageManager.GET_PERMISSIONS.toLong() or
- PackageManager.MATCH_ARCHIVED_PACKAGES
- verify(packageManagers).getPackageInfoAsUser(PACKAGE_NAME, flags, USER_ID)
- }
-
private fun verifyAction(category: Int) {
verify(metricsFeatureProvider).action(context, category, PACKAGE_NAME)
}
diff --git a/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt b/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt
index 8707065..ad2fbae 100644
--- a/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/system/DeveloperOptionsControllerTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.os.UserManager
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
@@ -25,7 +26,9 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.SettingsActivity
+import com.android.settings.core.BasePreferenceController
import com.android.settings.development.DevelopmentSettingsDashboardFragment
+import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -33,7 +36,10 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -42,13 +48,38 @@
@get:Rule
val composeTestRule = createComposeRule()
+ private val mockUserManager = mock<UserManager>()
+
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { userManager } doReturn mockUserManager
doNothing().whenever(mock).startActivity(any())
}
private val controller = DeveloperOptionsController(context, TEST_KEY)
@Test
+ fun getAvailabilityStatus_isAdminUser_returnAvailable() {
+ mockUserManager.stub {
+ on { isAdminUser } doReturn true
+ }
+
+ val availabilityStatus = controller.getAvailabilityStatus()
+
+ assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
+ }
+
+ @Test
+ fun getAvailabilityStatus_notAdminUser_returnDisabledForUser() {
+ mockUserManager.stub {
+ on { isAdminUser } doReturn false
+ }
+
+ val availabilityStatus = controller.getAvailabilityStatus()
+
+ assertThat(availabilityStatus).isEqualTo(BasePreferenceController.DISABLED_FOR_USER)
+ }
+
+ @Test
fun title_isDisplayed() {
composeTestRule.setContent {
controller.DeveloperOptionsPreference()