Snap for 11420186 from ca4565083f to 24Q2-release

Change-Id: I8313be7be164bd6b6f9de9e4290a5765681d1f54
This commit is contained in:
Android Build Coastguard Worker
2024-02-08 00:22:04 +00:00
45 changed files with 1518 additions and 339 deletions

View File

@@ -802,6 +802,11 @@
</intent-filter>
</activity>
<activity android:name=".network.SimOnboardingActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
android:theme="@style/Theme.SpaLib.BottomSheetDialog"/>
<activity android:name=".network.telephony.ToggleSubscriptionDialogActivity"
android:exported="false"
android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
@@ -4404,9 +4409,12 @@
<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" />
android:exported="true">
<intent-filter android:priority="1">
<action android:name="android.settings.REQUEST_SET_AUTOFILL_SERVICE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
</activity>
<activity
@@ -4551,19 +4559,6 @@
android:exported="false">
</activity>
<activity android:name=".applications.autofill.AutofillPickerTrampolineActivity"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:exported="true"
android:label="@string/autofill_app">
<intent-filter android:priority="1">
<action android:name="android.settings.REQUEST_SET_AUTOFILL_SERVICE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
</activity>
<activity android:name="Settings$AdvancedConnectedDeviceActivity"
android:exported="true"
android:label="@string/connected_device_connections_title">

View File

@@ -709,7 +709,7 @@
<bool name="default_allow_sensitive_lockscreen_content">true</bool>
<!-- Whether to enable the app battery usage list page feature. -->
<bool name="config_app_battery_usage_list_enabled">true</bool>
<bool name="config_app_battery_usage_list_enabled">false</bool>
<!-- Whether sim related information is visible to the end user. -->
<bool name="config_show_sim_info">true</bool>

View File

@@ -1269,7 +1269,7 @@
<!-- Header in hide Private Space settings page to access Private Space when hidden. [CHAR LIMIT=60] -->
<string name="privatespace_access_header">Access private space when hidden</string>
<!-- Text in hide Private Space settings page on how to search Private Space when hidden. [CHAR LIMIT=60] -->
<string name="privatespace_search_description">From your apps list, enter \'private space\' in the search bar</string>
<string name="privatespace_search_description">From your apps list, enter \"private space\" in the search bar</string>
<!-- Text in hide Private Space settings page to tap on Private Space tile. [CHAR LIMIT=60] -->
<string name="privatespace_tap_tile_description">Tap the private space tile</string>
<!-- Text in hide Private Space settings page to Unlock Private Space. [CHAR LIMIT=60] -->
@@ -1373,18 +1373,6 @@
<!-- Header for private space choose your pattern screen [CHAR LIMIT=40] -->
<string name="private_space_choose_your_pattern_header">Set a pattern for your private space</string>
<!-- TODO(b/309950257): Remove below strings once QSTIle fulfillment is complete. -->
<!-- Header in hide Private Space settings page to unhide Private Space. [CHAR LIMIT=90] -->
<string name="privatespace_unhide_header">To show private space (Not final UX)</string>
<!-- Text in hide Private Space settings page on how to open Private Space setting. [CHAR LIMIT=NONE] -->
<string name="privatespace_open_settings">Open the Settings App</string>
<!-- Text in hide Private Space settings page on how to open Private Space setting. [CHAR LIMIT=NONE] -->
<string name="privatespace_tap_settings">Tap on Security &amp; privacy > private space > Hide private space when locked</string>
<!-- Text in hide Private Space settings page to off hide toggle. [CHAR LIMIT=90] -->
<string name="privatespace_turnoff_hide">Turn off \Hide private space when locked\ toggle</string>
<!-- Note in hide Private Space settings page to inform that this is a development feature. [CHAR LIMIT=NONE] -->
<string name="privatespace_development_note">Note to Googlers: The development of this feature is still in progress</string>
<!-- Text shown when "Add fingerprint" button is disabled -->
<string name="fingerprint_add_max">You can add up to <xliff:g id="count" example="5">%d</xliff:g> fingerprints</string>
<!-- Text shown when users has enrolled a maximum number of fingerprints [CHAR LIMIT=NONE] -->
@@ -7318,7 +7306,7 @@
<!-- Title for setting tile leading to saved autofill passwords, autofill, and account settings [CHAR LIMIT=40]-->
<string name="account_dashboard_title">Passwords &amp; accounts</string>
<!-- Summary for setting tile leading to saved autofill passwords, autofill, and account settings [CHAR LIMIT=NONE]-->
<string name="account_dashboard_default_summary">Saved passwords, autofill, synced accounts</string>
<string name="account_dashboard_default_summary">Suggestions for sign-in &amp; autofill</string>
<!-- Title for setting tile leading to setting UI which allows user set default app to
handle actions such as open web page, making phone calls, default SMS apps [CHAR LIMIT=40]-->
<string name="app_default_dashboard_title">Default apps</string>
@@ -10785,8 +10773,8 @@
<string name="autofill_passwords">Passwords</string>
<!-- Preference category for showing autofill and credman services with saved credentials. [CHAR LIMIT=60] -->
<string name="credman_chosen_app_title">Preferred service</string>
<!-- Preference category for showing additional credential providers. [CHAR LIMIT=60] -->
<string name="credman_credentials">Additional providers</string>
<!-- Preference category for showing additional credential services. [CHAR LIMIT=60] -->
<string name="credman_credentials">Additional services</string>
<!-- Summary for passwords settings that shows how many passwords are saved for each autofill
service. [CHAR LIMIT=NONE] -->
<string name="autofill_passwords_count">{count, plural,
@@ -10831,7 +10819,7 @@
</string>
<!-- Title of the screen where the user picks a provider. [CHAR_LIMIT=NONE] -->
<string name="credman_picker_title">Passwords, passkeys, and data services</string>
<string name="credman_picker_title">Preferred service for passwords, passkeys &amp; autofill</string>
<!-- Title of the warning dialog for disabling the credential provider. [CHAR_LIMIT=NONE] -->
<string name="credman_confirmation_message_title">Turn off %1$s\?</string>
@@ -10848,6 +10836,9 @@
]]>
</string>
<!-- Title for setting tile leading to saved autofill passwords, passkeys, autofill, and account settings [CHAR LIMIT=40]-->
<string name="account_dashboard_title_with_passkeys">Passwords, passkeys &amp; autofill</string>
<!-- Message of the warning dialog for disabling the credential provider (new strings for 24Q3). [CHAR_LIMIT=NONE] -->
<string name="credman_confirmation_message_new_ui">
<![CDATA[
@@ -12891,4 +12882,4 @@
<!-- Authority of the content provider that support methods restartPhoneProcess and restartRild. Will be overlaid by OEM.-->
<string name="reset_telephony_stack_content_provider_authority" translatable="false"></string>
</resources>
</resources>

View File

@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="user_and_account_settings_screen"
android:title="@string/account_dashboard_title"
android:title="@string/account_dashboard_title_with_passkeys"
settings:keywords="@string/keywords_accounts">
<PreferenceCategory

View File

@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="user_and_account_settings_screen"
android:title="@string/account_dashboard_title"
android:title="@string/account_dashboard_title_with_passkeys"
settings:keywords="@string/keywords_accounts">
<PreferenceCategory

View File

@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="user_and_account_settings_screen"
android:title="@string/account_dashboard_title"
android:title="@string/account_dashboard_title_with_passkeys"
settings:keywords="@string/keywords_accounts">
<PreferenceCategory

View File

@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="user_and_account_settings_screen"
android:title="@string/account_dashboard_title"
android:title="@string/account_dashboard_title_with_passkeys"
settings:keywords="@string/keywords_accounts">
<PreferenceCategory

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 The Android Open Source Project
<!-- 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.
@@ -51,6 +51,13 @@
settings:keywords="@string/keywords_battery_saver"
settings:controller="com.android.settings.fuelgauge.BatterySaverController" />
<Preference
android:fragment="com.android.settings.fuelgauge.SmartBatterySettings"
android:key="smart_battery_manager"
android:title="@string/smart_battery_manager_title"
settings:controller="com.android.settings.fuelgauge.batterytip.BatteryManagerPreferenceController"
settings:keywords="@string/keywords_battery_adaptive_preferences" />
<SwitchPreferenceCompat
android:key="battery_percentage"
android:title="@string/battery_percentage"

View File

@@ -34,33 +34,27 @@
android:selectable="false"
settings:searchable="false" />
<Preference
android:key="private_space_note"
android:summary="@string/privatespace_development_note"
android:selectable="false"
settings:searchable="false" />
<PreferenceCategory
android:title="@string/privatespace_unhide_header">
android:title="@string/privatespace_access_header">
<Preference
android:key="search_when_locked_footer"
android:icon="@drawable/counter_1_24dp"
android:title="@string/privatespace_open_settings"
android:title="@string/privatespace_search_description"
android:selectable="false"
settings:searchable="false" />
<Preference
android:key="tap_tile_footer"
android:icon="@drawable/counter_2_24dp"
android:title="@string/privatespace_tap_settings"
android:title="@string/privatespace_tap_tile_description"
android:selectable="false"
settings:searchable="false" />
<Preference
android:key="turn_off_footer"
android:key="unlock_profile_footer"
android:icon="@drawable/counter_3_24dp"
android:title="@string/privatespace_turnoff_hide"
android:title="@string/privatespace_unlock_description"
android:selectable="false"
settings:searchable="false" />

View File

@@ -180,7 +180,7 @@
android:icon="@drawable/ic_settings_accounts"
android:key="top_level_accounts"
android:order="-10"
android:title="@string/account_dashboard_title"
android:title="@string/account_dashboard_title_with_passkeys"
android:summary="@string/summary_placeholder"
settings:highlightableMenuKey="@string/menu_key_accounts"
settings:controller="com.android.settings.accounts.TopLevelAccountEntryPreferenceController"/>

View File

@@ -17,6 +17,7 @@
package com.android.settings.accessibility;
import static android.app.Activity.RESULT_OK;
import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.app.settings.SettingsEnums;
@@ -191,7 +192,7 @@ public class HearingDevicePairingFragment extends RestrictedDashboardFragment im
public void onDeviceBondStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int bondState) {
if (DEBUG) {
Log.d(TAG, "onDeviceBondStateChanged: " + cachedDevice.getName() + ", state = "
Log.d(TAG, "onDeviceBondStateChanged: " + cachedDevice.getDevice() + ", state = "
+ bondState);
}
if (bondState == BluetoothDevice.BOND_BONDED) {
@@ -275,13 +276,13 @@ public class HearingDevicePairingFragment extends RestrictedDashboardFragment im
}
mDevicePreferenceMap.put(cachedDevice, preference);
if (DEBUG) {
Log.d(TAG, "Add device. device: " + cachedDevice);
Log.d(TAG, "Add device. device: " + cachedDevice.getDevice());
}
}
void removeDevice(CachedBluetoothDevice cachedDevice) {
if (DEBUG) {
Log.d(TAG, "removeDevice: " + cachedDevice);
Log.d(TAG, "removeDevice: " + cachedDevice.getDevice());
}
BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
if (mAvailableHearingDeviceGroup != null && preference != null) {
@@ -328,10 +329,15 @@ public class HearingDevicePairingFragment extends RestrictedDashboardFragment im
CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
if (cachedDevice == null) {
cachedDevice = mCachedDeviceManager.addDevice(device);
} else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
if (DEBUG) {
Log.d(TAG, "Skip this device, already bonded: " + cachedDevice.getDevice());
}
return;
}
if (cachedDevice.getHearingAidInfo() == null) {
if (DEBUG) {
Log.d(TAG, "Set hearing aid info on device: " + cachedDevice);
Log.d(TAG, "Set hearing aid info on device: " + cachedDevice.getDevice());
}
cachedDevice.setHearingAidInfo(new HearingAidInfo.Builder().build());
}
@@ -449,7 +455,7 @@ public class HearingDevicePairingFragment extends RestrictedDashboardFragment im
void discoverServices(CachedBluetoothDevice cachedDevice) {
if (DEBUG) {
Log.d(TAG, "connectGattToCheckCompatibility, device: " + cachedDevice);
Log.d(TAG, "connectGattToCheckCompatibility, device: " + cachedDevice.getDevice());
}
BluetoothGatt gatt = cachedDevice.getDevice().connectGatt(getContext(), false,
new BluetoothGattCallback() {
@@ -459,26 +465,36 @@ public class HearingDevicePairingFragment extends RestrictedDashboardFragment im
super.onConnectionStateChange(gatt, status, newState);
if (DEBUG) {
Log.d(TAG, "onConnectionStateChange, status: " + status + ", newState: "
+ newState + ", device: " + cachedDevice);
+ newState + ", device: " + cachedDevice.getDevice());
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
if (status == GATT_SUCCESS
&& newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.disconnect();
mConnectingGattList.remove(gatt);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
boolean isCompatible = gatt.getService(BluetoothUuid.HEARING_AID.getUuid())
!= null
|| gatt.getService(BluetoothUuid.HAS.getUuid()) != null;
if (DEBUG) {
Log.d(TAG,
"onServicesDiscovered, compatible with Android: " + isCompatible
+ ", device: " + cachedDevice);
Log.d(TAG, "onServicesDiscovered, status: " + status + ", device: "
+ cachedDevice.getDevice());
}
if (isCompatible) {
addDevice(cachedDevice);
if (status == GATT_SUCCESS) {
if (gatt.getService(BluetoothUuid.HEARING_AID.getUuid()) != null
|| gatt.getService(BluetoothUuid.HAS.getUuid()) != null) {
if (DEBUG) {
Log.d(TAG, "compatible with Android, device: "
+ cachedDevice.getDevice());
}
addDevice(cachedDevice);
}
} else {
gatt.disconnect();
mConnectingGattList.remove(gatt);
}
}
});

View File

@@ -16,6 +16,7 @@
package com.android.settings.applications.credentials;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -24,7 +25,6 @@ import android.content.pm.ServiceInfo;
import android.credentials.CredentialProviderInfo;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
@@ -49,7 +49,7 @@ public final class CombinedProviderInfo {
private static final String TAG = "CombinedProviderInfo";
private static final String SETTINGS_ACTIVITY_INTENT_ACTION = "android.intent.action.MAIN";
private static final String SETTINGS_ACTIVITY_INTENT_CATEGORY =
"android.intent.category.LAUNCHER";
"android.intent.category.DEFAULT";
private final List<CredentialProviderInfo> mCredentialProviderInfos;
private final @Nullable AutofillServiceInfo mAutofillServiceInfo;
@@ -327,10 +327,8 @@ public final class CombinedProviderInfo {
}
public static @Nullable Intent createSettingsActivityIntent(
@NonNull Context context,
@Nullable CharSequence packageName,
@Nullable CharSequence settingsActivity,
int currentUserId) {
@Nullable CharSequence settingsActivity) {
if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(settingsActivity)) {
return null;
}
@@ -350,19 +348,25 @@ public final class CombinedProviderInfo {
Intent intent = new Intent(SETTINGS_ACTIVITY_INTENT_ACTION);
intent.addCategory(SETTINGS_ACTIVITY_INTENT_CATEGORY);
intent.setComponent(cn);
int contextUserId = context.getUser().getIdentifier();
if (currentUserId != contextUserId && UserManager.isHeadlessSystemUserMode()) {
Log.w(
TAG,
"onLeftSideClicked(): using context for current user ("
+ currentUserId
+ ") instead of user "
+ contextUserId
+ " on headless system user mode");
context = context.createContextAsUser(UserHandle.of(currentUserId), /* flags= */ 0);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
/** Launches the settings activity intent. */
public static void launchSettingsActivityIntent(
@NonNull Context context,
@Nullable CharSequence packageName,
@Nullable CharSequence settingsActivity,
int userId) {
Intent settingsIntent = createSettingsActivityIntent(packageName, settingsActivity);
if (settingsIntent == null) {
return;
}
try {
context.startActivityAsUser(settingsIntent, UserHandle.of(userId));
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open settings activity", e);
}
}
}

View File

@@ -20,7 +20,6 @@ import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
import android.app.Activity;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -677,16 +676,8 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl
@Override
public void onLeftSideClicked() {
Intent settingsIntent =
CombinedProviderInfo.createSettingsActivityIntent(
mContext, packageName, settingsActivity, getUser());
if (settingsIntent != null) {
try {
mContext.startActivity(settingsIntent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to open settings activity", e);
}
}
CombinedProviderInfo.launchSettingsActivityIntent(
mContext, packageName, settingsActivity, getUser());
}
});

View File

@@ -16,15 +16,53 @@
package com.android.settings.applications.credentials;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.SettingsActivity;
/** Standalone activity used to launch a {@link DefaultCombinedPicker} fragment. */
/**
* Standalone activity used to launch a {@link DefaultCombinedPicker} fragment if the user is a
* normal user, a {@link DefaultCombinedPickerWork} fragment if the user is a work profile or {@link
* DefaultCombinedPickerPrivate} fragment if the user is a private profile.
*/
public class CredentialsPickerActivity extends SettingsActivity {
private static final String TAG = "CredentialsPickerActivity";
/** Injects the fragment name into the intent so the correct fragment is opened. */
@VisibleForTesting
public static void injectFragmentIntoIntent(Context context, Intent intent) {
final int userId = UserHandle.myUserId();
final UserManager userManager = UserManager.get(context);
if (DefaultCombinedPickerWork.isUserHandledByFragment(userManager, userId)) {
Slog.d(TAG, "Creating picker fragment using work profile");
intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPickerWork.class.getName());
} else if (DefaultCombinedPickerPrivate.isUserHandledByFragment(userManager)) {
Slog.d(TAG, "Creating picker fragment using private profile");
intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPickerPrivate.class.getName());
} else {
Slog.d(TAG, "Creating picker fragment using normal profile");
intent.putExtra(EXTRA_SHOW_FRAGMENT, DefaultCombinedPicker.class.getName());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
injectFragmentIntoIntent(this, getIntent());
super.onCreate(savedInstanceState);
}
@Override
protected boolean isValidFragment(String fragmentName) {
return super.isValidFragment(fragmentName)
|| DefaultCombinedPicker.class.getName().equals(fragmentName);
|| DefaultCombinedPicker.class.getName().equals(fragmentName)
|| DefaultCombinedPickerWork.class.getName().equals(fragmentName)
|| DefaultCombinedPickerPrivate.class.getName().equals(fragmentName);
}
}

View File

@@ -17,14 +17,29 @@
package com.android.settings.applications.credentials;
import android.os.UserManager;
import android.util.Slog;
import com.android.settings.Utils;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment.ProfileType;
public class DefaultCombinedPickerPrivate extends DefaultCombinedPicker {
private static final String TAG = "DefaultCombinedPickerPrivate";
@Override
protected int getUser() {
UserManager userManager = getContext().getSystemService(UserManager.class);
return Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
}
/** Returns whether the user is handled by this fragment. */
public static boolean isUserHandledByFragment(UserManager userManager) {
try {
// If there is no private profile then this will throw an exception.
Utils.getCurrentUserIdOfType(userManager, ProfileType.PRIVATE);
return true;
} catch (IllegalStateException e) {
Slog.e(TAG, "Failed to get private profile user id", e);
return false;
}
}
}

View File

@@ -19,13 +19,16 @@ package com.android.settings.applications.credentials;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.settings.Utils;
public class DefaultCombinedPickerWork extends DefaultCombinedPicker {
private static final String TAG = "DefaultCombinedPickerWork";
@Override
protected int getUser() {
UserHandle workProfile = Utils.getManagedProfile(UserManager.get(getContext()));
return workProfile.getIdentifier();
return UserHandle.myUserId();
}
/** Returns whether the user is handled by this fragment. */
public static boolean isUserHandledByFragment(UserManager userManager, int userId) {
return userManager.isManagedProfile(userId);
}
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.applications.credentials;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialManager;
@@ -26,11 +25,11 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
import android.text.TextUtils;
import android.view.autofill.AutofillManager;
import android.util.Slog;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.internal.annotations.VisibleForTesting;
@@ -83,7 +82,7 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
// hand side presses to align the UX.
if (PrimaryProviderPreference.shouldUseNewSettingsUi()) {
// We need to return an empty intent here since the class we inherit
// from will throw an NPE if we return null and we don't want it to
// from will throw an NPE if we return null and we don't want it to
// open anything since we added the buttons.
return new Intent();
}
@@ -99,10 +98,10 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
topProvider.getAppName(mContext),
topProvider.getSettingsSubtitle(),
topProvider.getAppIcon(mContext, getUser()),
createSettingsActivityIntent(
topProvider.getPackageName(), topProvider.getSettingsActivity()));
topProvider.getPackageName(),
topProvider.getSettingsActivity());
} else {
updatePreferenceForProvider(preference, null, null, null, null);
updatePreferenceForProvider(preference, null, null, null, null, null);
}
}
@@ -112,7 +111,8 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
@Nullable CharSequence appName,
@Nullable String appSubtitle,
@Nullable Drawable appIcon,
@Nullable Intent settingsActivityIntent) {
@Nullable CharSequence packageName,
@Nullable CharSequence settingsActivity) {
if (appName == null) {
preference.setTitle(R.string.app_list_preference_none);
} else {
@@ -133,13 +133,8 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
primaryPref.setDelegate(
new PrimaryProviderPreference.Delegate() {
public void onOpenButtonClicked() {
if (settingsActivityIntent != null) {
try {
startActivity(settingsActivityIntent);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "Failed to open settings activity", e);
}
}
CombinedProviderInfo.launchSettingsActivityIntent(
mContext, packageName, settingsActivity, getUser());
}
public void onChangeButtonClicked() {
@@ -148,7 +143,7 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
});
// Hide the open button if there is no defined settings activity.
primaryPref.setOpenButtonVisible(settingsActivityIntent != null);
primaryPref.setOpenButtonVisible(!TextUtils.isEmpty(settingsActivity));
primaryPref.setButtonsVisible(appName != null);
}
}
@@ -198,13 +193,8 @@ public class DefaultCombinedPreferenceController extends DefaultAppPreferenceCon
/** Creates an intent to open the credential picker. */
private Intent createIntentToOpenPicker() {
return new Intent(mContext, CredentialsPickerActivity.class);
}
/** Creates an intent to open the settings activity of the primary provider (if available). */
public @Nullable Intent createSettingsActivityIntent(
@Nullable String packageName, @Nullable String settingsActivity) {
return CombinedProviderInfo.createSettingsActivityIntent(
mContext, packageName, settingsActivity, getUser());
final Context context =
mContext.createContextAsUser(UserHandle.of(getUser()), /* flags= */ 0);
return new Intent(context, CredentialsPickerActivity.class);
}
}

View File

@@ -98,6 +98,12 @@ public interface PowerUsageFeatureProvider {
/** Checks whether we should show usage information by slots or not */
boolean isChartGraphSlotsEnabled(Context context);
/** Checks whether adaptive charging feature is supported in this device */
boolean isAdaptiveChargingSupported();
/** Checks whether battery manager feature is supported in this device */
boolean isBatteryManagerSupported();
/** Returns {@code true} if current defender mode is extra defend */
boolean isExtraDefend();

View File

@@ -148,6 +148,16 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider
return false;
}
@Override
public boolean isAdaptiveChargingSupported() {
return false;
}
@Override
public boolean isBatteryManagerSupported() {
return true;
}
@Override
public Intent getResumeChargeIntent(boolean isDockDefender) {
return null;

View File

@@ -0,0 +1,85 @@
/*
* 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.fuelgauge.batterytip;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.UserManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.StringUtil;
/** Preference controller to control the battery manager */
public class BatteryManagerPreferenceController extends BasePreferenceController {
private static final String KEY_BATTERY_MANAGER = "smart_battery_manager";
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
private AppOpsManager mAppOpsManager;
private UserManager mUserManager;
private boolean mEnableAppBatteryUsagePage;
public BatteryManagerPreferenceController(Context context) {
super(context, KEY_BATTERY_MANAGER);
mPowerUsageFeatureProvider = FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mUserManager = context.getSystemService(UserManager.class);
mEnableAppBatteryUsagePage =
mContext.getResources().getBoolean(R.bool.config_app_battery_usage_list_enabled);
}
@Override
public int getAvailabilityStatus() {
if (!mPowerUsageFeatureProvider.isBatteryManagerSupported()) {
return UNSUPPORTED_ON_DEVICE;
}
if (!mContext.getResources().getBoolean(R.bool.config_battery_manager_consider_ac)) {
return AVAILABLE_UNSEARCHABLE;
}
return mPowerUsageFeatureProvider.isAdaptiveChargingSupported()
? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!mEnableAppBatteryUsagePage) {
final int num = BatteryTipUtils.getRestrictedAppsList(mAppOpsManager,
mUserManager).size();
updateSummary(preference, num);
}
}
@VisibleForTesting
void updateSummary(Preference preference, int num) {
if (num > 0) {
preference.setSummary(StringUtil.getIcuPluralsString(mContext, num,
R.string.battery_manager_app_restricted));
} else {
preference.setSummary(
mPowerUsageFeatureProvider.isAdaptiveChargingSupported()
? R.string.battery_manager_summary
: R.string.battery_manager_summary_unsupported);
}
}
}

View File

@@ -35,7 +35,7 @@ public class TermsOfAddressFeminineController extends TermsOfAddressBaseControll
@Override
protected int getMetricsActionKey() {
return SettingsEnums.ACTION_TERMS_OF_ADDRESS_FEMININE;
return 0;
}
@Override

View File

@@ -35,7 +35,7 @@ public class TermsOfAddressMasculineController extends TermsOfAddressBaseControl
@Override
protected int getMetricsActionKey() {
return SettingsEnums.ACTION_TERMS_OF_ADDRESS_MASCULINE;
return 0;
}
@Override

View File

@@ -35,7 +35,7 @@ public class TermsOfAddressNeutralController extends TermsOfAddressBaseControlle
@Override
protected int getMetricsActionKey() {
return SettingsEnums.ACTION_TERMS_OF_ADDRESS_NEUTRAL;
return 0;
}
@Override

View File

@@ -0,0 +1,430 @@
/*
* 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.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.telephony.SubscriptionManager
import android.util.Log
import android.view.MotionEvent
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import com.android.settings.R
import com.android.settings.SidecarFragment
import com.android.settings.network.telephony.SubscriptionActionDialogActivity
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.SimOnboardingPageProvider.getRoute
import com.android.settingslib.spa.SpaBaseDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spa.widget.ui.SettingsTitle
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch
class SimOnboardingActivity : SpaBaseDialogActivity() {
lateinit var scope: CoroutineScope
lateinit var showBottomSheet: MutableState<Boolean>
lateinit var showError: MutableState<Boolean>
lateinit var showDialog: MutableState<Boolean>
private var switchToEuiccSubscriptionSidecar: SwitchToEuiccSubscriptionSidecar? = null
private var switchToRemovableSlotSidecar: SwitchToRemovableSlotSidecar? = null
private var enableMultiSimSidecar: EnableMultiSimSidecar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!this.userManager.isAdminUser) {
Log.e(TAG, "It is not the admin user. Unable to toggle subscription.")
finish()
return
}
var targetSubId = intent.getIntExtra(SUB_ID,SubscriptionManager.INVALID_SUBSCRIPTION_ID)
initServiceData(this, targetSubId, callbackListener)
if (!onboardingService.isUsableTargetSubscriptionId) {
Log.e(TAG, "The subscription id is not usable.")
finish()
return
}
switchToEuiccSubscriptionSidecar = SwitchToEuiccSubscriptionSidecar.get(fragmentManager)
switchToRemovableSlotSidecar = SwitchToRemovableSlotSidecar.get(fragmentManager)
enableMultiSimSidecar = EnableMultiSimSidecar.get(fragmentManager)
setContent {
Content()
}
}
override fun finish() {
setProgressDialog(false)
onboardingService.clear()
super.finish()
}
var callbackListener: (Int) -> Unit = {
Log.d(TAG, "Receive the CALLBACK: $it")
when (it) {
CALLBACK_ERROR -> {
setProgressDialog(false)
showError.value = true
}
CALLBACK_ONBOARDING_COMPLETE -> {
showBottomSheet.value = false
setProgressDialog(true)
scope.launch {
// TODO: refactor the Sidecar
// start to activate the sim
startSimSwitching()
}
}
CALLBACK_SETUP_NAME -> {
scope.launch {
onboardingService.startSetupName()
}
}
CALLBACK_SETUP_PRIMARY_SIM -> {
scope.launch {
onboardingService.startSetupPrimarySim(this@SimOnboardingActivity)
}
}
CALLBACK_FINISH -> {
finish()
}
}
}
fun setProgressDialog(enable: Boolean) {
showDialog.value = enable
val progressState = if (enable) {
SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
} else {
SubscriptionActionDialogActivity.PROGRESS_IS_NOT_SHOWING
}
setProgressState(progressState)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
showBottomSheet = remember { mutableStateOf(true) }
showError = remember { mutableStateOf(false) }
showDialog = remember { mutableStateOf(false) }
scope = rememberCoroutineScope()
registerSidecarReceiverFlow()
if(showError.value){
// show error
return
}
if (showBottomSheet.value) {
var sheetState = rememberModalBottomSheetState()
BottomSheetImpl(
sheetState = sheetState,
nextAction = {
// TODO: if the phone is SS mode and the isDsdsConditionSatisfied is true, then
// enable the DSDS mode.
// case#1: the device need the reboot after enabling DSDS. Showing the confirm
// dialog to user whether reboot device or not.
// case#2: The device don't need the reboot. Enabling DSDS and then showing
// the SIM onboarding UI.
// case#2
val route = getRoute(onboardingService.targetSubId)
startSpaActivity(route)
},
cancelAction = { finish() },
)
} else {
ProgressDialogImpl()
}
}
@Composable
fun ProgressDialogImpl() {
// TODO: 1. Create the SPA's ProgressDialog and using SPA's widget
val dialog: ProgressDialog = object : ProgressDialog(this) {
override fun onTouchEvent(event: MotionEvent): Boolean {
return true
}
}
dialog.setMessage(
stringResource(
R.string.sim_onboarding_progressbar_turning_sim_on,
onboardingService.targetSubInfo?.displayName ?: ""
)
)
dialog.setCancelable(false)
if(showDialog.value) {
dialog.show()
}
}
@Composable
fun registerSidecarReceiverFlow(){
switchToEuiccSubscriptionSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
onStateChange(it)
}
switchToRemovableSlotSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
onStateChange(it)
}
enableMultiSimSidecar?.sidecarReceiverFlow()
?.collectLatestWithLifecycle(LocalLifecycleOwner.current) {
onStateChange(it)
}
}
fun SidecarFragment.sidecarReceiverFlow(): Flow<SidecarFragment> = callbackFlow {
val broadcastReceiver = SidecarFragment.Listener {
Log.d(TAG, "onReceive: $it")
trySend(it)
}
addListener(broadcastReceiver)
awaitClose { removeListener(broadcastReceiver) }
}.catch { e ->
Log.e(TAG, "Error while sidecarReceiverFlow", e)
}.conflate()
fun startSimSwitching(){
Log.d(TAG, "startSimSwitching:")
var targetSubInfo = onboardingService.targetSubInfo
targetSubInfo?.let {
var removedSubInfo = onboardingService.getRemovedSim()
if (targetSubInfo.isEmbedded) {
switchToEuiccSubscriptionSidecar!!.run(
targetSubInfo.subscriptionId,
UiccSlotUtil.INVALID_PORT_ID,
removedSubInfo
)
return@let
}
switchToRemovableSlotSidecar!!.run(
UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID,
removedSubInfo
)
} ?: run {
Log.e(TAG, "no target subInfo in onboardingService")
finish()
}
}
fun onStateChange(fragment: SidecarFragment?) {
if (fragment === switchToEuiccSubscriptionSidecar) {
handleSwitchToEuiccSubscriptionSidecarStateChange()
} else if (fragment === switchToRemovableSlotSidecar) {
handleSwitchToRemovableSlotSidecarStateChange()
} else if (fragment === enableMultiSimSidecar) {
handleEnableMultiSimSidecarStateChange()
}
}
fun handleSwitchToEuiccSubscriptionSidecarStateChange() {
when (switchToEuiccSubscriptionSidecar!!.state) {
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.i(TAG, "Failed to enable the eSIM profile.")
switchToEuiccSubscriptionSidecar!!.reset()
callbackListener(CALLBACK_ERROR)
// TODO: showErrorDialog and using privileged_action_disable_fail_title and
// privileged_action_disable_fail_text
}
}
}
fun handleSwitchToRemovableSlotSidecarStateChange() {
when (switchToRemovableSlotSidecar!!.state) {
SidecarFragment.State.SUCCESS -> {
Log.i(TAG, "Successfully switched to removable slot.")
switchToRemovableSlotSidecar!!.reset()
onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_SETUP_NAME)
}
SidecarFragment.State.ERROR -> {
Log.e(TAG, "Failed switching to removable slot.")
switchToRemovableSlotSidecar!!.reset()
callbackListener(CALLBACK_ERROR)
// TODO: showErrorDialog and using sim_action_enable_sim_fail_title and
// sim_action_enable_sim_fail_text
}
}
}
fun handleEnableMultiSimSidecarStateChange() {
when (enableMultiSimSidecar!!.state) {
SidecarFragment.State.SUCCESS -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Successfully switched to DSDS without reboot.")
handleEnableSubscriptionAfterEnablingDsds()
}
SidecarFragment.State.ERROR -> {
enableMultiSimSidecar!!.reset()
Log.i(TAG, "Failed to switch to DSDS without rebooting.")
callbackListener(CALLBACK_ERROR)
// TODO: showErrorDialog and using dsds_activation_failure_title and
// dsds_activation_failure_body_msg2
}
}
}
fun handleEnableSubscriptionAfterEnablingDsds() {
var targetSubInfo = onboardingService.targetSubInfo
if (targetSubInfo?.isEmbedded == true) {
Log.i(TAG,
"DSDS enabled, start to enable profile: " + targetSubInfo.getSubscriptionId()
)
// For eSIM operations, we simply switch to the selected eSIM profile.
switchToEuiccSubscriptionSidecar!!.run(
targetSubInfo.subscriptionId,
UiccSlotUtil.INVALID_PORT_ID,
null
)
return
}
Log.i(TAG, "DSDS enabled, start to enable pSIM profile.")
onboardingService.handleTogglePsimAction()
callbackListener(CALLBACK_FINISH)
}
@Composable
fun BottomSheetBody(nextAction: () -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(bottom = SettingsDimension.itemPaddingVertical)) {
Icon(
imageVector = Icons.Outlined.SignalCellularAlt,
contentDescription = null,
modifier = Modifier
.size(SettingsDimension.iconLarge),
tint = MaterialTheme.colorScheme.primary,
)
SettingsTitle(stringResource(R.string.sim_onboarding_bottomsheets_title))
Column(Modifier.padding(SettingsDimension.itemPadding)) {
Text(
text = stringResource(R.string.sim_onboarding_bottomsheets_msg),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
}
Button(onClick = nextAction) {
Text(stringResource(R.string.sim_onboarding_setup))
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BottomSheetImpl(
sheetState: SheetState,
nextAction: () -> Unit,
cancelAction: () -> Unit,
) {
ModalBottomSheet(
onDismissRequest = cancelAction,
sheetState = sheetState,
) {
BottomSheetBody(nextAction = nextAction)
}
LaunchedEffect(Unit) {
sheetState.show()
}
}
fun setProgressState(state: Int) {
val prefs = getSharedPreferences(
SubscriptionActionDialogActivity.SIM_ACTION_DIALOG_PREFS,
MODE_PRIVATE
)
prefs.edit().putInt(SubscriptionActionDialogActivity.KEY_PROGRESS_STATE, state).apply()
Log.i(TAG, "setProgressState:$state")
}
fun initServiceData(context: Context,targetSubId: Int, callback:(Int)->Unit) {
onboardingService.initData(targetSubId, context,callback)
}
companion object {
@JvmStatic
fun startSimOnboardingActivity(
context: Context,
subId: Int,
) {
val intent = Intent(context, SimOnboardingActivity::class.java).apply {
putExtra(SUB_ID, subId)
}
context.startActivity(intent)
}
var onboardingService:SimOnboardingService = SimOnboardingService()
const val TAG = "SimOnboardingActivity"
const val SUB_ID = "sub_id"
const val CALLBACK_ERROR = -1
const val CALLBACK_ONBOARDING_COMPLETE = 1
const val CALLBACK_SETUP_NAME = 2
const val CALLBACK_SETUP_PRIMARY_SIM = 3
const val CALLBACK_FINISH = 4
}
}

View File

@@ -23,11 +23,17 @@ import android.telephony.TelephonyManager
import android.telephony.UiccCardInfo
import android.telephony.UiccSlotInfo
import android.util.Log
import com.android.settings.spa.network.setAutomaticData
import com.android.settings.spa.network.setDefaultData
import com.android.settings.spa.network.setDefaultSms
import com.android.settings.spa.network.setDefaultVoice
import com.android.settingslib.utils.ThreadUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private const val TAG = "SimOnboardingService"
private const val INVALID = -1
private const val INVALID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
class SimOnboardingService {
var subscriptionManager:SubscriptionManager? = null
@@ -40,19 +46,72 @@ class SimOnboardingService {
var slotInfoList: List<UiccSlotInfo> = listOf()
var uiccCardInfoList: List<UiccCardInfo> = listOf()
var selectedSubInfoList: MutableList<SubscriptionInfo> = mutableListOf()
var targetPrimarySimCalls: Int = -1
var targetPrimarySimTexts: Int = -1
var targetPrimarySimMobileData: Int = -1
var targetPrimarySimCalls: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
var targetPrimarySimTexts: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
var targetPrimarySimMobileData: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
var targetPrimarySimAutoDataSwitch: Boolean = false
var targetNonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
get() {
if(targetPrimarySimMobileData == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.w(TAG, "No DDS")
return SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
return selectedSubInfoList
.filter { info ->
(info.simSlotIndex != -1) && (info.subscriptionId != targetPrimarySimMobileData)
}
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
var callback: (Int) -> Unit = {}
var isMultipleEnabledProfilesSupported: Boolean = false
get() {
if (uiccCardInfoList.isEmpty()) {
Log.w(TAG, "UICC cards info list is empty.")
return false
}
return uiccCardInfoList.stream()
.anyMatch { cardInfo: UiccCardInfo -> cardInfo.isMultipleEnabledProfilesSupported }
return uiccCardInfoList.any { it.isMultipleEnabledProfilesSupported }
}
var isRemovableSimEnabled: Boolean = false
get() {
if(slotInfoList.isEmpty()) {
Log.w(TAG, "UICC Slot info list is empty.")
return false
}
return UiccSlotUtil.isRemovableSimEnabled(slotInfoList)
}
var doesTargetSimHaveEsimOperation = false
get() {
return targetSubInfo?.isEmbedded ?: false
}
var isUsableTargetSubscriptionId = false
get() {
return SubscriptionManager.isUsableSubscriptionId(targetSubId)
}
var getActiveModemCount = 0
get() {
return telephonyManager?.getActiveModemCount() ?: 0
}
var renameMutableMap : MutableMap<Int, String> = mutableMapOf()
var userSelectedSubInfoList : MutableList<SubscriptionInfo> = mutableListOf()
var isSimSelectionFinished = false
get() {
return getActiveModemCount != 0 && userSelectedSubInfoList.size == getActiveModemCount
}
var isAllOfSlotAssigned = false
get() {
if(getActiveModemCount == 0){
Log.e(TAG, "isAllOfSlotAssigned: getActiveModemCount is 0")
return true
}
return getActiveModemCount != 0 && activeSubInfoList.size == getActiveModemCount
}
fun isValid(): Boolean {
return targetSubId != INVALID
@@ -73,18 +132,27 @@ class SimOnboardingService {
targetPrimarySimCalls = -1
targetPrimarySimTexts = -1
targetPrimarySimMobileData = -1
renameMutableMap.clear()
clearUserRecord()
}
fun initData(inputTargetSubId:Int,context: Context) {
fun clearUserRecord(){
renameMutableMap.clear()
userSelectedSubInfoList.clear()
}
fun initData(inputTargetSubId:Int,context: Context, callback: (Int) -> Unit) {
this.callback = callback
targetSubId = inputTargetSubId
subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
telephonyManager = context.getSystemService(TelephonyManager::class.java)
Log.d(
TAG, "startInit: targetSubId:$targetSubId"
)
ThreadUtils.postOnBackgroundThread {
activeSubInfoList = SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
availableSubInfoList = SubscriptionUtil.getAvailableSubscriptions(context)
targetSubInfo = availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
targetSubInfo?.let { userSelectedSubInfoList.add(it) }
Log.d(
TAG, "targetSubId: $targetSubId" + ", targetSubInfo: $targetSubInfo" +
". activeSubInfoList: $activeSubInfoList"
@@ -94,11 +162,24 @@ class SimOnboardingService {
uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
Log.d(TAG, "uiccCardInfoList: $uiccCardInfoList")
Log.d(TAG, "isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported")
targetPrimarySimCalls = SubscriptionManager.getDefaultVoiceSubscriptionId()
targetPrimarySimTexts = SubscriptionManager.getDefaultSmsSubscriptionId()
targetPrimarySimMobileData = SubscriptionManager.getDefaultDataSubscriptionId()
Log.d(
TAG,"doesTargetSimHaveEsimOperation: $doesTargetSimHaveEsimOperation" +
", isRemovableSimEnabled: $isRemovableSimEnabled" +
", isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported" +
", targetPrimarySimCalls: $targetPrimarySimCalls" +
", targetPrimarySimTexts: $targetPrimarySimTexts" +
", targetPrimarySimMobileData: $targetPrimarySimMobileData")
}
}
fun getSelectableSubscriptionInfo(): List<SubscriptionInfo> {
/**
* Return the subscriptionInfo list which has
* the target subscriptionInfo + active subscriptionInfo.
*/
fun getSelectableSubscriptionInfoList(): List<SubscriptionInfo> {
var list: MutableList<SubscriptionInfo> = mutableListOf()
list.addAll(activeSubInfoList)
if (!list.contains(targetSubInfo)) {
@@ -109,18 +190,102 @@ class SimOnboardingService {
return list.toList()
}
/**
* Return the user selected SubscriptionInfo list.
*/
fun getSelectedSubscriptionInfoList(): List<SubscriptionInfo> {
if (userSelectedSubInfoList.isEmpty()){
Log.d(TAG, "userSelectedSubInfoList is empty")
return activeSubInfoList
}
return userSelectedSubInfoList.toList()
}
fun addItemForRenaming(subInfo: SubscriptionInfo, newName: String) {
if (subInfo.displayName == newName) {
return
}
renameMutableMap[subInfo.subscriptionId] = newName
Log.d(TAG, "renameMutableMap add ${subInfo.subscriptionId} & $newName into: $renameMutableMap")
}
fun getSubscriptionInfoDisplayName(subInfo: SubscriptionInfo): String {
return renameMutableMap[subInfo.subscriptionId] ?: subInfo.displayName.toString()
}
fun startActivatingSim(callback:() -> Unit){
fun addCurrentItemForSelectedSim(){
userSelectedSubInfoList.addAll(activeSubInfoList)
}
fun addItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
userSelectedSubInfoList.add(selectedSubInfo)
}
fun removeItemForSelectedSim(selectedSubInfo: SubscriptionInfo) {
if (userSelectedSubInfoList.contains(selectedSubInfo)) {
userSelectedSubInfoList.remove(selectedSubInfo)
}
}
/**
* Return the subscriptionInfo which will be removed in the slot during the sim onboarding.
* If return Null, then no subscriptionInfo will be removed in the slot.
*/
fun getRemovedSim():SubscriptionInfo?{
return activeSubInfoList.find { !userSelectedSubInfoList.contains(it) }
}
fun handleTogglePsimAction() {
val canDisablePhysicalSubscription =
subscriptionManager?.canDisablePhysicalSubscription() == true
if (targetSubInfo != null && canDisablePhysicalSubscription) {
// TODO: to support disable case.
subscriptionManager?.setUiccApplicationsEnabled(
targetSubInfo!!.subscriptionId, /*enabled=*/true)
} else {
Log.i(TAG, "The device does not support toggling pSIM. It is enough to just "
+ "enable the removable slot."
)
}
}
fun startActivatingSim(){
// TODO: start to activate sim
callback(SimOnboardingActivity.CALLBACK_FINISH)
}
suspend fun startSetupName() {
withContext(Dispatchers.Default) {
renameMutableMap.forEach {
subscriptionManager?.setDisplayName(
it.value, it.key,
SubscriptionManager.NAME_SOURCE_USER_INPUT
)
}
// next action is SETUP_PRIMARY_SIM
callback(SimOnboardingActivity.CALLBACK_SETUP_PRIMARY_SIM)
}
}
suspend fun startSetupPrimarySim(context: Context) {
withContext(Dispatchers.Default) {
setDefaultVoice(subscriptionManager,targetPrimarySimCalls)
setDefaultSms(subscriptionManager,targetPrimarySimTexts)
setDefaultData(
context,
subscriptionManager,
null,
targetPrimarySimMobileData
)
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(targetNonDds)
setAutomaticData(telephonyManagerForNonDds, targetPrimarySimAutoDataSwitch)
// no next action, send finish
callback(SimOnboardingActivity.CALLBACK_FINISH)
}
}
}

View File

@@ -48,8 +48,6 @@ import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;
import com.android.settings.spa.SpaActivity;
import com.android.settings.spa.network.SimOnboardingPageProvider;
import java.util.ArrayList;
import java.util.Collections;
@@ -546,8 +544,7 @@ public class SubscriptionUtil {
return;
}
if (enable && Flags.isDualSimOnboardingEnabled()) {
String route = SimOnboardingPageProvider.INSTANCE.getRoute(subId);
SpaActivity.startSpaActivity(context, route);
SimOnboardingActivity.startSimOnboardingActivity(context, subId);
return;
}
context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));

View File

@@ -463,17 +463,27 @@ public class UiccSlotUtil {
if (telMgr == null) {
return false;
}
ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
List<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
return isRemovableSimEnabled(slotInfos);
}
/**
* Return whether the removable psim is enabled.
*
* @param slotInfos is a List of UiccSlotInfo.
* @return whether the removable psim is enabled.
*/
public static boolean isRemovableSimEnabled(List<UiccSlotInfo> slotInfos) {
boolean isRemovableSimEnabled =
slotInfos.stream()
.anyMatch(
slot -> slot != null
&& slot.isRemovable()
&& !slot.getIsEuicc()
&& slot.getPorts().stream().anyMatch(
port -> port.isActive())
&& slot.getPorts().stream()
.anyMatch(port -> port.isActive())
&& slot.getCardStateInfo()
== UiccSlotInfo.CARD_STATE_INFO_PRESENT);
== UiccSlotInfo.CARD_STATE_INFO_PRESENT);
Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled);
return isRemovableSimEnabled;
}

View File

@@ -41,6 +41,7 @@ import androidx.fragment.app.FragmentManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.flags.Flags;
import com.android.settings.network.CarrierConfigCache;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.ims.WifiCallingQueryImsState;
@@ -134,6 +135,17 @@ public class SimDialogActivity extends FragmentActivity {
return;
}
if (Flags.isDualSimOnboardingEnabled()
&& getProgressState() == SubscriptionActionDialogActivity.PROGRESS_IS_SHOWING
&& (dialogType == PREFERRED_PICK
|| dialogType == DATA_PICK
|| dialogType == CALLS_PICK
|| dialogType == SMS_PICK)) {
Log.d(TAG, "Finish the sim dialog since the sim onboarding is shown");
finish();
return;
}
final String tag = Integer.toString(dialogType);
final FragmentManager fragmentManager = getSupportFragmentManager();
SimDialogFragment fragment = (SimDialogFragment) fragmentManager.findFragmentByTag(tag);

View File

@@ -67,6 +67,7 @@ import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverF
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -171,6 +172,8 @@ object NetworkCellularGroupProvider : SettingsPageProvider {
.map { it.subscriptionId }
.firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
Log.d(name, "defaultDataSubId: $defaultDataSubId, nonDds: $nonDds")
}
}
@@ -195,7 +198,6 @@ fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>,
selectableSubscriptionInfoList
)
PrimarySimSectionImpl(
subscriptionManager,
activeSubscriptionInfoList,
defaultVoiceSubId,
defaultSmsSubId,
@@ -257,12 +259,49 @@ fun SimsSectionImpl(
@Composable
fun PrimarySimSectionImpl(
subscriptionManager: SubscriptionManager?,
activeSubscriptionInfoList: List<SubscriptionInfo>,
callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState
subscriptionInfoList: List<SubscriptionInfo>,
callsSelectedId: MutableIntState,
textsSelectedId: MutableIntState,
mobileDataSelectedId: MutableIntState,
nonDds: MutableIntState,
subscriptionManager: SubscriptionManager? =
LocalContext.current.getSystemService(SubscriptionManager::class.java),
coroutineScope: CoroutineScope = rememberCoroutineScope(),
context: Context = LocalContext.current,
actionSetCalls: (Int) -> Unit = {
callsSelectedId.intValue = it
coroutineScope.launch {
setDefaultVoice(subscriptionManager, it)
}
},
actionSetTexts: (Int) -> Unit = {
textsSelectedId.intValue = it
coroutineScope.launch {
setDefaultSms(subscriptionManager, it)
}
},
actionSetMobileData: (Int) -> Unit = {
mobileDataSelectedId.intValue = it
coroutineScope.launch {
// TODO: to fix the WifiPickerTracker crash when create
// the wifiPickerTrackerHelper
setDefaultData(
context,
subscriptionManager,
null/*wifiPickerTrackerHelper*/,
it
)
}
},
actionSetAutoDataSwitch: (Boolean) -> Unit = { newState ->
coroutineScope.launch {
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(nonDds.intValue)
Log.d(NetworkCellularGroupProvider.name, "NonDds:${nonDds.intValue} setAutomaticData")
setAutomaticData(telephonyManagerForNonDds, newState)
}
},
) {
var state = rememberSaveable { mutableStateOf(false) }
var callsAndSmsList = remember {
@@ -272,11 +311,11 @@ fun PrimarySimSectionImpl(
mutableListOf(ListPreferenceOption(id = -1, text = "Loading"))
}
if (activeSubscriptionInfoList.size >= 2) {
if (subscriptionInfoList.size >= 2) {
state.value = true
callsAndSmsList.clear()
dataList.clear()
for (info in activeSubscriptionInfoList) {
for (info in subscriptionInfoList) {
var item = ListPreferenceOption(
id = info.subscriptionId,
text = "${info.displayName}"
@@ -291,12 +330,10 @@ fun PrimarySimSectionImpl(
} else {
// hide the primary sim
state.value = false
Log.d("NetworkCellularGroupProvider", "Hide primary sim")
Log.d(NetworkCellularGroupProvider.name, "Hide primary sim")
}
if (state.value) {
val coroutineScope = rememberCoroutineScope()
var context = LocalContext.current
val telephonyManagerForNonDds: TelephonyManager? =
context.getSystemService(TelephonyManager::class.java)
?.createForSubscriptionId(nonDds.intValue)
@@ -305,44 +342,27 @@ fun PrimarySimSectionImpl(
}
Category(title = stringResource(id = R.string.primary_sim_title)) {
createPrimarySimListPreference(
CreatePrimarySimListPreference(
stringResource(id = R.string.primary_sim_calls_title),
callsAndSmsList,
callsSelectedId,
ImageVector.vectorResource(R.drawable.ic_phone),
) {
callsSelectedId.intValue = it
coroutineScope.launch {
setDefaultVoice(subscriptionManager, it)
}
}
createPrimarySimListPreference(
actionSetCalls
)
CreatePrimarySimListPreference(
stringResource(id = R.string.primary_sim_texts_title),
callsAndSmsList,
textsSelectedId,
Icons.AutoMirrored.Outlined.Message,
) {
textsSelectedId.intValue = it
coroutineScope.launch {
setDefaultSms(subscriptionManager, it)
}
}
createPrimarySimListPreference(
actionSetTexts
)
CreatePrimarySimListPreference(
stringResource(id = R.string.mobile_data_settings_title),
dataList,
mobileDataSelectedId,
Icons.Outlined.DataUsage,
) {
mobileDataSelectedId.intValue = it
coroutineScope.launch {
// TODO: to fix the WifiPickerTracker crash when create
// the wifiPickerTrackerHelper
setDefaultData(context,
subscriptionManager,
null/*wifiPickerTrackerHelper*/,
it)
}
}
actionSetMobileData
)
}
val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
@@ -351,25 +371,18 @@ fun PrimarySimSectionImpl(
object : SwitchPreferenceModel {
override val title = autoDataTitle
override val summary = { autoDataSummary }
override val changeable: () -> Boolean = {
nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID
}
override val checked = {
coroutineScope.launch {
withContext(Dispatchers.Default) {
automaticDataChecked.value = telephonyManagerForNonDds != null
&& telephonyManagerForNonDds.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
if (nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
coroutineScope.launch {
automaticDataChecked.value = getAutomaticData(telephonyManagerForNonDds)
}
}
automaticDataChecked.value
}
override val onCheckedChange: ((Boolean) -> Unit)? =
{ newChecked: Boolean ->
coroutineScope.launch {
setAutomaticData(telephonyManagerForNonDds, newChecked)
}
}
override val onCheckedChange: ((Boolean) -> Unit)? = {
automaticDataChecked.value = it
actionSetAutoDataSwitch(it)
}
}
})
}
@@ -428,19 +441,19 @@ private fun showEuiccSettings(context: Context): Boolean {
return MobileNetworkUtils.showEuiccSettings(context)
}
private suspend fun setDefaultVoice(
suspend fun setDefaultVoice(
subscriptionManager: SubscriptionManager?,
subId: Int): Unit = withContext(Dispatchers.Default) {
subscriptionManager?.setDefaultVoiceSubscriptionId(subId)
}
private suspend fun setDefaultSms(
suspend fun setDefaultSms(
subscriptionManager: SubscriptionManager?,
subId: Int): Unit = withContext(Dispatchers.Default) {
subscriptionManager?.setDefaultSmsSubId(subId)
}
private suspend fun setDefaultData(context: Context,
suspend fun setDefaultData(context: Context,
subscriptionManager: SubscriptionManager?,
wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
subId: Int): Unit = withContext(Dispatchers.Default) {
@@ -455,11 +468,22 @@ private suspend fun setDefaultData(context: Context,
wifiPickerTrackerHelper.setCarrierNetworkEnabled(true)
}
}
suspend fun getAutomaticData(telephonyManagerForNonDds: TelephonyManager?): Boolean =
withContext(Dispatchers.Default) {
telephonyManagerForNonDds != null
&& telephonyManagerForNonDds.isMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
}
private suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit =
withContext(Dispatchers.Default) {
telephonyManager?.setMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
newState)
//TODO: setup backup calling
}
suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit =
withContext(Dispatchers.Default) {
Log.d(
"NetworkCellularGroupProvider",
"setAutomaticData: MOBILE_DATA_POLICY_AUTO_DATA_SWITCH as $newState"
)
telephonyManager?.setMobileDataPolicyEnabled(
TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
newState
)
//TODO: setup backup calling
}

View File

@@ -27,11 +27,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
@@ -74,7 +72,7 @@ private fun labelSimBody(onboardingService: SimOnboardingService) {
SettingsBody(stringResource(R.string.sim_onboarding_label_sim_msg))
}
for (subInfo in onboardingService.getSelectableSubscriptionInfo()) {
for (subInfo in onboardingService.getSelectableSubscriptionInfoList()) {
var titleSimName by remember {
mutableStateOf(
onboardingService.getSubscriptionInfoDisplayName(subInfo)

View File

@@ -21,6 +21,7 @@ import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.os.Bundle
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
@@ -31,12 +32,12 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.network.SimOnboardingActivity
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -59,7 +60,7 @@ object SimOnboardingPageProvider : SettingsPageProvider {
private val owner = createSettingsPage()
@VisibleForTesting
var onboardingService: SimOnboardingService = SimOnboardingService()
var onboardingService: SimOnboardingService = SimOnboardingActivity.onboardingService
fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
.setUiLayoutFn {
@@ -72,18 +73,12 @@ object SimOnboardingPageProvider : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
initServiceData(arguments!!.getInt(SUB_ID))
PageImpl(onboardingService,rememberNavController())
}
fun getRoute(
subId: Int
): String = "${name}/$subId"
@Composable
fun initServiceData(targetSubId: Int) {
onboardingService.initData(targetSubId, LocalContext.current)
}
}
private fun Context.getActivity(): Activity? = when (this) {
@@ -95,7 +90,10 @@ private fun Context.getActivity(): Activity? = when (this) {
@Composable
fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostController) {
val context = LocalContext.current
var previousPageOfOnboarding: () -> Unit = { context.getActivity()?.finish() }
var finishOnboarding: () -> Unit = {
context.getActivity()?.finish()
onboardingService.callback(SimOnboardingActivity.CALLBACK_FINISH)
}
NavHost(
navController = navHostController,
@@ -103,31 +101,32 @@ fun PageImpl(onboardingService:SimOnboardingService,navHostController: NavHostCo
) {
composable(route = SimOnboardingScreen.LabelSim.name) {
val nextPage =
// Adding more conditions
if (onboardingService.isMultipleEnabledProfilesSupported) {
if (onboardingService.isMultipleEnabledProfilesSupported && onboardingService.isAllOfSlotAssigned) {
SimOnboardingScreen.SelectSim.name
} else {
onboardingService.addCurrentItemForSelectedSim()
SimOnboardingScreen.PrimarySim.name
}
SimOnboardingLabelSimImpl(
nextAction = { navHostController.navigate(nextPage) },
cancelAction = previousPageOfOnboarding,
cancelAction = finishOnboarding,
onboardingService = onboardingService
)
}
composable(route = SimOnboardingScreen.PrimarySim.name) {
SimOnboardingPrimarySimImpl(
nextAction = {
//go back and activate sim
onboardingService.callback(SimOnboardingActivity.CALLBACK_ONBOARDING_COMPLETE)
context.getActivity()?.finish()
},
cancelAction = previousPageOfOnboarding,
cancelAction = finishOnboarding,
onboardingService = onboardingService
)
}
composable(route = SimOnboardingScreen.SelectSim.name) {
SimOnboardingSelectSimImpl(
nextAction = { navHostController.navigate(SimOnboardingScreen.PrimarySim.name) },
cancelAction = previousPageOfOnboarding,
cancelAction = finishOnboarding,
onboardingService = onboardingService
)
}

View File

@@ -16,32 +16,25 @@
package com.android.settings.spa.network
import android.telephony.SubscriptionManager
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Message
import androidx.compose.material.icons.outlined.DataUsage
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.ListPreference
import com.android.settingslib.spa.widget.preference.ListPreferenceModel
import com.android.settingslib.spa.widget.preference.ListPreferenceOption
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
import com.android.settingslib.spa.widget.scaffold.SuwScaffold
import com.android.settingslib.spa.widget.ui.SettingsBody
@@ -68,84 +61,54 @@ fun SimOnboardingPrimarySimImpl(
cancelAction
),
) {
primarySimBody(onboardingService)
}
}
@Composable
private fun primarySimBody(onboardingService: SimOnboardingService) {
//TODO: Load the status from the frameworks
var callsSelectedId = rememberSaveable { mutableIntStateOf(1) }
var textsSelectedId = rememberSaveable { mutableIntStateOf(1) }
var mobileDataSelectedId = rememberSaveable { mutableIntStateOf(1) }
var automaticDataChecked by rememberSaveable { mutableStateOf(true) }
Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
}
var selectableSubscriptionInfo = onboardingService.getSelectableSubscriptionInfo()
var list = listOf(ListPreferenceOption(id = -1, text = "Loading"))
if (selectableSubscriptionInfo.size >= 2) {
list = listOf(
ListPreferenceOption(
id = selectableSubscriptionInfo[0].subscriptionId,
text = "${selectableSubscriptionInfo[0].displayName}"
),
ListPreferenceOption(
id = selectableSubscriptionInfo[1].subscriptionId,
text = "${selectableSubscriptionInfo[1].displayName}"
),
ListPreferenceOption(
id = -1,
text = stringResource(id = R.string.sim_calls_ask_first_prefs_title)
),
)
} else {
// set all of primary sim items' enable as false and showing that sim.
}
createPrimarySimListPreference(
stringResource(id = R.string.primary_sim_calls_title),
list,
callsSelectedId,
ImageVector.vectorResource(R.drawable.ic_phone),
onIdSelected = { callsSelectedId.intValue = it }
)
createPrimarySimListPreference(
stringResource(id = R.string.primary_sim_texts_title),
list,
textsSelectedId,
Icons.AutoMirrored.Outlined.Message,
onIdSelected = { textsSelectedId.intValue = it }
)
createPrimarySimListPreference(
stringResource(id = R.string.mobile_data_settings_title),
list,
mobileDataSelectedId,
Icons.Outlined.DataUsage,
onIdSelected = { mobileDataSelectedId.intValue = it }
)
val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = autoDataTitle
override val summary = { autoDataSummary }
override val checked = { automaticDataChecked }
override val onCheckedChange =
{ newChecked: Boolean -> automaticDataChecked = newChecked }
val callsSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
})
val textsSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
val mobileDataSelectedId = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
val nonDdsRemember = rememberSaveable {
mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
}
Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(id = R.string.sim_onboarding_primary_sim_msg))
}
var selectedSubscriptionInfoList = onboardingService.getSelectedSubscriptionInfoList()
callsSelectedId.intValue = onboardingService.targetPrimarySimCalls
textsSelectedId.intValue = onboardingService.targetPrimarySimTexts
mobileDataSelectedId.intValue = onboardingService.targetPrimarySimMobileData
PrimarySimSectionImpl(
subscriptionInfoList = selectedSubscriptionInfoList,
callsSelectedId = callsSelectedId,
textsSelectedId = textsSelectedId,
mobileDataSelectedId = mobileDataSelectedId,
nonDds = nonDdsRemember,
actionSetCalls = {
callsSelectedId.intValue = it
onboardingService.targetPrimarySimCalls = it},
actionSetTexts = {
textsSelectedId.intValue = it
onboardingService.targetPrimarySimTexts = it},
actionSetMobileData = {
mobileDataSelectedId.intValue = it
onboardingService.targetPrimarySimMobileData = it},
actionSetAutoDataSwitch = {
onboardingService.targetPrimarySimAutoDataSwitch = it},
)
}
}
@Composable
fun createPrimarySimListPreference(
fun CreatePrimarySimListPreference(
title: String,
list: List<ListPreferenceOption>,
selectedId: MutableIntState,
icon: ImageVector,
enable: Boolean = true,
onIdSelected: (id: Int) -> Unit
) = ListPreference(remember {
object : ListPreferenceModel {
@@ -156,7 +119,5 @@ fun createPrimarySimListPreference(
override val icon = @Composable {
SettingsIcon(icon)
}
override val enabled: () -> Boolean
get() = { enable }
}
})

View File

@@ -16,24 +16,23 @@
package com.android.settings.spa.network
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SignalCellularAlt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.network.SimOnboardingService
import com.android.settings.sim.SimDialogActivity
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.CheckboxPreference
import com.android.settingslib.spa.widget.preference.CheckboxPreferenceModel
import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton
import com.android.settingslib.spa.widget.scaffold.SuwScaffold
import com.android.settingslib.spa.widget.ui.SettingsBody
@@ -68,22 +67,38 @@ private fun selectSimBody(onboardingService: SimOnboardingService) {
Column(Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(id = R.string.sim_onboarding_select_sim_msg))
}
for (subInfo in onboardingService.getSelectableSubscriptionInfo()) {
var isFinished = rememberSaveable { mutableStateOf(false) }
isFinished.value = onboardingService.isSimSelectionFinished
for (subInfo in onboardingService.getSelectableSubscriptionInfoList()) {
var title = onboardingService.getSubscriptionInfoDisplayName(subInfo)
var summaryNumber =
subInfo.number // TODO using the SubscriptionUtil.getFormattedPhoneNumber
var changeable = subInfo.isActive
var checked by rememberSaveable { mutableStateOf(!subInfo.isActive) }
var checked = rememberSaveable {
mutableStateOf(
onboardingService.getSelectedSubscriptionInfoList().contains(subInfo)
)
}
CheckboxPreference(remember {
object : CheckboxPreferenceModel {
override val title = title
override val summary: () -> String
get() = { summaryNumber }
override val checked = { checked }
override val changeable = { changeable }
override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
override val checked = { checked.value }
override val onCheckedChange = { newChecked: Boolean ->
checked.value = newChecked
if (newChecked) {
onboardingService.addItemForSelectedSim(subInfo)
} else {
onboardingService.removeItemForSelectedSim(subInfo)
}
isFinished.value = onboardingService.isSimSelectionFinished
}
override val changeable = {
subInfo.isActive
&& (!isFinished.value || (isFinished.value && checked.value))
}
}
})
}
}
}

View File

@@ -133,6 +133,11 @@ public class PowerUsageFeatureProviderImplTest {
assertThat(mPowerFeatureProvider.isSmartBatterySupported()).isFalse();
}
@Test
public void testIsAdaptiveChargingSupported_returnFalse() {
assertThat(mPowerFeatureProvider.isAdaptiveChargingSupported()).isFalse();
}
@Test
public void testGetResumeChargeIntentWithoutDockDefender_returnNull() {
assertThat(mPowerFeatureProvider.getResumeChargeIntent(false)).isNull();

View File

@@ -0,0 +1,129 @@
/*
* 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.fuelgauge.batterytip;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import org.junit.Before;
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.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = SettingsShadowResources.class)
public class BatteryManagerPreferenceControllerTest {
private static final int ON = 1;
private static final int OFF = 0;
@Mock
private AppOpsManager mAppOpsManager;
private Context mContext;
private Preference mPreference;
private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
private BatteryManagerPreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager);
mFeatureFactory = FakeFeatureFactory.setupForTest();
mPreference = new Preference(mContext);
mController = new BatteryManagerPreferenceController(mContext);
mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
}
@Test
public void updateState_smartBatteryWithRestrictApps_showSummary() {
mController.updateSummary(mPreference, 2);
assertThat(mPreference.getSummary()).isEqualTo("2 apps restricted");
}
@Test
public void updateState_smartBatteryWithoutRestriction_showSummary() {
when(mPowerUsageFeatureProvider.isSmartBatterySupported()).thenReturn(true);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, ON);
mController.updateState(mPreference);
assertThat(mPreference.getSummary()).isEqualTo("Detecting when apps drain battery");
}
@Test
public void getAvailabilityStatus_supportBatteryManager_showPrefPage() {
SettingsShadowResources.overrideResource(
R.bool.config_battery_manager_consider_ac, true);
when(mPowerUsageFeatureProvider.isBatteryManagerSupported()).thenReturn(true);
when(mPowerUsageFeatureProvider.isAdaptiveChargingSupported()).thenReturn(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BatteryManagerPreferenceController.AVAILABLE_UNSEARCHABLE);
}
@Test
public void getAvailabilityStatus_notSupportBatteryManager_notShowPrefPage() {
when(mPowerUsageFeatureProvider.isBatteryManagerSupported()).thenReturn(false);
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BatteryManagerPreferenceController.UNSUPPORTED_ON_DEVICE);
}
@Test
public void getAvailabilityStatus_supportBatteryManagerWithoutAC_notShowPrefPage() {
SettingsShadowResources.overrideResource(
R.bool.config_battery_manager_consider_ac, true);
when(mPowerUsageFeatureProvider.isBatteryManagerSupported()).thenReturn(true);
when(mPowerUsageFeatureProvider.isAdaptiveChargingSupported()).thenReturn(false);
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BatteryManagerPreferenceController.UNSUPPORTED_ON_DEVICE);
}
@Test
public void getAvailabilityStatus_ignoreBatteryManagerWithoutAC_showPrefPage() {
SettingsShadowResources.overrideResource(
R.bool.config_battery_manager_consider_ac, false);
when(mPowerUsageFeatureProvider.isBatteryManagerSupported()).thenReturn(true);
when(mPowerUsageFeatureProvider.isAdaptiveChargingSupported()).thenReturn(false);
assertThat(mController.getAvailabilityStatus()).isEqualTo(
BatteryManagerPreferenceController.AVAILABLE_UNSEARCHABLE);
}
}

View File

@@ -108,7 +108,7 @@ class SimOnboardingLabelSimTest {
on { targetSubInfo }.doReturn(SUB_INFO_1)
on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
on { getSelectableSubscriptionInfo() }.doReturn(
on { getSelectableSubscriptionInfoList() }.doReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2,
@@ -139,7 +139,7 @@ class SimOnboardingLabelSimTest {
on { targetSubInfo }.doReturn(SUB_INFO_1)
on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
on { getSelectableSubscriptionInfo() }.doReturn(
on { getSelectableSubscriptionInfoList() }.doReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2,

View File

@@ -91,6 +91,7 @@ class SimOnboardingPageProviderTest {
fun simOnboardingPage_nextAction_fromLabelSimToSelectSim() {
mockSimOnboardingService.stub {
on { isMultipleEnabledProfilesSupported }.thenReturn(true)
on { isAllOfSlotAssigned }.thenReturn(true)
}
composeTestRule.setContent {

View File

@@ -108,7 +108,7 @@ class SimOnboardingSelectSimTest {
on { targetSubInfo }.doReturn(SUB_INFO_1)
on { availableSubInfoList }.doReturn(listOf(SUB_INFO_1, SUB_INFO_2, SUB_INFO_3))
on { activeSubInfoList }.doReturn(listOf(SUB_INFO_2, SUB_INFO_3))
on { getSelectableSubscriptionInfo() }.doReturn(
on { getSelectableSubscriptionInfoList() }.doReturn(
listOf(
SUB_INFO_1,
SUB_INFO_2,

View File

@@ -0,0 +1,92 @@
/*
* 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.applications.credentials;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class CredentialsPickerActivityTest {
@Mock private UserManager mUserManager;
private Context mMockContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mMockContext = spy(ApplicationProvider.getApplicationContext());
when(mMockContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
}
@Test
public void testInjectFragmentIntoIntent_normalProfile() {
Intent intent = new Intent();
CredentialsPickerActivity.injectFragmentIntoIntent(mMockContext, intent);
assertThat(intent.getStringExtra(CredentialsPickerActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DefaultCombinedPicker.class.getName());
}
@Test
public void testInjectFragmentIntoIntent_workProfile() {
Intent intent = new Intent();
// Simulate managed / work profile.
when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
assertThat(DefaultCombinedPickerWork.isUserHandledByFragment(mUserManager, 10)).isTrue();
CredentialsPickerActivity.injectFragmentIntoIntent(mMockContext, intent);
assertThat(intent.getStringExtra(CredentialsPickerActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DefaultCombinedPickerWork.class.getName());
}
@Test
public void testInjectFragmentIntoIntent_privateProfile() {
Intent intent = new Intent();
// Simulate private profile.
UserHandle privateUser = new UserHandle(100);
when(mUserManager.getUserInfo(100))
.thenReturn(new UserInfo(100, "", "", 0, UserManager.USER_TYPE_PROFILE_PRIVATE));
when(mUserManager.getUserProfiles()).thenReturn(Lists.newArrayList(privateUser));
assertThat(DefaultCombinedPickerPrivate.isUserHandledByFragment(mUserManager)).isTrue();
CredentialsPickerActivity.injectFragmentIntoIntent(mMockContext, intent);
assertThat(intent.getStringExtra(CredentialsPickerActivity.EXTRA_SHOW_FRAGMENT))
.isEqualTo(DefaultCombinedPickerPrivate.class.getName());
}
}

View File

@@ -87,16 +87,14 @@ public class DefaultCombinedPreferenceControllerTest {
@Test
public void ensureSettingsActivityIntentCreatedSuccessfully() {
DefaultCombinedPreferenceController dcpc =
new DefaultCombinedPreferenceController(mContext);
// Ensure that the settings activity is only created if we haved the right combination
// of package and class name.
assertThat(dcpc.createSettingsActivityIntent(null, null)).isNull();
assertThat(dcpc.createSettingsActivityIntent("", null)).isNull();
assertThat(dcpc.createSettingsActivityIntent("", "")).isNull();
assertThat(dcpc.createSettingsActivityIntent("com.test", "")).isNull();
assertThat(dcpc.createSettingsActivityIntent("com.test", "ClassName")).isNotNull();
assertThat(CombinedProviderInfo.createSettingsActivityIntent(null, null)).isNull();
assertThat(CombinedProviderInfo.createSettingsActivityIntent("", null)).isNull();
assertThat(CombinedProviderInfo.createSettingsActivityIntent("", "")).isNull();
assertThat(CombinedProviderInfo.createSettingsActivityIntent("com.test", "")).isNull();
assertThat(CombinedProviderInfo.createSettingsActivityIntent("com.test", "ClassName"))
.isNotNull();
}
@Test
@@ -112,13 +110,13 @@ public class DefaultCombinedPreferenceControllerTest {
// Update the preference to use the provider and make sure the view
// was updated.
dcpc.updatePreferenceForProvider(ppp, "App Name", "Subtitle", appIcon, null);
dcpc.updatePreferenceForProvider(ppp, "App Name", "Subtitle", appIcon, null, null);
assertThat(ppp.getTitle().toString()).isEqualTo("App Name");
assertThat(ppp.getSummary().toString()).isEqualTo("Subtitle");
assertThat(ppp.getIcon()).isEqualTo(appIcon);
// Set the preference back to none and make sure the view was updated.
dcpc.updatePreferenceForProvider(ppp, null, null, null, null);
dcpc.updatePreferenceForProvider(ppp, null, null, null, null, null);
assertThat(ppp.getTitle().toString()).isEqualTo("None");
assertThat(ppp.getSummary()).isNull();
assertThat(ppp.getIcon()).isNull();

View File

@@ -0,0 +1,201 @@
/*
* 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.biometrics.fingerprint.feature
import android.animation.Animator
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.view.View
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_CENTER
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_FINGERTIP
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_LEFT_EDGE
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_NO_ANIMATION
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE
import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN
import com.google.common.truth.Truth.assertThat
import kotlin.math.roundToInt
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class)
class SfpsEnrollmentFeatureImplTest {
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Spy
private val context: Context = ApplicationProvider.getApplicationContext()
private val settingsPackageName = "com.android.settings"
private lateinit var settingsContext: Context
@Mock
private lateinit var mockFingerprintManager: FingerprintManager
private val mSfpsEnrollmentFeatureImpl: SfpsEnrollmentFeatureImpl = SfpsEnrollmentFeatureImpl()
@Before
fun setUp() {
assertThat(mSfpsEnrollmentFeatureImpl).isInstanceOf(SfpsEnrollmentFeatureImpl::class.java)
whenever(context.getSystemService(FingerprintManager::class.java))
.thenReturn(mockFingerprintManager)
doReturn(0f).`when`(mockFingerprintManager).getEnrollStageThreshold(0)
doReturn(0.36f).`when`(mockFingerprintManager).getEnrollStageThreshold(1)
doReturn(0.52f).`when`(mockFingerprintManager).getEnrollStageThreshold(2)
doReturn(0.76f).`when`(mockFingerprintManager).getEnrollStageThreshold(3)
doReturn(1f).`when`(mockFingerprintManager).getEnrollStageThreshold(4)
settingsContext = context.createPackageContext(settingsPackageName, 0)
}
@Test
fun testGetEnrollStageThreshold() {
assertThat(mSfpsEnrollmentFeatureImpl.getEnrollStageThreshold(context, 0)).isEqualTo(0f)
assertThat(mSfpsEnrollmentFeatureImpl.getEnrollStageThreshold(context, 1)).isEqualTo(0.36f)
assertThat(mSfpsEnrollmentFeatureImpl.getEnrollStageThreshold(context, 2)).isEqualTo(0.52f)
assertThat(mSfpsEnrollmentFeatureImpl.getEnrollStageThreshold(context, 3)).isEqualTo(0.76f)
assertThat(mSfpsEnrollmentFeatureImpl.getEnrollStageThreshold(context, 4)).isEqualTo(1f)
}
@Test
fun testGetHelpAnimator() {
val mockView: View = mock(View::class.java)
val animator: Animator = mSfpsEnrollmentFeatureImpl.getHelpAnimator(mockView)
assertThat(animator.duration).isEqualTo(550)
}
@Test
fun testGetCurrentSfpsEnrollStage() {
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(0, null))
.isEqualTo(STAGE_UNKNOWN)
val mapper = { i: Int ->
(25 * mSfpsEnrollmentFeatureImpl.getEnrollStageThreshold(context, i)).roundToInt()
}
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(-1, mapper))
.isEqualTo(SFPS_STAGE_NO_ANIMATION)
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(0, mapper))
.isEqualTo(SFPS_STAGE_CENTER)
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(9, mapper))
.isEqualTo(SFPS_STAGE_FINGERTIP)
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(13, mapper))
.isEqualTo(SFPS_STAGE_LEFT_EDGE)
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(19, mapper))
.isEqualTo(SFPS_STAGE_RIGHT_EDGE)
assertThat(mSfpsEnrollmentFeatureImpl.getCurrentSfpsEnrollStage(25, mapper))
.isEqualTo(SFPS_STAGE_RIGHT_EDGE)
}
@Test
fun testGetFeaturedStageHeaderResource() {
val type = "string"
assertThat(
mSfpsEnrollmentFeatureImpl.getFeaturedStageHeaderResource(SFPS_STAGE_NO_ANIMATION)
).isEqualTo(
settingsContext.resources.getIdentifier(
"security_settings_fingerprint_enroll_repeat_title",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getFeaturedStageHeaderResource(SFPS_STAGE_CENTER)
).isEqualTo(
settingsContext.resources.getIdentifier(
"security_settings_sfps_enroll_finger_center_title",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getFeaturedStageHeaderResource(SFPS_STAGE_FINGERTIP)
).isEqualTo(
settingsContext.resources.getIdentifier(
"security_settings_sfps_enroll_fingertip_title",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getFeaturedStageHeaderResource(SFPS_STAGE_LEFT_EDGE)
).isEqualTo(
settingsContext.resources.getIdentifier(
"security_settings_sfps_enroll_left_edge_title",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getFeaturedStageHeaderResource(SFPS_STAGE_RIGHT_EDGE)
).isEqualTo(
settingsContext.resources.getIdentifier(
"security_settings_sfps_enroll_right_edge_title",
type,
settingsPackageName)
)
}
@Test
fun testGetSfpsEnrollLottiePerStage() {
val type = "raw"
assertThat(
mSfpsEnrollmentFeatureImpl.getSfpsEnrollLottiePerStage(SFPS_STAGE_NO_ANIMATION)
).isEqualTo(
settingsContext.resources.getIdentifier(
"sfps_lottie_no_animation",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getSfpsEnrollLottiePerStage(SFPS_STAGE_CENTER)
).isEqualTo(
settingsContext.resources.getIdentifier(
"sfps_lottie_pad_center",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getSfpsEnrollLottiePerStage(SFPS_STAGE_FINGERTIP)
).isEqualTo(
settingsContext.resources.getIdentifier(
"sfps_lottie_tip",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getSfpsEnrollLottiePerStage(SFPS_STAGE_LEFT_EDGE)
).isEqualTo(
settingsContext.resources.getIdentifier(
"sfps_lottie_left_edge",
type,
settingsPackageName)
)
assertThat(
mSfpsEnrollmentFeatureImpl.getSfpsEnrollLottiePerStage(SFPS_STAGE_RIGHT_EDGE)
).isEqualTo(
settingsContext.resources.getIdentifier(
"sfps_lottie_right_edge",
type,
settingsPackageName)
)
}
}

View File

@@ -97,7 +97,6 @@ public class TermsOfAddressFeminineControllerTest {
selectedPreference.performClick();
assertThat(selectedPreference.getKey()).isEqualTo(KEY_FEMININE);
assertThat(selectedPreference.isSelected()).isTrue();
assertThat(pref.isSelected()).isFalse();
}

View File

@@ -97,7 +97,6 @@ public class TermsOfAddressMasculineControllerTest {
selectedPreference.performClick();
assertThat(selectedPreference.getKey()).isEqualTo(KEY_MASCULINE);
assertThat(selectedPreference.isSelected()).isTrue();
assertThat(pref.isSelected()).isFalse();
}

View File

@@ -97,7 +97,6 @@ public class TermsOfAddressNeutralControllerTest {
selectedPreference.performClick();
assertThat(selectedPreference.getKey()).isEqualTo(KEY_NEUTRAL);
assertThat(selectedPreference.isSelected()).isTrue();
assertThat(pref.isSelected()).isFalse();
}

View File

@@ -34,7 +34,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import androidx.preference.SwitchPreferenceCompat;
import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -275,7 +275,7 @@ public class ChannelListPreferenceControllerTest {
PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
assertEquals("group", group.getKey());
assertEquals(3, group.getPreferenceCount());
SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
SwitchPreferenceCompat groupBlockPref = (SwitchPreferenceCompat) group.getPreference(0);
assertNull(groupBlockPref.getKey());
assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle().toString());
assertTrue(groupBlockPref.isChecked());
@@ -297,7 +297,7 @@ public class ChannelListPreferenceControllerTest {
PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
assertEquals("group", group.getKey());
assertEquals(1, group.getPreferenceCount());
SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
SwitchPreferenceCompat groupBlockPref = (SwitchPreferenceCompat) group.getPreference(0);
assertNull(groupBlockPref.getKey());
assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle().toString());
assertFalse(groupBlockPref.isChecked());
@@ -311,7 +311,7 @@ public class ChannelListPreferenceControllerTest {
PreferenceGroup group = (PreferenceGroup) mGroupList.getPreference(0);
assertEquals("group", group.getKey());
assertEquals(3, group.getPreferenceCount());
SwitchPreference groupBlockPref = (SwitchPreference) group.getPreference(0);
SwitchPreferenceCompat groupBlockPref = (SwitchPreferenceCompat) group.getPreference(0);
assertNull(groupBlockPref.getKey());
assertEquals("All \"My Group\" notifications", groupBlockPref.getTitle().toString());
assertTrue(groupBlockPref.isChecked());