Merge "Fix tests in UserSettingsTest" into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9723ed4..095452c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4285,6 +4285,15 @@
android:value="true" />
</activity>
+ <!-- Access to the Credential Manager list. -->
+ <activity android:name=".applications.credentials.CredentialsPickerActivity"
+ android:excludeFromRecents="true"
+ android:launchMode="singleInstance"
+ android:exported="false">
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.applications.credentials.DefaultCombinedPicker" />
+ </activity>
+
<activity
android:name=".Settings$SystemDashboardActivity"
android:label="@string/header_category_system"
diff --git a/res/drawable/battery_hints_chip_bg.xml b/res/drawable/battery_hints_chip_bg.xml
index e7d1d0f..c2b662c 100644
--- a/res/drawable/battery_hints_chip_bg.xml
+++ b/res/drawable/battery_hints_chip_bg.xml
@@ -16,7 +16,8 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="@color/settingslib_dialog_background" />
+ <solid android:color="?androidprv:attr/materialColorPrimaryContainer" />
<corners android:radius="@dimen/battery_hints_chip_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/res/layout/power_anomaly_hints.xml b/res/layout/power_anomaly_hints.xml
index e4ec47b..3781046 100644
--- a/res/layout/power_anomaly_hints.xml
+++ b/res/layout/power_anomaly_hints.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
@@ -36,7 +37,7 @@
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:textAlignment="viewStart"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"/>
+ android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
+ android:textColor="?androidprv:attr/materialColorOnSurface"/>
</LinearLayout>
\ No newline at end of file
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index 8e9d45d..76ef984 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -65,6 +65,7 @@
android:icon="@drawable/ic_wifi_tethering"
android:order="5"
android:summary="@string/summary_placeholder"
+ settings:controller="com.android.settings.network.TetherPreferenceController"
settings:keywords="@string/keywords_hotspot_tethering"
settings:userRestriction="no_config_tethering"
settings:useAdminDisabledSummary="true" />
diff --git a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
index 074fb7b..e8541389 100644
--- a/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
+++ b/src/com/android/settings/applications/credentials/CombinedProviderInfo.java
@@ -82,6 +82,37 @@
return mAutofillServiceInfo.getServiceInfo().applicationInfo;
}
+ /** Returns the package name. */
+ public @Nullable String getPackageName() {
+ ApplicationInfo ai = getApplicationInfo();
+ if (ai != null) {
+ return ai.packageName;
+ }
+
+ return null;
+ }
+
+ /** Returns the settings activity. */
+ public @Nullable String getSettingsActivity() {
+ // This logic is not used by the top entry but rather what activity should
+ // be launched from the settings screen.
+ for (CredentialProviderInfo cpi : mCredentialProviderInfos) {
+ final CharSequence settingsActivity = cpi.getSettingsActivity();
+ if (!TextUtils.isEmpty(settingsActivity)) {
+ return String.valueOf(settingsActivity);
+ }
+ }
+
+ if (mAutofillServiceInfo != null) {
+ final String settingsActivity = mAutofillServiceInfo.getSettingsActivity();
+ if (!TextUtils.isEmpty(settingsActivity)) {
+ return settingsActivity;
+ }
+ }
+
+ return null;
+ }
+
/** Returns the app icon. */
@Nullable
public Drawable getAppIcon(@NonNull Context context, int userId) {
diff --git a/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
new file mode 100644
index 0000000..495c104
--- /dev/null
+++ b/src/com/android/settings/applications/credentials/CredentialsPickerActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.applications.credentials;
+
+
+import com.android.settings.SettingsActivity;
+
+/** Standalone activity used to launch a {@link DefaultCombinedPicker} fragment. */
+public class CredentialsPickerActivity extends SettingsActivity {
+
+ @Override
+ protected boolean isValidFragment(String fragmentName) {
+ return super.isValidFragment(fragmentName)
+ || DefaultCombinedPicker.class.getName().equals(fragmentName);
+ }
+}
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
index 59c33b2..47a89ec 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -16,11 +16,10 @@
package com.android.settings.applications.credentials;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
import android.credentials.CredentialProviderInfo;
@@ -29,16 +28,19 @@
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
-import android.util.Log;
import android.view.autofill.AutofillManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settingslib.applications.DefaultAppInfo;
import java.util.ArrayList;
import java.util.List;
-public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController {
+public class DefaultCombinedPreferenceController extends DefaultAppPreferenceController
+ implements Preference.OnPreferenceClickListener {
private static final Intent AUTOFILL_PROBE = new Intent(AutofillService.SERVICE_INTERFACE);
private static final String TAG = "DefaultCombinedPreferenceController";
@@ -73,18 +75,55 @@
@Override
protected Intent getSettingIntent(DefaultAppInfo info) {
- if (info == null) {
- return null;
+ // Despite this method being called getSettingIntent this intent actually
+ // opens the primary picker. This is so that we can swap the cog and the left
+ // hand side presses to align the UX.
+ return new Intent(mContext, CredentialsPickerActivity.class);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ final String prefKey = getPreferenceKey();
+ final Preference preference = screen.findPreference(prefKey);
+ if (preference != null) {
+ preference.setOnPreferenceClickListener((Preference.OnPreferenceClickListener) this);
}
- final AutofillSettingIntentProvider intentProvider =
- new AutofillSettingIntentProvider(mContext, getUser(), info.getKey());
- return intentProvider.getIntent();
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ // Get the selected provider.
+ final CombinedProviderInfo topProvider = getTopProvider();
+ if (topProvider == null) {
+ return false;
+ }
+
+ // If the top provider has a defined Credential Manager settings
+ // provider then we should open that up.
+ final String settingsActivity = topProvider.getSettingsActivity();
+ if (!TextUtils.isEmpty(settingsActivity)) {
+ final Intent intent =
+ new Intent(Intent.ACTION_MAIN)
+ .setComponent(
+ new ComponentName(
+ topProvider.getPackageName(), settingsActivity));
+ startActivity(intent);
+ return true;
+ }
+
+ return false;
+ }
+
+ private @Nullable CombinedProviderInfo getTopProvider() {
+ List<CombinedProviderInfo> providers = getAllProviders(getUser());
+ return CombinedProviderInfo.getTopProvider(providers);
}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
- List<CombinedProviderInfo> providers = getAllProviders(getUser());
- CombinedProviderInfo topProvider = CombinedProviderInfo.getTopProvider(providers);
+ CombinedProviderInfo topProvider = getTopProvider();
if (topProvider != null) {
ServiceInfo brandingService = topProvider.getBrandingService();
if (brandingService == null) {
@@ -138,53 +177,6 @@
return true;
}
- /** Provides Intent to setting activity for the specified autofill service. */
- static final class AutofillSettingIntentProvider {
-
- private final String mKey;
- private final Context mContext;
- private final int mUserId;
-
- public AutofillSettingIntentProvider(Context context, int userId, String key) {
- mKey = key;
- mContext = context;
- mUserId = userId;
- }
-
- public Intent getIntent() {
- final List<ResolveInfo> resolveInfos =
- mContext.getPackageManager()
- .queryIntentServicesAsUser(
- AUTOFILL_PROBE, PackageManager.GET_META_DATA, mUserId);
-
- for (ResolveInfo resolveInfo : resolveInfos) {
- final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
-
- // If there are multiple autofill services then pick the first one.
- if (mKey != null && mKey.startsWith(serviceInfo.packageName)) {
- final String settingsActivity;
- try {
- settingsActivity =
- new AutofillServiceInfo(mContext, serviceInfo)
- .getSettingsActivity();
- } catch (SecurityException e) {
- // Service does not declare the proper permission, ignore it.
- Log.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
- return null;
- }
- if (TextUtils.isEmpty(settingsActivity)) {
- return null;
- }
- return new Intent(Intent.ACTION_MAIN)
- .setComponent(
- new ComponentName(serviceInfo.packageName, settingsActivity));
- }
- }
-
- return null;
- }
- }
-
protected int getUser() {
return UserHandle.myUserId();
}
diff --git a/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
index d606f3c..990e221 100644
--- a/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
+++ b/src/com/android/settings/applications/credentials/DefaultPrivateCombinedPreferenceController.kt
@@ -36,15 +36,6 @@
return "default_credman_autofill_private"
}
- override fun getSettingIntent(info: DefaultAppInfo ?): Intent ? {
- if (info == null) {
- return null
- }
- return userHandle?.let { handle ->
- AutofillSettingIntentProvider(mContext, handle.identifier, info.key).intent
- } ?: null
- }
-
override fun startActivity(intent: Intent) {
userHandle?.let { handle ->
mContext.startActivityAsUser(intent, handle)
diff --git a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
index eb0aa7a..f7ca204 100644
--- a/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultWorkCombinedPreferenceController.java
@@ -21,7 +21,6 @@
import android.os.UserHandle;
import com.android.settings.Utils;
-import com.android.settingslib.applications.DefaultAppInfo;
public class DefaultWorkCombinedPreferenceController extends DefaultCombinedPreferenceController {
private final UserHandle mUserHandle;
@@ -45,17 +44,6 @@
}
@Override
- protected Intent getSettingIntent(DefaultAppInfo info) {
- if (info == null) {
- return null;
- }
- final AutofillSettingIntentProvider intentProvider =
- new AutofillSettingIntentProvider(
- mContext, mUserHandle.getIdentifier(), info.getKey());
- return intentProvider.getIntent();
- }
-
- @Override
protected void startActivity(Intent intent) {
mContext.startActivityAsUser(intent, mUserHandle);
}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
index 0bfb87d..d3bc977 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollHelper.java
@@ -182,7 +182,7 @@
* Called when a fingerprint image has been acquired, but wasn't processed yet.
*/
public void onAcquired(boolean isAcquiredGood) {
- if (mListener != null && mTotalSteps != -1) {
+ if (mListener != null) {
mListener.onAcquired(isAcquiredGood && animateIfLastStep());
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
index 21f1c0e..90cf779 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
@@ -16,8 +16,12 @@
package com.android.settings.connecteddevice.audiosharing;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -26,14 +30,18 @@
import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
-public abstract class AudioSharingBasePreferenceController extends BasePreferenceController {
+public abstract class AudioSharingBasePreferenceController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private final BluetoothAdapter mBluetoothAdapter;
private final LocalBluetoothManager mBtManager;
protected final LocalBluetoothLeBroadcast mBroadcast;
protected Preference mPreference;
public AudioSharingBasePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBtManager = Utils.getLocalBtManager(context);
mBroadcast =
mBtManager == null
@@ -43,28 +51,40 @@
@Override
public int getAvailabilityStatus() {
- return mBtManager != null && Flags.enableLeAudioSharing()
- ? AVAILABLE
- : UNSUPPORTED_ON_DEVICE;
+ return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
- updateVisibility(isBroadcasting());
}
- /**
- * Update the visibility of the preference.
- *
- * @param isVisible the latest visibility state for the preference.
- */
- public void updateVisibility(boolean isVisible) {
- mPreference.setVisible(isVisible);
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (isAvailable()) {
+ updateVisibility();
+ }
+ }
+
+ /** Update the visibility of the preference. */
+ protected void updateVisibility() {
+ if (mPreference != null) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ boolean isVisible = isBroadcasting() && isBluetoothStateOn();
+ ThreadUtils.postOnMainThread(
+ () -> mPreference.setVisible(isVisible));
+ });
+ }
}
protected boolean isBroadcasting() {
return mBroadcast != null && mBroadcast.isEnabled(null);
}
+
+ protected boolean isBluetoothStateOn() {
+ return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index 7f90ceb..52a8f18 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -93,14 +93,14 @@
}
@Override
- public void onSwitchBarChanged(boolean newState) {
- updateVisibilityForAttachedPreferences(newState);
+ public void onSwitchBarChanged() {
+ updateVisibilityForAttachedPreferences();
}
- private void updateVisibilityForAttachedPreferences(boolean isVisible) {
- mAudioSharingDeviceVolumeGroupController.updateVisibility(isVisible);
- mCallsAndAlarmsPreferenceController.updateVisibility(isVisible);
- mAudioSharingNamePreferenceController.updateVisibility(isVisible);
- mAudioStreamsCategoryController.updateVisibility(isVisible);
+ private void updateVisibilityForAttachedPreferences() {
+ mAudioSharingDeviceVolumeGroupController.updateVisibility();
+ mCallsAndAlarmsPreferenceController.updateVisibility();
+ mAudioSharingNamePreferenceController.updateVisibility();
+ mAudioStreamsCategoryController.updateVisibility();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
index 90054d4..bdaa534 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
@@ -27,7 +27,6 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
@@ -48,7 +47,7 @@
import java.util.concurrent.Executors;
public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController
- implements DefaultLifecycleObserver, DevicePreferenceCallback {
+ implements DevicePreferenceCallback {
private static final String TAG = "AudioSharingDeviceVolumeGroupController";
private static final String KEY = "audio_sharing_device_volume_group";
@@ -162,6 +161,7 @@
@Override
public void onStart(@NonNull LifecycleOwner owner) {
+ super.onStart(owner);
if (mAssistant == null) {
Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device");
return;
@@ -176,6 +176,7 @@
@Override
public void onStop(@NonNull LifecycleOwner owner) {
+ super.onStop(owner);
if (mAssistant == null) {
Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device");
return;
@@ -233,10 +234,12 @@
}
@Override
- public void updateVisibility(boolean isVisible) {
- super.updateVisibility(isVisible);
+ public void updateVisibility() {
if (mPreferenceGroup != null) {
- mPreferenceGroup.setVisible(mPreferenceGroup.getPreferenceCount() > 0);
+ mPreferenceGroup.setVisible(false);
+ if (mPreferenceGroup.getPreferenceCount() > 0) {
+ super.updateVisibility();
+ }
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
index 8336691..36f66ff 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -19,16 +19,13 @@
import android.content.Context;
import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import com.android.settings.widget.ValidatedEditTextPreference;
public class AudioSharingNamePreferenceController extends AudioSharingBasePreferenceController
- implements ValidatedEditTextPreference.Validator,
- Preference.OnPreferenceChangeListener,
- DefaultLifecycleObserver {
+ implements ValidatedEditTextPreference.Validator, Preference.OnPreferenceChangeListener {
private static final String TAG = "AudioSharingNamePreferenceController";
@@ -59,11 +56,13 @@
@Override
public void onStart(@NonNull LifecycleOwner owner) {
+ super.onStart(owner);
// TODO
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
+ super.onStop(owner);
// TODO
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 96a5579..469a387 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -31,6 +31,7 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
@@ -58,19 +59,20 @@
private static final String PREF_KEY = "audio_sharing_main_switch";
interface OnSwitchBarChangedListener {
- void onSwitchBarChanged(boolean newState);
+ void onSwitchBarChanged();
}
private final SettingsMainSwitchBar mSwitchBar;
private final BluetoothAdapter mBluetoothAdapter;
- private final IntentFilter mIntentFilter;
private final LocalBluetoothManager mBtManager;
private final LocalBluetoothLeBroadcast mBroadcast;
private final LocalBluetoothLeBroadcastAssistant mAssistant;
private final Executor mExecutor;
private final OnSwitchBarChangedListener mListener;
private DashboardFragment mFragment;
+ @VisibleForTesting IntentFilter mIntentFilter;
+ @VisibleForTesting
BroadcastReceiver mReceiver =
new BroadcastReceiver() {
@Override
@@ -80,6 +82,7 @@
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
mSwitchBar.setChecked(isBroadcasting());
mSwitchBar.setEnabled(adapterState == BluetoothAdapter.STATE_ON);
+ mListener.onSwitchBarChanged();
}
};
@@ -346,15 +349,19 @@
}
private void updateSwitch() {
- ThreadUtils.postOnMainThread(
- () -> {
- boolean isBroadcasting = isBroadcasting();
- if (mSwitchBar.isChecked() != isBroadcasting) {
- mSwitchBar.setChecked(isBroadcasting);
- }
- mSwitchBar.setEnabled(true);
- mListener.onSwitchBarChanged(isBroadcasting);
- });
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ boolean isBroadcasting = isBroadcasting();
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mSwitchBar.isChecked() != isBroadcasting) {
+ mSwitchBar.setChecked(isBroadcasting);
+ }
+ mSwitchBar.setEnabled(true);
+ mListener.onSwitchBarChanged();
+ });
+ });
}
private boolean isBroadcasting() {
@@ -376,7 +383,7 @@
TAG,
"Add broadcast with broadcastId: "
+ broadcastMetadata.getBroadcastId()
- + "to the device: "
+ + " to the device: "
+ sink.getAnonymizedAddress());
mAssistant.addSource(sink, broadcastMetadata, /* isGroupOp= */ false);
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
index a6adf8a..b3d676c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
@@ -22,7 +22,6 @@
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
@@ -31,6 +30,7 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.HashMap;
@@ -39,7 +39,7 @@
/** PreferenceController to control the dialog to choose the active device for calls and alarms */
public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController
- implements BluetoothCallback, DefaultLifecycleObserver {
+ implements BluetoothCallback {
private static final String TAG = "CallsAndAlarmsPreferenceController";
private static final String PREF_KEY = "calls_and_alarms";
@@ -86,6 +86,7 @@
@Override
public void onStart(@NonNull LifecycleOwner owner) {
+ super.onStart(owner);
if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().registerCallback(this);
}
@@ -93,25 +94,46 @@
@Override
public void onStop(@NonNull LifecycleOwner owner) {
+ super.onStop(owner);
if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().unregisterCallback(this);
}
}
@Override
- public void updateVisibility(boolean isVisible) {
- super.updateVisibility(isVisible);
- if (isVisible && mPreference != null) {
- updateDeviceItemsInSharingSession();
- // mDeviceItemsInSharingSession is ordered. The active device is the first place if
- // exits.
- if (!mDeviceItemsInSharingSession.isEmpty()
- && mDeviceItemsInSharingSession.get(0).isActive()) {
- mPreference.setSummary(mDeviceItemsInSharingSession.get(0).getName());
- } else {
- mPreference.setSummary("");
- }
- }
+ public void updateVisibility() {
+ if (mPreference == null) return;
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ boolean isVisible = isBroadcasting() && isBluetoothStateOn();
+ if (!isVisible) {
+ ThreadUtils.postOnMainThread(() -> mPreference.setVisible(false));
+ } else {
+ updateDeviceItemsInSharingSession();
+ // mDeviceItemsInSharingSession is ordered. The active device is the
+ // first
+ // place if exits.
+ if (!mDeviceItemsInSharingSession.isEmpty()
+ && mDeviceItemsInSharingSession.get(0).isActive()) {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ mPreference.setVisible(true);
+ mPreference.setSummary(
+ mDeviceItemsInSharingSession
+ .get(0)
+ .getName());
+ });
+ } else {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ mPreference.setVisible(true);
+ mPreference.setSummary(
+ "No active device in sharing");
+ });
+ }
+ }
+ });
}
@Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
index f80fdab..f47526f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
@@ -22,7 +22,6 @@
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import com.android.settings.bluetooth.Utils;
@@ -38,8 +37,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController
- implements DefaultLifecycleObserver {
+public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController {
private static final String TAG = "AudioStreamsCategoryController";
private static final boolean DEBUG = BluetoothUtils.D;
private final LocalBluetoothManager mLocalBtManager;
@@ -50,7 +48,7 @@
public void onActiveDeviceChanged(
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- updateVisibility(isBroadcasting());
+ updateVisibility();
}
}
};
@@ -63,14 +61,15 @@
@Override
public void onStart(@NonNull LifecycleOwner owner) {
+ super.onStart(owner);
if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback);
}
- updateVisibility(isBroadcasting());
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
+ super.onStop(owner);
if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback);
}
@@ -84,21 +83,28 @@
}
@Override
- public void updateVisibility(boolean isBroadcasting) {
+ public void updateVisibility() {
+ if (mPreference == null) return;
mExecutor.execute(
() -> {
boolean hasActiveLe =
AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent();
+ boolean isBroadcasting = isBroadcasting();
+ boolean isBluetoothOn = isBluetoothStateOn();
if (DEBUG) {
Log.d(
TAG,
"updateVisibility() isBroadcasting : "
+ isBroadcasting
+ " hasActiveLe : "
- + hasActiveLe);
+ + hasActiveLe
+ + " is BT on : "
+ + isBluetoothOn);
}
ThreadUtils.postOnMainThread(
- () -> super.updateVisibility(hasActiveLe && !isBroadcasting));
+ () ->
+ mPreference.setVisible(
+ isBluetoothOn && hasActiveLe && !isBroadcasting));
});
}
}
diff --git a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
index a6dd732..0b88657 100644
--- a/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
+++ b/src/com/android/settings/deviceinfo/StorageWizardMigrateConfirm.java
@@ -101,7 +101,7 @@
final LockPatternUtils lpu = new LockPatternUtils(this);
if (StorageManager.isFileEncrypted()) {
for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
- if (StorageManager.isUserKeyUnlocked(user.id)) {
+ if (StorageManager.isCeStorageUnlocked(user.id)) {
continue;
}
if (!lpu.isSecure(user.id)) {
diff --git a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
index bf16ab0..f55f822 100644
--- a/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
+++ b/src/com/android/settings/deviceinfo/StorageWizardMoveConfirm.java
@@ -83,7 +83,7 @@
final LockPatternUtils lpu = new LockPatternUtils(this);
if (StorageManager.isFileEncrypted()) {
for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
- if (StorageManager.isUserKeyUnlocked(user.id)) {
+ if (StorageManager.isCeStorageUnlocked(user.id)) {
continue;
}
if (!lpu.isSecure(user.id)) {
diff --git a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
index 4a25238..15f2094 100644
--- a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
+++ b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
@@ -95,6 +95,9 @@
/** Suggest a list of package to set as POLICY_REJECT. */
public void setDenylist(Set<Integer> denylistTargetUids) {
+ if (denylistTargetUids == null) {
+ return;
+ }
final Set<Integer> manualDenylistUids = getDenylistAllUids(getManualDenylistPref());
denylistTargetUids.removeAll(manualDenylistUids);
@@ -105,27 +108,40 @@
return;
}
- // Store target denied uids into DynamicDenylistPref.
- final SharedPreferences.Editor editor = getDynamicDenylistPref().edit();
- editor.clear();
- denylistTargetUids.forEach(
- uid -> editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND));
- editor.apply();
-
+ final ArraySet<Integer> failedUids = new ArraySet<>();
synchronized (mLock) {
// Set new added UIDs into REJECT policy.
for (int uid : denylistTargetUids) {
if (!lastDynamicDenylistUids.contains(uid)) {
- mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
+ try {
+ mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to setUidPolicy(REJECT) for " + uid, e);
+ failedUids.add(uid);
+ }
}
}
// Unset removed UIDs back to NONE policy.
for (int uid : lastDynamicDenylistUids) {
if (!denylistTargetUids.contains(uid)) {
- mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE);
+ try {
+ mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to setUidPolicy(NONE) for " + uid, e);
+ }
}
}
}
+
+ // Store target denied uids into DynamicDenylistPref.
+ final SharedPreferences.Editor editor = getDynamicDenylistPref().edit();
+ editor.clear();
+ denylistTargetUids.forEach(uid -> {
+ if (!failedUids.contains(uid)) {
+ editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND);
+ }
+ });
+ editor.apply();
}
/** Return true if the target uid is in {@link #getManualDenylistPref()}. */
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 10fdc98..e79c1b2 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -98,7 +98,6 @@
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
- controllers.add(new TetherPreferenceController(context, lifecycle));
controllers.add(vpnPreferenceController);
if (internetPreferenceController != null) {
controllers.add(internetPreferenceController);
diff --git a/src/com/android/settings/network/TetherPreferenceController.java b/src/com/android/settings/network/TetherPreferenceController.java
deleted file mode 100644
index f9e5a43..0000000
--- a/src/com/android/settings/network/TetherPreferenceController.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2016 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 static android.os.UserManager.DISALLOW_CONFIG_TETHERING;
-
-import static com.android.settingslib.RestrictedLockUtilsInternal.checkIfRestrictionEnforced;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.TetheringManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.core.FeatureFlags;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.TetherUtil;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnCreate;
-import com.android.settingslib.core.lifecycle.events.OnDestroy;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-public class TetherPreferenceController extends AbstractPreferenceController implements
- PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy {
-
- private static final String TAG = "TetherPreferenceController";
- private static final String KEY_TETHER_SETTINGS = "tether_settings";
-
- private final boolean mAdminDisallowedTetherConfig;
- private final AtomicReference<BluetoothPan> mBluetoothPan;
- private final BluetoothAdapter mBluetoothAdapter;
- private final TetheringManager mTetheringManager;
- @VisibleForTesting
- final BluetoothProfile.ServiceListener mBtProfileServiceListener =
- new android.bluetooth.BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (mBluetoothPan.get() == null) {
- mBluetoothPan.set((BluetoothPan) proxy);
- }
- updateSummary();
- }
-
- public void onServiceDisconnected(int profile) {
- updateSummary();
- }
- };
-
- private SettingObserver mAirplaneModeObserver;
- private Preference mPreference;
- private TetherBroadcastReceiver mTetherReceiver;
- private BroadcastReceiver mBluetoothStateReceiver;
-
- @VisibleForTesting(otherwise = VisibleForTesting.NONE)
- TetherPreferenceController() {
- super(null);
- mAdminDisallowedTetherConfig = false;
- mBluetoothPan = new AtomicReference<>();
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mTetheringManager = null;
- }
-
- public TetherPreferenceController(Context context, Lifecycle lifecycle) {
- super(context);
- mBluetoothPan = new AtomicReference<>();
- mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context);
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mTetheringManager = context.getSystemService(TetheringManager.class);
- if (lifecycle != null) {
- lifecycle.addObserver(this);
- }
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(KEY_TETHER_SETTINGS);
- if (mPreference != null && !mAdminDisallowedTetherConfig) {
- mPreference.setTitle(
- com.android.settingslib.Utils.getTetheringLabel(mTetheringManager));
- }
- }
-
- @Override
- public boolean isAvailable() {
- return TetherUtil.isTetherAvailable(mContext)
- && !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE);
- }
-
- @Override
- public void updateState(Preference preference) {
- updateSummary();
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_TETHER_SETTINGS;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- if (mBluetoothAdapter != null &&
- mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
- mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener,
- BluetoothProfile.PAN);
- }
- if (mBluetoothStateReceiver == null) {
- mBluetoothStateReceiver = new BluetoothStateReceiver();
- mContext.registerReceiver(
- mBluetoothStateReceiver,
- new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
- }
- }
-
- @Override
- public void onResume() {
- if (mAirplaneModeObserver == null) {
- mAirplaneModeObserver = new SettingObserver();
- }
- if (mTetherReceiver == null) {
- mTetherReceiver = new TetherBroadcastReceiver();
- }
- mContext.registerReceiver(
- mTetherReceiver, new IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED));
- mContext.getContentResolver()
- .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver);
- }
-
- @Override
- public void onPause() {
- if (mAirplaneModeObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
- }
- if (mTetherReceiver != null) {
- mContext.unregisterReceiver(mTetherReceiver);
- }
- }
-
- @Override
- public void onDestroy() {
- final BluetoothProfile profile = mBluetoothPan.getAndSet(null);
- if (profile != null && mBluetoothAdapter != null) {
- mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile);
- }
- if (mBluetoothStateReceiver != null) {
- mContext.unregisterReceiver(mBluetoothStateReceiver);
- mBluetoothStateReceiver = null;
- }
- }
-
- public static boolean isTetherConfigDisallowed(Context context) {
- return checkIfRestrictionEnforced(
- context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null;
- }
-
- @VisibleForTesting
- void updateSummary() {
- if (mPreference == null) {
- // Preference is not ready yet.
- return;
- }
- String[] allTethered = mTetheringManager.getTetheredIfaces();
- String[] wifiTetherRegex = mTetheringManager.getTetherableWifiRegexs();
- String[] bluetoothRegex = mTetheringManager.getTetherableBluetoothRegexs();
-
- boolean hotSpotOn = false;
- boolean tetherOn = false;
- if (allTethered != null) {
- if (wifiTetherRegex != null) {
- for (String tethered : allTethered) {
- for (String regex : wifiTetherRegex) {
- if (tethered.matches(regex)) {
- hotSpotOn = true;
- break;
- }
- }
- }
- }
- if (allTethered.length > 1) {
- // We have more than 1 tethered connection
- tetherOn = true;
- } else if (allTethered.length == 1) {
- // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of
- // tether.
- tetherOn = !hotSpotOn;
- } else {
- // No tethered connection.
- tetherOn = false;
- }
- }
- if (!tetherOn
- && bluetoothRegex != null && bluetoothRegex.length > 0
- && mBluetoothAdapter != null
- && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
- // Check bluetooth state. It's not included in mTetheringManager.getTetheredIfaces.
- final BluetoothPan pan = mBluetoothPan.get();
- tetherOn = pan != null && pan.isTetheringOn();
- }
- if (!hotSpotOn && !tetherOn) {
- // Both off
- updateSummaryToOff();
- } else if (hotSpotOn && tetherOn) {
- // Both on
- mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on);
- } else if (hotSpotOn) {
- mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off);
- } else {
- mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on);
- }
- }
-
- private void updateSummaryToOff() {
- if (mPreference == null) {
- // Preference is not ready yet.
- return;
- }
- mPreference.setSummary(R.string.tether_preference_summary_off);
- }
-
- class SettingObserver extends ContentObserver {
-
- public final Uri uri;
-
- public SettingObserver() {
- super(new Handler());
- uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- if (this.uri.equals(uri)) {
- boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
- if (isAirplaneMode) {
- // Airplane mode is on. Update summary to say tether is OFF directly. We cannot
- // go through updateSummary() because turning off tether takes time, and we
- // might still get "ON" status when rerun updateSummary(). So, just say it's off
- updateSummaryToOff();
- }
- }
- }
- }
-
- @VisibleForTesting
- class TetherBroadcastReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- updateSummary();
- }
-
- }
-
- private class BluetoothStateReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- Log.i(TAG, "onReceive: action: " + action);
-
- if (TextUtils.equals(action, BluetoothAdapter.ACTION_STATE_CHANGED)) {
- final int state =
- intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
- Log.i(TAG, "onReceive: state: " + BluetoothAdapter.nameForState(state));
- final BluetoothProfile profile = mBluetoothPan.get();
- switch(state) {
- case BluetoothAdapter.STATE_ON:
- if (profile == null && mBluetoothAdapter != null) {
- mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener,
- BluetoothProfile.PAN);
- }
- break;
- }
- }
- }
- }
-}
diff --git a/src/com/android/settings/network/TetherPreferenceController.kt b/src/com/android/settings/network/TetherPreferenceController.kt
new file mode 100644
index 0000000..8f55f50
--- /dev/null
+++ b/src/com/android/settings/network/TetherPreferenceController.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.net.TetheringManager
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.FeatureFlagUtils
+import androidx.annotation.StringRes
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.preference.Preference
+import androidx.preference.PreferenceScreen
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.core.FeatureFlags
+import com.android.settingslib.RestrictedLockUtilsInternal
+import com.android.settingslib.TetherUtil
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class TetherPreferenceController(context: Context, key: String) :
+ BasePreferenceController(context, key) {
+
+ private val tetheredRepository = TetheredRepository(context)
+ private val tetheringManager = mContext.getSystemService(TetheringManager::class.java)!!
+
+ private var preference: Preference? = null
+
+ override fun getAvailabilityStatus() =
+ if (TetherUtil.isTetherAvailable(mContext)
+ && !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE)
+ ) {
+ AVAILABLE
+ } else {
+ CONDITIONALLY_UNAVAILABLE
+ }
+
+ override fun displayPreference(screen: PreferenceScreen) {
+ super.displayPreference(screen)
+ preference = screen.findPreference(preferenceKey)
+ }
+
+ override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ getTitleResId()?.let { preference?.setTitle(it) }
+ }
+ }
+
+ tetheredRepository.tetheredTypesFlow().collectLatestWithLifecycle(viewLifecycleOwner) {
+ preference?.setSummary(getSummaryResId(it))
+ }
+ }
+
+ private suspend fun getTitleResId(): Int? = withContext(Dispatchers.Default) {
+ if (isTetherConfigDisallowed(mContext)) null
+ else Utils.getTetheringLabel(tetheringManager)
+ }
+
+ @VisibleForTesting
+ @StringRes
+ fun getSummaryResId(tetheredTypes: Set<Int>): Int {
+ val hotSpotOn = TetheringManager.TETHERING_WIFI in tetheredTypes
+ val tetherOn = tetheredTypes.any { it != TetheringManager.TETHERING_WIFI }
+ return when {
+ hotSpotOn && tetherOn -> R.string.tether_settings_summary_hotspot_on_tether_on
+ hotSpotOn -> R.string.tether_settings_summary_hotspot_on_tether_off
+ tetherOn -> R.string.tether_settings_summary_hotspot_off_tether_on
+ else -> R.string.tether_preference_summary_off
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun isTetherConfigDisallowed(context: Context?): Boolean =
+ RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context, UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()
+ ) != null
+ }
+}
diff --git a/src/com/android/settings/network/TetheredRepository.kt b/src/com/android/settings/network/TetheredRepository.kt
new file mode 100644
index 0000000..f18bdca
--- /dev/null
+++ b/src/com/android/settings/network/TetheredRepository.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.network
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothPan
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.content.IntentFilter
+import android.net.TetheringInterface
+import android.net.TetheringManager
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+
+class TetheredRepository(private val context: Context) {
+ private val tetheringManager = context.getSystemService(TetheringManager::class.java)!!
+
+ private val adapter = context.getSystemService(BluetoothManager::class.java)!!.adapter
+
+ fun tetheredTypesFlow(): Flow<Set<Int>> =
+ combine(
+ tetheredInterfacesFlow(),
+ isBluetoothTetheringOnFlow(),
+ ) { tetheringInterfaces, isBluetoothTetheringOn ->
+ val mutableSet = tetheringInterfaces.map { it.type }.toMutableSet()
+ if (isBluetoothTetheringOn) mutableSet += TetheringManager.TETHERING_BLUETOOTH
+ mutableSet
+ }.conflate().flowOn(Dispatchers.Default)
+
+ private fun tetheredInterfacesFlow(): Flow<Set<TetheringInterface>> = callbackFlow {
+ val callback = object : TetheringManager.TetheringEventCallback {
+ override fun onTetheredInterfacesChanged(interfaces: Set<TetheringInterface>) {
+ trySend(interfaces)
+ }
+ }
+
+ tetheringManager.registerTetheringEventCallback(Dispatchers.Default.asExecutor(), callback)
+
+ awaitClose { tetheringManager.unregisterTetheringEventCallback(callback) }
+ }.conflate().flowOn(Dispatchers.Default)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun isBluetoothTetheringOnFlow(): Flow<Boolean> =
+ merge(
+ flowOf(null), // kick an initial value
+ context.broadcastReceiverFlow(IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)),
+ ).flatMapLatest {
+ if (adapter.getState() == BluetoothAdapter.STATE_ON) {
+ isBluetoothPanTetheringOnFlow()
+ } else {
+ flowOf(false)
+ }
+ }.conflate().flowOn(Dispatchers.Default)
+
+ private fun isBluetoothPanTetheringOnFlow() = callbackFlow {
+ var connectedProxy: BluetoothProfile? = null
+
+ val listener = object : BluetoothProfile.ServiceListener {
+ override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
+ connectedProxy = proxy
+ launch(Dispatchers.Default) {
+ trySend((proxy as BluetoothPan).isTetheringOn)
+ }
+ }
+
+ override fun onServiceDisconnected(profile: Int) {}
+ }
+
+ adapter.getProfileProxy(context, listener, BluetoothProfile.PAN)
+
+ awaitClose {
+ connectedProxy?.let { adapter.closeProfileProxy(BluetoothProfile.PAN, it) }
+ }
+ }.conflate().flowOn(Dispatchers.Default)
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
index 1904cb5..cfd256f 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceDetailsFragmentTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Bundle;
@@ -49,8 +50,9 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -63,7 +65,6 @@
import org.robolectric.annotation.Config;
import org.robolectric.fakes.RoboMenu;
-@Ignore("b/313014781")
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
com.android.settings.testutils.shadow.ShadowUserManager.class,
@@ -90,12 +91,17 @@
private UserManager mUserManager;
@Mock
private InputManager mInputManager;
+ @Mock
+ private CompanionDeviceManager mCompanionDeviceManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
doReturn(mInputManager).when(mContext).getSystemService(InputManager.class);
+ doReturn(mCompanionDeviceManager).when(mContext)
+ .getSystemService(CompanionDeviceManager.class);
+ when(mCompanionDeviceManager.getAllAssociations()).thenReturn(ImmutableList.of());
removeInputDeviceWithMatchingBluetoothAddress();
FakeFeatureFactory.setupForTest();
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
index 88ace91..853d2c48 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
@@ -33,7 +33,6 @@
import com.android.settingslib.widget.FooterPreference;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,7 +44,7 @@
public class BluetoothSwitchPreferenceControllerTest {
private static final String BLUETOOTH_INFO_STRING = "When Bluetooth is turned on, your device"
- + " can communicate with other nearby Bluetooth devices.";
+ + " can communicate with other nearby Bluetooth devices";
@Mock
private RestrictionUtils mRestrictionUtils;
@Mock
@@ -83,7 +82,6 @@
assertThat(TextUtils.equals(mFooterPreference.getTitle(), text)).isTrue();
}
- @Ignore("b/313014781")
@Test
public void updateText_bluetoothOffScanningOff() {
Settings.Global.putInt(mContext.getContentResolver(),
@@ -93,7 +91,6 @@
assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
}
- @Ignore("b/313014781")
@Test
public void updateText_bluetoothOnScanningOff() {
Settings.Global.putInt(mContext.getContentResolver(),
@@ -103,7 +100,6 @@
assertThat(mFooterPreference.getTitle()).isEqualTo(BLUETOOTH_INFO_STRING);
}
- @Ignore("b/313014781")
@Test
public void updateText_bluetoothOnScanningOn() {
Settings.Global.putInt(mContext.getContentResolver(),
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
new file mode 100644
index 0000000..0b94061
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Looper;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.widget.Switch;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.flags.Flags;
+import com.android.settings.widget.SettingsMainSwitchBar;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioSharingSwitchBarControllerTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Spy Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private Switch mSwitch;
+
+ private SettingsMainSwitchBar mSwitchBar;
+ private AudioSharingSwitchBarController mController;
+ private AudioSharingSwitchBarController.OnSwitchBarChangedListener mListener;
+ private boolean mOnSwitchBarChanged;
+
+ @Before
+ public void setUp() {
+ mSwitchBar = new SettingsMainSwitchBar(mContext);
+ mOnSwitchBarChanged = false;
+ mListener = () -> mOnSwitchBarChanged = true;
+ mController = new AudioSharingSwitchBarController(mContext, mSwitchBar, mListener);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
+ public void bluetoothOff_switchDisabled() {
+ mContext.registerReceiver(
+ mController.mReceiver,
+ mController.mIntentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
+ mContext.sendBroadcast(intent);
+ shadowOf(Looper.getMainLooper()).idle();
+ verify(mSwitch).setEnabled(false);
+ assertThat(mOnSwitchBarChanged).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
index bdf81e4..91d8c7d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -40,7 +40,6 @@
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -156,7 +155,6 @@
"ScreenTimeoutAnomaly");
}
- @Ignore("b/313582999")
@Test
public void onClick_mainBtnOfAppsAnomaly_selectHighlightSlot() {
final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
@@ -178,7 +176,6 @@
.action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "AppAnomaly");
}
- @Ignore("b/313582999")
@Test
public void onClick_dismissBtnOfAppsAnomaly_keepHighlightSlotIndex() {
final PowerAnomalyEvent appsAnomaly = BatteryTestUtils.createAppAnomalyEvent();
diff --git a/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java
deleted file mode 100644
index 99869d8..0000000
--- a/tests/robotests/src/com/android/settings/network/TetherPreferenceControllerTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 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 static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.TetheringManager;
-import android.provider.Settings;
-
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.util.ReflectionHelpers;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-@RunWith(RobolectricTestRunner.class)
-public class TetherPreferenceControllerTest {
-
- @Mock
- private Context mContext;
- @Mock
- private TetheringManager mTetheringManager;
- @Mock
- private BluetoothAdapter mBluetoothAdapter;
- @Mock
- private Preference mPreference;
-
- private TetherPreferenceController mController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- doReturn(null).when(mContext)
- .getSystemService(Context.DEVICE_POLICY_SERVICE);
- mController = spy(new TetherPreferenceController(mContext, /* lifecycle= */ null));
- ReflectionHelpers.setField(mController, "mContext", mContext);
- ReflectionHelpers.setField(mController, "mTetheringManager", mTetheringManager);
- ReflectionHelpers.setField(mController, "mBluetoothAdapter", mBluetoothAdapter);
- ReflectionHelpers.setField(mController, "mPreference", mPreference);
- }
-
- @Test
- public void lifeCycle_onCreate_shouldInitBluetoothPan() {
- when(mBluetoothAdapter.getState()).thenReturn(BluetoothAdapter.STATE_ON);
- mController.onCreate(null);
-
- verify(mBluetoothAdapter).getState();
- verify(mBluetoothAdapter).getProfileProxy(mContext, mController.mBtProfileServiceListener,
- BluetoothProfile.PAN);
- }
-
- @Test
- public void lifeCycle_onCreate_shouldNotInitBluetoothPanWhenBluetoothOff() {
- when(mBluetoothAdapter.getState()).thenReturn(BluetoothAdapter.STATE_OFF);
- mController.onCreate(null);
-
- verify(mBluetoothAdapter).getState();
- verifyNoMoreInteractions(mBluetoothAdapter);
- }
-
- @Test
- public void goThroughLifecycle_shouldDestoryBluetoothProfile() {
- final BluetoothPan pan = mock(BluetoothPan.class);
- final AtomicReference<BluetoothPan> panRef =
- ReflectionHelpers.getField(mController, "mBluetoothPan");
- panRef.set(pan);
-
- mController.onDestroy();
-
- verify(mBluetoothAdapter).closeProfileProxy(BluetoothProfile.PAN, pan);
- }
-
- @Test
- public void updateSummary_noPreference_noInteractionWithTetheringManager() {
- ReflectionHelpers.setField(mController, "mPreference", null);
- mController.updateSummary();
- verifyNoMoreInteractions(mTetheringManager);
- }
-
- @Test
- public void updateSummary_wifiTethered_shouldShowHotspotMessage() {
- when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[]{"123"});
- when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"123"});
-
- mController.updateSummary();
- verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_on_tether_off);
- }
-
- @Test
- public void updateSummary_btThetherOn_shouldShowTetherMessage() {
- when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[]{"123"});
- when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"123"});
-
- mController.updateSummary();
- verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_off_tether_on);
- }
-
- @Ignore
- @Test
- public void updateSummary_tetherOff_shouldShowTetherOffMessage() {
- when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"123"});
- when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"456"});
-
- mController.updateSummary();
- verify(mPreference).setSummary(R.string.switch_off_text);
- }
-
- @Test
- public void updateSummary_wifiBtTetherOn_shouldShowHotspotAndTetherMessage() {
- when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[]{"123", "456"});
- when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"456"});
- when(mTetheringManager.getTetherableBluetoothRegexs()).thenReturn(new String[]{"23"});
-
- mController.updateSummary();
- verify(mPreference).setSummary(R.string.tether_settings_summary_hotspot_on_tether_on);
- }
-
- @Ignore
- @Test
- public void airplaneModeOn_shouldUpdateSummaryToOff() {
- final Context context = RuntimeEnvironment.application;
- ReflectionHelpers.setField(mController, "mContext", context);
-
- Settings.Global.putInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
-
- mController.onResume();
-
- verifyNoInteractions(mPreference);
-
- Settings.Global.putInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
-
- final ContentObserver observer =
- ReflectionHelpers.getField(mController, "mAirplaneModeObserver");
- observer.onChange(true, Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON));
-
- verify(mPreference).setSummary(R.string.switch_off_text);
- }
-
- @Test
- public void onResume_shouldRegisterTetherReceiver() {
- when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
-
- mController.onResume();
-
- verify(mContext).registerReceiver(
- any(TetherPreferenceController.TetherBroadcastReceiver.class),
- any(IntentFilter.class));
- }
-
- @Test
- public void onPause_shouldUnregisterTetherReceiver() {
- when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
- mController.onResume();
-
- mController.onPause();
-
- verify(mContext)
- .unregisterReceiver(any(TetherPreferenceController.TetherBroadcastReceiver.class));
- }
-
- @Test
- public void tetherStatesChanged_shouldUpdateSummary() {
- final Context context = RuntimeEnvironment.application;
- ReflectionHelpers.setField(mController, "mContext", context);
- mController.onResume();
-
- context.sendBroadcast(new Intent(TetheringManager.ACTION_TETHER_STATE_CHANGED));
-
- shadowMainLooper().idle();
- verify(mController).updateSummary();
- }
-}
diff --git a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
index dbd3372..d74b6df 100644
--- a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
@@ -38,6 +38,7 @@
import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +74,7 @@
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
+ @Ignore("b/313614100")
@Test
public void testHandlePreferenceTreeClick() {
Preference preference = new Preference(mContext);
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
index c693938..d35b608 100644
--- a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
@@ -48,6 +48,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -207,6 +208,7 @@
verify(mActivity, times(1)).finish();
}
+ @Ignore("b/313615637")
@Test
public void testNotFinishedIfAdmin() {
mUserManager.setIsAdminUser(true);
diff --git a/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt
new file mode 100644
index 0000000..51d2c87
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/TetherPreferenceControllerTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.network
+
+import android.content.Context
+import android.net.TetheringManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+import com.android.settingslib.TetherUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
+
+@RunWith(AndroidJUnit4::class)
+class TetherPreferenceControllerTest {
+ private lateinit var mockSession: MockitoSession
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val controller = TetherPreferenceController(context, TEST_KEY)
+
+ @Before
+ fun setUp() {
+ mockSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(TetherUtil::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+
+ ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun getAvailabilityStatus_whenTetherAvailable() {
+ ExtendedMockito.doReturn(true).`when` { TetherUtil.isTetherAvailable(context) }
+
+ val availabilityStatus = controller.availabilityStatus
+
+ assertThat(availabilityStatus).isEqualTo(BasePreferenceController.AVAILABLE)
+ }
+
+ @Test
+ fun getAvailabilityStatus_whenTetherNotAvailable() {
+ ExtendedMockito.doReturn(false).`when` { TetherUtil.isTetherAvailable(context) }
+
+ val availabilityStatus = controller.availabilityStatus
+
+ assertThat(availabilityStatus).isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE)
+ }
+
+ @Test
+ fun getSummaryResId_bothWifiAndBluetoothOn() {
+ val summaryResId = controller.getSummaryResId(
+ setOf(TetheringManager.TETHERING_WIFI, TetheringManager.TETHERING_BLUETOOTH)
+ )
+
+ assertThat(summaryResId).isEqualTo(R.string.tether_settings_summary_hotspot_on_tether_on)
+ }
+
+ @Test
+ fun getSummaryResId_onlyWifiHotspotOn() {
+ val summaryResId = controller.getSummaryResId(setOf(TetheringManager.TETHERING_WIFI))
+
+ assertThat(summaryResId).isEqualTo(R.string.tether_settings_summary_hotspot_on_tether_off)
+ }
+
+ @Test
+ fun getSummaryResId_onlyBluetoothTetheringOn() {
+ val summaryResId = controller.getSummaryResId(setOf(TetheringManager.TETHERING_BLUETOOTH))
+
+ assertThat(summaryResId).isEqualTo(R.string.tether_settings_summary_hotspot_off_tether_on)
+ }
+
+ @Test
+ fun getSummaryResId_allOff() {
+ val summaryResId = controller.getSummaryResId(emptySet())
+
+ assertThat(summaryResId).isEqualTo(R.string.tether_preference_summary_off)
+ }
+
+ private companion object {
+ const val TEST_KEY = "test_key"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/TetheredRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/TetheredRepositoryTest.kt
new file mode 100644
index 0000000..5bd5210
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/TetheredRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.network
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothPan
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.net.TetheringInterface
+import android.net.TetheringManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class TetheredRepositoryTest {
+
+ private var tetheringInterfaces: Set<TetheringInterface> = emptySet()
+
+ private var tetheringEventCallback: TetheringManager.TetheringEventCallback? = null
+
+ private val mockTetheringManager = mock<TetheringManager> {
+ on { registerTetheringEventCallback(any(), any()) } doAnswer {
+ tetheringEventCallback = it.arguments[1] as TetheringManager.TetheringEventCallback
+ tetheringEventCallback?.onTetheredInterfacesChanged(tetheringInterfaces)
+ }
+ }
+
+ private val mockBluetoothPan = mock<BluetoothPan> {
+ on { isTetheringOn } doReturn false
+ }
+
+ private val mockBluetoothAdapter = mock<BluetoothAdapter> {
+ on { getProfileProxy(any(), any(), eq(BluetoothProfile.PAN)) } doAnswer {
+ val listener = it.arguments[1] as BluetoothProfile.ServiceListener
+ listener.onServiceConnected(BluetoothProfile.PAN, mockBluetoothPan)
+ true
+ }
+ }
+
+ private val mockBluetoothManager = mock<BluetoothManager> {
+ on { adapter } doReturn mockBluetoothAdapter
+ }
+
+ private val context = mock<Context> {
+ on { getSystemService(TetheringManager::class.java) } doReturn mockTetheringManager
+ on { getSystemService(BluetoothManager::class.java) } doReturn mockBluetoothManager
+ }
+
+ private val repository = TetheredRepository(context)
+
+ @Test
+ fun tetheredTypesFlow_allOff() = runBlocking {
+ val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+ assertThat(tetheredTypes).isEmpty()
+ }
+
+ @Test
+ fun tetheredTypesFlow_wifiHotspotOn(): Unit = runBlocking {
+ tetheringInterfaces = setOf(TetheringInterface(TetheringManager.TETHERING_WIFI, ""))
+
+ val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+ assertThat(tetheredTypes).containsExactly(TetheringManager.TETHERING_WIFI)
+ }
+
+ @Test
+ fun tetheredTypesFlow_usbTetheringTurnOnLater(): Unit = runBlocking {
+ val tetheredTypeDeferred = async {
+ repository.tetheredTypesFlow().mapNotNull {
+ it.singleOrNull()
+ }.firstWithTimeoutOrNull()
+ }
+ delay(100)
+
+ tetheringEventCallback?.onTetheredInterfacesChanged(
+ setOf(TetheringInterface(TetheringManager.TETHERING_USB, ""))
+ )
+
+ assertThat(tetheredTypeDeferred.await()).isEqualTo(TetheringManager.TETHERING_USB)
+ }
+
+ @Test
+ fun tetheredTypesFlow_bluetoothOff(): Unit = runBlocking {
+ mockBluetoothAdapter.stub {
+ on { state } doReturn BluetoothAdapter.STATE_OFF
+ }
+
+ val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+ assertThat(tetheredTypes).isEmpty()
+ }
+
+ @Test
+ fun tetheredTypesFlow_bluetoothOnTetheringOff(): Unit = runBlocking {
+ mockBluetoothAdapter.stub {
+ on { state } doReturn BluetoothAdapter.STATE_ON
+ }
+
+ val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+ assertThat(tetheredTypes).isEmpty()
+ }
+
+ @Test
+ fun tetheredTypesFlow_bluetoothTetheringOn(): Unit = runBlocking {
+ mockBluetoothAdapter.stub {
+ on { state } doReturn BluetoothAdapter.STATE_ON
+ }
+ mockBluetoothPan.stub {
+ on { isTetheringOn } doReturn true
+ }
+
+ val tetheredTypes = repository.tetheredTypesFlow().firstWithTimeoutOrNull()
+
+ assertThat(tetheredTypes).containsExactly(TetheringManager.TETHERING_BLUETOOTH)
+ }
+}