Merge "Avoid empty line for storage summary" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2ba87d7..02dd9cd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5036,6 +5036,12 @@
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
+ <activity
+ android:name="com.android.settings.network.WepNetworkDialogActivity"
+ android:exported="false"
+ android:theme="@style/Theme.SpaLib.Dialog">
+ </activity>
+
<!-- This is the longest AndroidManifest.xml ever. -->
</application>
</manifest>
diff --git a/res/xml/bluetooth_audio_streams_dialog.xml b/res/xml/bluetooth_audio_streams_dialog.xml
new file mode 100644
index 0000000..afc5055
--- /dev/null
+++ b/res/xml/bluetooth_audio_streams_dialog.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/dialog_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/broadcast_dialog_margin"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/dialog_icon"
+ android:layout_width="36dp"
+ android:layout_height="36dp"
+ android:layout_marginTop="@dimen/broadcast_dialog_icon_margin_top"
+ android:layout_marginBottom="@dimen/broadcast_dialog_title_img_margin_top"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_bt_audio_sharing"/>
+
+ <TextView
+ style="@style/BroadcastDialogTitleStyle"
+ android:id="@+id/dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="center"/>
+
+ <TextView
+ style="@style/BroadcastDialogBodyStyle"
+ android:id="@+id/dialog_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:visibility="gone"/>
+
+ <TextView
+ style="@style/BroadcastDialogBodyStyle"
+ android:id="@+id/dialog_subtitle_2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:visibility="gone"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/broadcast_dialog_margin"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/left_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ android:layout_weight="1"
+ android:visibility="gone"/>
+ <Button
+ android:id="@+id/right_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginRight="16dp"
+ android:visibility="gone"/>
+ </LinearLayout>
+
+ </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/src/com/android/settings/accessibility/HearingDevicePairingFragment.java b/src/com/android/settings/accessibility/HearingDevicePairingFragment.java
index fb79ece..78f5b4c 100644
--- a/src/com/android/settings/accessibility/HearingDevicePairingFragment.java
+++ b/src/com/android/settings/accessibility/HearingDevicePairingFragment.java
@@ -149,6 +149,7 @@
for (BluetoothGatt gatt: mConnectingGattList) {
gatt.disconnect();
}
+ mConnectingGattList.clear();
mLocalManager.setForegroundActivity(null);
mLocalManager.getEventManager().unregisterCallback(this);
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
index b0af7dd..b5c71b6 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
@@ -71,6 +71,7 @@
super.onAttach(context);
use(AudioStreamsScanQrCodeController.class).setFragment(this);
mAudioStreamsProgressCategoryController = use(AudioStreamsProgressCategoryController.class);
+ mAudioStreamsProgressCategoryController.setFragment(this);
}
@Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragment.java
new file mode 100644
index 0000000..c7d7f16
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 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.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+
+import com.google.common.base.Strings;
+
+import java.util.function.Consumer;
+
+public class AudioStreamsDialogFragment extends InstrumentedDialogFragment {
+ private static final String TAG = "AudioStreamsDialogFragment";
+ private final DialogBuilder mDialogBuilder;
+
+ AudioStreamsDialogFragment(DialogBuilder dialogBuilder) {
+ mDialogBuilder = dialogBuilder;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(chelseahao): update metrics id
+ return 0;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return mDialogBuilder.build();
+ }
+
+ static void show(Fragment host, DialogBuilder dialogBuilder) {
+ FragmentManager manager = host.getChildFragmentManager();
+ (new AudioStreamsDialogFragment(dialogBuilder)).show(manager, TAG);
+ }
+
+ static class DialogBuilder {
+ private final Context mContext;
+ private final AlertDialog.Builder mBuilder;
+ private String mTitle;
+ private String mSubTitle1;
+ private String mSubTitle2;
+ private String mLeftButtonText;
+ private String mRightButtonText;
+ private Consumer<AlertDialog> mLeftButtonOnClickListener;
+ private Consumer<AlertDialog> mRightButtonOnClickListener;
+
+ DialogBuilder(Context context) {
+ mContext = context;
+ mBuilder = new AlertDialog.Builder(context);
+ }
+
+ DialogBuilder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ DialogBuilder setSubTitle1(String subTitle1) {
+ mSubTitle1 = subTitle1;
+ return this;
+ }
+
+ DialogBuilder setSubTitle2(String subTitle2) {
+ mSubTitle2 = subTitle2;
+ return this;
+ }
+
+ DialogBuilder setLeftButtonText(String text) {
+ mLeftButtonText = text;
+ return this;
+ }
+
+ DialogBuilder setLeftButtonOnClickListener(Consumer<AlertDialog> listener) {
+ mLeftButtonOnClickListener = listener;
+ return this;
+ }
+
+ DialogBuilder setRightButtonText(String text) {
+ mRightButtonText = text;
+ return this;
+ }
+
+ DialogBuilder setRightButtonOnClickListener(Consumer<AlertDialog> listener) {
+ mRightButtonOnClickListener = listener;
+ return this;
+ }
+
+ AlertDialog build() {
+ View rootView =
+ LayoutInflater.from(mContext)
+ .inflate(R.xml.bluetooth_audio_streams_dialog, /* parent= */ null);
+
+ AlertDialog dialog = mBuilder.setView(rootView).setCancelable(false).create();
+ dialog.setCanceledOnTouchOutside(false);
+
+ TextView title = rootView.requireViewById(R.id.dialog_title);
+ title.setText(mTitle);
+
+ if (!Strings.isNullOrEmpty(mSubTitle1)) {
+ TextView subTitle1 = rootView.requireViewById(R.id.dialog_subtitle);
+ subTitle1.setText(mSubTitle1);
+ subTitle1.setVisibility(View.VISIBLE);
+ }
+ if (!Strings.isNullOrEmpty(mSubTitle2)) {
+ TextView subTitle2 = rootView.requireViewById(R.id.dialog_subtitle_2);
+ subTitle2.setText(mSubTitle2);
+ subTitle2.setVisibility(View.VISIBLE);
+ }
+ if (!Strings.isNullOrEmpty(mLeftButtonText)) {
+ Button leftButton = rootView.requireViewById(R.id.left_button);
+ leftButton.setText(mLeftButtonText);
+ leftButton.setVisibility(View.VISIBLE);
+ leftButton.setOnClickListener(unused -> mLeftButtonOnClickListener.accept(dialog));
+ }
+ if (!Strings.isNullOrEmpty(mRightButtonText)) {
+ Button rightButton = rootView.requireViewById(R.id.right_button);
+ rightButton.setText(mRightButtonText);
+ rightButton.setVisibility(View.VISIBLE);
+ rightButton.setOnClickListener(
+ unused -> mRightButtonOnClickListener.accept(dialog));
+ }
+
+ return dialog;
+ }
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index ab380c8..cb9975d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -24,8 +24,10 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
+import android.provider.Settings;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -92,6 +94,7 @@
new ConcurrentHashMap<>();
private TimedSourceFromQrCode mTimedSourceFromQrCode;
private AudioStreamsProgressCategoryPreference mCategoryPreference;
+ private AudioStreamsDashboardFragment mFragment;
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -135,10 +138,13 @@
mExecutor.execute(this::stopScanning);
}
+ void setFragment(AudioStreamsDashboardFragment fragment) {
+ mFragment = fragment;
+ }
+
void setSourceFromQrCode(BluetoothLeBroadcastMetadata source) {
mTimedSourceFromQrCode =
- new TimedSourceFromQrCode(
- mContext, source, () -> handleSourceLost(source.getBroadcastId()));
+ new TimedSourceFromQrCode(source, () -> handleSourceLost(source.getBroadcastId()));
}
void setScanning(boolean isScanning) {
@@ -324,6 +330,8 @@
startScanning();
} else {
stopScanning();
+ ThreadUtils.postOnMainThread(
+ () -> AudioStreamsDialogFragment.show(mFragment, getNoLeDeviceDialog()));
}
}
@@ -463,15 +471,41 @@
alertDialog.show();
}
- private static class TimedSourceFromQrCode {
+ private AudioStreamsDialogFragment.DialogBuilder getNoLeDeviceDialog() {
+ return new AudioStreamsDialogFragment.DialogBuilder(mContext)
+ .setTitle("Connect compatible headphones")
+ .setSubTitle1(
+ "To listen to an audio stream, first connect headphones that support LE"
+ + " Audio to this device. Learn more")
+ .setLeftButtonText("Close")
+ .setLeftButtonOnClickListener(AlertDialog::dismiss)
+ .setRightButtonText("Connect a device")
+ .setRightButtonOnClickListener(
+ unused ->
+ mContext.startActivity(
+ new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)));
+ }
+
+ private AudioStreamsDialogFragment.DialogBuilder getBroadcastUnavailableDialog(
+ String broadcastName) {
+ return new AudioStreamsDialogFragment.DialogBuilder(mContext)
+ .setTitle("Audio stream isn't available")
+ .setSubTitle1(broadcastName)
+ .setSubTitle2("This audio stream isn't playing anything right now")
+ .setLeftButtonText("Close")
+ .setLeftButtonOnClickListener(AlertDialog::dismiss)
+ .setRightButtonText("Retry")
+ // TODO(chelseahao): Add retry action
+ .setRightButtonOnClickListener(AlertDialog::dismiss);
+ }
+
+ private class TimedSourceFromQrCode {
private static final int WAIT_FOR_SYNC_TIMEOUT_MILLIS = 15000;
private final CountDownTimer mTimer;
private BluetoothLeBroadcastMetadata mSourceFromQrCode;
private TimedSourceFromQrCode(
- Context context,
- BluetoothLeBroadcastMetadata sourceFromQrCode,
- Runnable timeoutAction) {
+ BluetoothLeBroadcastMetadata sourceFromQrCode, Runnable timeoutAction) {
mSourceFromQrCode = sourceFromQrCode;
mTimer =
new CountDownTimer(WAIT_FOR_SYNC_TIMEOUT_MILLIS, 1000) {
@@ -481,7 +515,12 @@
@Override
public void onFinish() {
timeoutAction.run();
- AudioSharingUtils.toastMessage(context, "Audio steam isn't available");
+ ThreadUtils.postOnMainThread(
+ () ->
+ AudioStreamsDialogFragment.show(
+ mFragment,
+ getBroadcastUnavailableDialog(
+ sourceFromQrCode.getBroadcastName())));
}
};
}
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index f14c32c..0da1034 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -89,6 +89,7 @@
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.settingslib.wifi.WifiSavedConfigUtils;
+import com.android.wifi.flags.Flags;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiEntry.ConnectCallback;
import com.android.wifitrackerlib.WifiPickerTracker;
@@ -1257,8 +1258,19 @@
// If it's an unsaved secure WifiEntry, it will callback
// ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG
- wifiEntry.connect(new WifiEntryConnectCallback(wifiEntry, editIfNoConfig,
- fullScreenEdit));
+ WifiEntryConnectCallback callback =
+ new WifiEntryConnectCallback(wifiEntry, editIfNoConfig, fullScreenEdit);
+
+ if (Flags.wepUsage() && wifiEntry.getSecurityTypes().contains(WifiEntry.SECURITY_WEP)) {
+ WepNetworkDialogActivity.checkWepAllowed(
+ getContext(), getViewLifecycleOwner(), wifiEntry.getSsid(), () -> {
+ wifiEntry.connect(callback);
+ return null;
+ });
+ return;
+ }
+
+ wifiEntry.connect(callback);
}
private class WifiConnectActionListener implements WifiManager.ActionListener {
diff --git a/src/com/android/settings/network/WepNetworkDialogActivity.kt b/src/com/android/settings/network/WepNetworkDialogActivity.kt
new file mode 100644
index 0000000..2fa8784
--- /dev/null
+++ b/src/com/android/settings/network/WepNetworkDialogActivity.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 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.network
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.content.Intent
+import android.net.wifi.WifiManager
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.style.TextAlign
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settings.R
+import com.android.settings.core.SubSettingLauncher
+import com.android.settings.wifi.ConfigureWifiSettings
+import com.android.settingslib.spa.SpaBaseDialogActivity
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
+import kotlin.coroutines.resume
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+
+class WepNetworkDialogActivity : SpaBaseDialogActivity() {
+ @Composable
+ override fun Content() {
+ val context = LocalContext.current
+ val wifiManager = context.getSystemService(WifiManager::class.java)
+ SettingsAlertDialogWithIcon(
+ onDismissRequest = { finish() },
+ confirmButton = AlertDialogButton(
+ getString(R.string.wifi_settings_ssid_block_button_close)
+ ) { finish() },
+ dismissButton = AlertDialogButton(
+ getString(R.string.wifi_settings_wep_networks_button_allow)
+ ) {
+ SubSettingLauncher(context)
+ .setTitleText(context.getText(R.string.network_and_internet_preferences_title))
+ .setSourceMetricsCategory(SettingsEnums.CONFIGURE_WIFI)
+ .setDestination(ConfigureWifiSettings::class.java.getName())
+ .launch()
+ finish()
+ },
+ title = String.format(
+ getString(R.string.wifi_settings_wep_networks_blocked_title),
+ intent.getStringExtra(SSID) ?: SSID
+ ),
+ text = {
+ Text(
+ if (wifiManager?.isWepSupported == false)
+ getString(R.string.wifi_settings_wep_networks_summary_toggle_off)
+ else getString(R.string.wifi_settings_wep_networks_summary_blocked_by_carrier),
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ })
+ }
+
+ companion object {
+ @JvmStatic
+ fun checkWepAllowed(
+ context: Context,
+ lifecycleOwner: LifecycleOwner,
+ ssid: String,
+ onAllowed: () -> Unit,
+ ) {
+ lifecycleOwner.lifecycleScope.launch {
+ val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch
+ if (wifiManager.queryWepAllowed()) {
+ onAllowed()
+ } else {
+ val intent = Intent(context, WepNetworkDialogActivity::class.java).apply {
+ putExtra(SSID, ssid)
+ }
+ context.startActivity(intent)
+ }
+ }
+ }
+
+ private suspend fun WifiManager.queryWepAllowed(): Boolean =
+ withContext(Dispatchers.Default) {
+ suspendCancellableCoroutine { continuation ->
+ queryWepAllowed(Dispatchers.Default.asExecutor()) {
+ continuation.resume(it)
+ }
+ }
+ }
+
+ const val SSID = "ssid"
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt b/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt
index e4fb1ea..38a8499 100644
--- a/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppArchiveButton.kt
@@ -104,7 +104,7 @@
userHandle
)
try {
- packageInstaller.requestArchive(app.packageName, pendingIntent.intentSender, 0)
+ packageInstaller.requestArchive(app.packageName, pendingIntent.intentSender)
} catch (e: Exception) {
Log.e(LOG_TAG, "Request archive failed", e)
Toast.makeText(
diff --git a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
index a7f9714..aafe493 100644
--- a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
@@ -56,9 +56,12 @@
override val appOp = AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
override val permission = Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO
override val setModeByUid = true
-
+ private var receiveDetectionTrainingDataOpController:AppOpsController? = null
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
super.setAllowed(record, newAllowed)
+ if (!newAllowed && receiveDetectionTrainingDataOpController != null) {
+ receiveDetectionTrainingDataOpController!!.setAllowed(false)
+ }
logPermissionChange(newAllowed)
}
@@ -79,20 +82,21 @@
isReceiveSandBoxTriggerAudioOpAllowed: () -> Boolean?
): ReceiveDetectionTrainingDataOpSwitchModel {
val context = LocalContext.current
- val ReceiveDetectionTrainingDataOpController = remember {
+ receiveDetectionTrainingDataOpController = remember {
AppOpsController(
context = context,
app = record.app,
op = AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
)
}
- val isReceiveDetectionTrainingDataOpAllowed = isReceiveDetectionTrainingDataOpAllowed(record, ReceiveDetectionTrainingDataOpController)
+ val isReceiveDetectionTrainingDataOpAllowed = isReceiveDetectionTrainingDataOpAllowed(record, receiveDetectionTrainingDataOpController!!)
+
return remember(record) {
ReceiveDetectionTrainingDataOpSwitchModel(
context,
record,
isReceiveSandBoxTriggerAudioOpAllowed,
- ReceiveDetectionTrainingDataOpController,
+ receiveDetectionTrainingDataOpController!!,
isReceiveDetectionTrainingDataOpAllowed,
)
}.also { model -> LaunchedEffect(model, Dispatchers.Default) { model.initState() } }
diff --git a/src/com/android/settings/system/FactoryResetPreferenceController.java b/src/com/android/settings/system/FactoryResetPreferenceController.java
index e62e548..6d81179 100644
--- a/src/com/android/settings/system/FactoryResetPreferenceController.java
+++ b/src/com/android/settings/system/FactoryResetPreferenceController.java
@@ -28,6 +28,7 @@
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.preference.Preference;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.Settings;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.factory_reset.Flags;
@@ -36,11 +37,14 @@
private static final String TAG = "FactoryResetPreference";
- private static final String ACTION_PREPARE_FACTORY_RESET =
+ @VisibleForTesting
+ static final String ACTION_PREPARE_FACTORY_RESET =
"com.android.settings.ACTION_PREPARE_FACTORY_RESET";
private final UserManager mUm;
- private ActivityResultLauncher<Intent> mFactoryResetPreparationLauncher;
+
+ @VisibleForTesting
+ ActivityResultLauncher<Intent> mFactoryResetPreparationLauncher;
public FactoryResetPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
diff --git a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java
index 321fcf5..01d0df9 100644
--- a/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/system/FactoryResetPreferenceControllerTest.java
@@ -17,75 +17,125 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import com.android.settings.testutils.shadow.ShadowUserManager;
-import com.android.settings.testutils.shadow.ShadowUtils;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.preference.Preference;
+
+import com.google.common.collect.ImmutableList;
import org.junit.After;
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;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowUserManager.class)
public class FactoryResetPreferenceControllerTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String FACTORY_RESET_KEY = "factory_reset";
+ private static final String FACTORY_RESET_APP_PACKAGE = "com.frw_app";
- private ShadowUserManager mShadowUserManager;
+ @Mock private ActivityResultLauncher<Intent> mFactoryResetLauncher;
+ @Mock private Preference mPreference;
+ @Mock private Context mContext;
+ @Mock private PackageManager mPackageManager;
+ @Mock private UserManager mUserManager;
+ private ResolveInfo mFactoryResetAppResolveInfo;
+ private PackageInfo mFactoryResetAppPackageInfo;
- private Context mContext;
private FactoryResetPreferenceController mController;
@Before
- public void setUp() {
- mContext = RuntimeEnvironment.application;
- mShadowUserManager = ShadowUserManager.getShadow();
-
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
mController = new FactoryResetPreferenceController(mContext, FACTORY_RESET_KEY);
+ mFactoryResetAppResolveInfo = new ResolveInfo();
+ mFactoryResetAppResolveInfo.activityInfo = new ActivityInfo();
+ mFactoryResetAppResolveInfo.activityInfo.packageName = FACTORY_RESET_APP_PACKAGE;
+ mFactoryResetAppPackageInfo = new PackageInfo();
+ mFactoryResetAppPackageInfo.requestedPermissions =
+ new String[] {Manifest.permission.PREPARE_FACTORY_RESET};
+ mFactoryResetAppPackageInfo.requestedPermissionsFlags = new int[] {
+ PackageInfo.REQUESTED_PERMISSION_GRANTED
+ };
+ when(mPackageManager.resolveActivity(any(), anyInt()))
+ .thenReturn(mFactoryResetAppResolveInfo);
+ when(mPackageManager.getPackageInfo(anyString(), anyInt()))
+ .thenReturn(mFactoryResetAppPackageInfo);
+ when(mPreference.getKey()).thenReturn(FACTORY_RESET_KEY);
+ mController.mFactoryResetPreparationLauncher = mFactoryResetLauncher;
+
}
@After
public void tearDown() {
- ShadowUtils.reset();
- mShadowUserManager.setIsAdminUser(false);
- mShadowUserManager.setIsDemoUser(false);
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 0);
+ Mockito.reset(mUserManager, mPackageManager);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_DEMO_MODE, 0);
}
@Ignore("b/314930928")
@Test
public void isAvailable_systemUser() {
- mShadowUserManager.setIsAdminUser(true);
+ when(mUserManager.isAdminUser()).thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void isAvailable_nonSystemUser() {
- mShadowUserManager.setIsAdminUser(false);
- mShadowUserManager.setIsDemoUser(false);
+ when(mUserManager.isAdminUser()).thenReturn(false);
+ when(mUserManager.isDemoUser()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_demoUser() {
- mShadowUserManager.setIsAdminUser(false);
+ when(mUserManager.isAdminUser()).thenReturn(false);
// Place the device in demo mode.
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
+ Settings.Global.putInt(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Global.DEVICE_DEMO_MODE, 1);
// Indicate the user is a demo user.
- mShadowUserManager.addUser(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO);
+ when(mUserManager.getUserProfiles())
+ .thenReturn(ImmutableList.of(new UserHandle(UserHandle.myUserId())));
+ when(mUserManager.getUserInfo(eq(UserHandle.myUserId())))
+ .thenReturn(new UserInfo(UserHandle.myUserId(), "test", UserInfo.FLAG_DEMO));
assertThat(mController.isAvailable()).isFalse();
}
@@ -94,4 +144,16 @@
public void getPreferenceKey() {
assertThat(mController.getPreferenceKey()).isEqualTo(FACTORY_RESET_KEY);
}
+
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.factory_reset.Flags.FLAG_ENABLE_FACTORY_RESET_WIZARD)
+ public void handlePreference_factoryResetWizardEnabled() {
+ ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue();
+ verify(mFactoryResetLauncher).launch(intentArgumentCaptor.capture());
+ assertThat(intentArgumentCaptor.getValue()).isNotNull();
+ assertThat(intentArgumentCaptor.getValue().getAction())
+ .isEqualTo(FactoryResetPreferenceController.ACTION_PREPARE_FACTORY_RESET);
+ }
}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt
index 6b4cc0d..2afb3f1 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppArchiveButtonTest.kt
@@ -136,8 +136,7 @@
verify(packageInstaller).requestArchive(
eq(PACKAGE_NAME),
- any(),
- eq(0)
+ any()
)
}