diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 310ed3840ee..6e82b11ad29 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -111,6 +111,7 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/dialog_sim_status.xml b/res/layout/dialog_sim_status.xml index 27d12a8cb1c..c169e586d1d 100644 --- a/res/layout/dialog_sim_status.xml +++ b/res/layout/dialog_sim_status.xml @@ -166,6 +166,7 @@ android:id="@+id/esim_id_value" android:layout_width="match_parent" android:layout_height="wrap_content" + android:textIsSelectable="true" android:text="@string/device_info_not_available"/> Next - + Languages @@ -470,6 +470,29 @@ Add a language + + Language + + + Preferred Language + + + App Languages + + + Set the language for each app + + + App Language + + + Suggested languages + + + All languages + + + The app is set to %1$s by default and doesn\u2019t support multiple languages. Remove selected language? diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml index 14429607ec2..562c7d17192 100644 --- a/res/xml/app_info_settings.xml +++ b/res/xml/app_info_settings.xml @@ -88,6 +88,12 @@ android:title="@string/power_usage_summary_title" android:summary="@string/summary_placeholder" /> + + + + + + + + + + + + diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index f33ef5cb444..f2b6d8a1df9 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -19,12 +19,28 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/language_settings"> + + - + + + + + + locales, Locale appLocale) { + if (locales == null) { + return; + } + + for (Locale locale : locales) { + RadioButtonPreference pref = new RadioButtonPreference(getContext()); + pref.setTitle(locale.getDisplayName(locale)); + pref.setKey(locale.toLanguageTag()); + pref.setChecked(locale.equals(appLocale)); + pref.setOnClickListener(this); + group.addPreference(pref); + } + } + + @VisibleForTesting + static class AppLocaleDetailsHelper { + private String mPackageName; + private Context mContext; + private TelephonyManager mTelephonyManager; + private LocaleManager mLocaleManager; + + private Collection mSuggestedLocales = new ArrayList<>();; + private Collection mSupportedLocales = new ArrayList<>();; + + AppLocaleDetailsHelper(Context context, String packageName) { + mContext = context; + mPackageName = packageName; + mTelephonyManager = context.getSystemService(TelephonyManager.class); + mLocaleManager = context.getSystemService(LocaleManager.class); + } + + /** Handle suggested and supported locales for UI display. */ + public void handleAllLocalesData() { + clearLocalesData(); + handleSuggestedLocales(); + handleSupportedLocales(); + } + + /** Gets suggested locales in the app. */ + public Collection getSuggestedLocales() { + return mSuggestedLocales; + } + + /** Gets supported locales in the app. */ + public Collection getSupportedLocales() { + return mSupportedLocales; + } + + @VisibleForTesting + void handleSuggestedLocales() { + LocaleList currentSystemLocales = getCurrentSystemLocales(); + Locale simLocale = mTelephonyManager.getSimLocale(); + Locale appLocale = getAppDefaultLocale(mContext, mPackageName); + // 1st locale in suggested languages group. + if (appLocale != null) { + mSuggestedLocales.add(appLocale); + } + // 2nd locale in suggested languages group. + if (simLocale != null && !simLocale.equals(appLocale)) { + mSuggestedLocales.add(simLocale); + } + // Other locales in suggested languages group. + for (int i = 0; i < currentSystemLocales.size(); i++) { + Locale locale = currentSystemLocales.get(i); + if (!locale.equals(appLocale) && !locale.equals(simLocale)) { + mSuggestedLocales.add(locale); + } + } + } + + @VisibleForTesting + void handleSupportedLocales() { + //TODO Waiting for PackageManager api + String[] languages = getAssetSystemLocales(); + + for (String language : languages) { + mSupportedLocales.add(Locale.forLanguageTag(language)); + } + if (mSuggestedLocales != null || !mSuggestedLocales.isEmpty()) { + mSupportedLocales.removeAll(mSuggestedLocales); + } + } + + private void clearLocalesData() { + mSuggestedLocales.clear(); + mSupportedLocales.clear(); + } + + /** Gets per app's default locale */ + public static Locale getAppDefaultLocale(Context context, String packageName) { + LocaleManager localeManager = context.getSystemService(LocaleManager.class); + LocaleList localeList = (localeManager == null) + ? new LocaleList() : localeManager.getApplicationLocales(packageName); + return localeList.isEmpty() ? null : localeList.get(0); + } + + /** Sets per app's default language to system. */ + public void setAppDefaultLocale(String languageTag) { + if (languageTag.isEmpty()) { + Log.w(TAG, "[setAppDefaultLocale] No language tag."); + return; + } + setAppDefaultLocale(LocaleList.forLanguageTags(languageTag)); + } + + /** Sets per app's default language to system. */ + public void setAppDefaultLocale(LocaleList localeList) { + if (mLocaleManager == null) { + Log.w(TAG, "LocaleManager is null, and cannot set the app locale up."); + return; + } + mLocaleManager.setApplicationLocales(mPackageName, localeList); + } + + @VisibleForTesting + LocaleList getCurrentSystemLocales() { + return Resources.getSystem().getConfiguration().getLocales(); + } + + @VisibleForTesting + String[] getAssetSystemLocales() { + try { + PackageManager packageManager = mContext.getPackageManager(); + return packageManager.getResourcesForApplication( + packageManager.getPackageInfo(mPackageName, PackageManager.MATCH_ALL) + .applicationInfo).getAssets().getNonSystemLocales(); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Can not found the package name : " + e); + } + return new String[0]; + } + } +} diff --git a/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java new file mode 100644 index 00000000000..f1e43ad1ee9 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AppLocalePreferenceController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 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.appinfo; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.SettingsPreferenceFragment; + +/** + * A controller to update current locale information of application. + */ +public class AppLocalePreferenceController extends AppInfoPreferenceControllerBase { + public AppLocalePreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return FeatureFlagUtils + .isEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + protected Class getDetailFragmentClass() { + return AppLocaleDetails.class; + } + + @Override + public CharSequence getSummary() { + return AppLocaleDetails.getSummary(mContext, mParent.getAppEntry().info.packageName); + } +} diff --git a/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceController.java b/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceController.java new file mode 100644 index 00000000000..aa12b626eb7 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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.appinfo; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import com.android.settings.core.BasePreferenceController; + +/** + * A controller to update current locale information of application + * and a entry to launch {@link ManageApplications}. + * TODO(209775925) After feature release, this class may be removed. + */ +public class ManageAppLocalePreferenceController extends BasePreferenceController { + public ManageAppLocalePreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return FeatureFlagUtils + .isEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } +} diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 7b5c2218f7d..d98548280fb 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -108,6 +108,7 @@ import com.android.settings.applications.AppStorageSettings; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails; import com.android.settings.applications.appinfo.AppInfoDashboardFragment; +import com.android.settings.applications.appinfo.AppLocaleDetails; import com.android.settings.applications.appinfo.DrawOverlayDetails; import com.android.settings.applications.appinfo.ExternalSourcesDetails; import com.android.settings.applications.appinfo.ManageExternalStorageDetails; @@ -231,6 +232,7 @@ public class ManageApplications extends InstrumentedFragment public static final int LIST_MANAGE_EXTERNAL_STORAGE = 11; public static final int LIST_TYPE_ALARMS_AND_REMINDERS = 12; public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13; + public static final int LIST_TYPE_APPS_LOCAL = 14; // List types that should show instant apps. public static final Set LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList( @@ -318,6 +320,8 @@ public class ManageApplications extends InstrumentedFragment ServiceManager.getService(Context.USAGE_STATS_SERVICE)); mNotificationBackend = new NotificationBackend(); mSortOrder = R.id.sort_order_recent_notification; + } else if (className.equals(AppLocaleDetails.class.getName())) { + mListType = LIST_TYPE_APPS_LOCAL; } else { mListType = LIST_TYPE_MAIN; } @@ -500,6 +504,8 @@ public class ManageApplications extends InstrumentedFragment return SettingsEnums.ALARMS_AND_REMINDERS; case LIST_TYPE_MEDIA_MANAGEMENT_APPS: return SettingsEnums.MEDIA_MANAGEMENT_APPS; + case LIST_TYPE_APPS_LOCAL: + return SettingsEnums.APPS_LOCALE_LIST; default: return SettingsEnums.PAGE_UNKNOWN; } @@ -623,6 +629,10 @@ public class ManageApplications extends InstrumentedFragment startAppInfoFragment(MediaManagementAppsDetails.class, R.string.media_management_apps_title); break; + case LIST_TYPE_APPS_LOCAL: + startAppInfoFragment(AppLocaleDetails.class, + R.string.app_locale_picker_title); + break; // TODO: Figure out if there is a way where we can spin up the profile's settings // process ahead of time, to avoid a long load of data when user clicks on a managed // app. Maybe when they load the list of apps that contains managed profile apps. @@ -899,6 +909,8 @@ public class ManageApplications extends InstrumentedFragment screenTitle = R.string.alarms_and_reminders_title; } else if (className.equals(Settings.NotificationAppListActivity.class.getName())) { screenTitle = R.string.app_notifications_title; + } else if (className.equals(AppLocaleDetails.class.getName())) { + screenTitle = R.string.app_locales_picker_menu_title; } else { if (screenTitle == -1) { screenTitle = R.string.all_apps; @@ -1521,6 +1533,10 @@ public class ManageApplications extends InstrumentedFragment case LIST_TYPE_MEDIA_MANAGEMENT_APPS: holder.setSummary(MediaManagementAppsDetails.getSummary(mContext, entry)); break; + case LIST_TYPE_APPS_LOCAL: + holder.setSummary(AppLocaleDetails + .getSummary(mContext, entry.info.packageName)); + break; default: holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize); break; diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 87e176e2bc5..e94fafc3cd7 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -123,6 +123,7 @@ import com.android.settings.network.NetworkDashboardFragment; import com.android.settings.network.NetworkProviderSettings; import com.android.settings.network.apn.ApnEditor; import com.android.settings.network.apn.ApnSettings; +import com.android.settings.network.telephony.MobileNetworkSettings; import com.android.settings.network.telephony.NetworkSelectSettings; import com.android.settings.nfc.AndroidBeam; import com.android.settings.nfc.PaymentSettings; @@ -329,7 +330,8 @@ public class SettingsGateway { AlarmsAndRemindersDetails.class.getName(), MediaManagementAppsDetails.class.getName(), AutoBrightnessSettings.class.getName(), - OneHandedSettings.class.getName() + OneHandedSettings.class.getName(), + MobileNetworkSettings.class.getName() }; public static final String[] SETTINGS_FOR_RESTRICTED = { diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java index 94d1ff5da5e..1a85a7fa341 100644 --- a/src/com/android/settings/network/MobileNetworkSummaryController.java +++ b/src/com/android/settings/network/MobileNetworkSummaryController.java @@ -36,7 +36,7 @@ import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.network.helper.SubscriptionAnnotation; -import com.android.settings.network.telephony.MobileNetworkActivity; +import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.AddPreference; import com.android.settingslib.Utils; @@ -190,12 +190,8 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController SubscriptionAnnotation info = subs.get(0); if (info.getSubInfo().isEmbedded() || info.isActive() || mStatusCache.isPhysicalSimDisableSupport()) { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId()); - // MobileNetworkActivity is singleTask, set SplitPairRule to show in 2-pane. - MobileNetworkTwoPaneUtils.registerTwoPaneForMobileNetwork(mContext, intent, - null); - mContext.startActivity(intent); + MobileNetworkUtils.launchMobileNetworkSettings(mContext, + info.getSubInfo()); return true; } diff --git a/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java b/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java index 1bb50cbaf5e..48cd8aa9c0e 100644 --- a/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java +++ b/src/com/android/settings/network/NetworkProviderDownloadedSimListController.java @@ -36,7 +36,6 @@ import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.network.telephony.MobileNetworkActivity; import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -128,11 +127,7 @@ public class NetworkProviderDownloadedSimListController extends pref.setSummary(getSummary(subId)); pref.setOnPreferenceClickListener(clickedPref -> { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId()); - // MobileNetworkActivity is singleTask, set SplitPairRule to show in 2-pane. - MobileNetworkTwoPaneUtils.registerTwoPaneForMobileNetwork(mContext, intent, null); - mContext.startActivity(intent); + MobileNetworkUtils.launchMobileNetworkSettings(mContext, info); return true; }); mPreferences.put(subId, pref); diff --git a/src/com/android/settings/network/NetworkProviderSimListController.java b/src/com/android/settings/network/NetworkProviderSimListController.java index d6eaab8e14a..77d665a14b0 100644 --- a/src/com/android/settings/network/NetworkProviderSimListController.java +++ b/src/com/android/settings/network/NetworkProviderSimListController.java @@ -36,7 +36,7 @@ import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.network.telephony.MobileNetworkActivity; +import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -126,12 +126,7 @@ public class NetworkProviderSimListController extends AbstractPreferenceControll SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true); } else { - final Intent intent = new Intent(mContext, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId()); - // MobileNetworkActivity is singleTask, set SplitPairRule to show in 2-pane. - MobileNetworkTwoPaneUtils.registerTwoPaneForMobileNetwork(mContext, intent, - null); - mContext.startActivity(intent); + MobileNetworkUtils.launchMobileNetworkSettings(mContext, info); } return true; }); diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index d7e5876c5a4..1ba05025778 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -24,6 +24,7 @@ import static com.android.internal.util.CollectionUtils.emptyIfNull; import android.annotation.Nullable; import android.content.Context; import android.os.ParcelUuid; +import android.provider.Settings; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -37,6 +38,8 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.telephony.MccTable; import com.android.settings.R; +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.settingslib.DeviceInfoUtils; @@ -48,6 +51,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -643,4 +647,49 @@ public class SubscriptionUtil { private static int getDefaultDataSubscriptionId() { return SubscriptionManager.getDefaultDataSubscriptionId(); } + + + /** + * Select one of the subscription as the default subscription. + * @param subAnnoList a list of {@link SubscriptionAnnotation} + * @return ideally the {@link SubscriptionAnnotation} as expected + */ + private static SubscriptionAnnotation getDefaultSubscriptionSelection( + List subAnnoList) { + return (subAnnoList == null) ? null : + subAnnoList.stream() + .filter(SubscriptionAnnotation::isDisplayAllowed) + .filter(SubscriptionAnnotation::isActive) + .findFirst().orElse(null); + } + + public static SubscriptionInfo getSubscriptionOrDefault(Context context, int subscriptionId) { + return getSubscription(context, subscriptionId, + (subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) ? null : ( + subAnnoList -> getDefaultSubscriptionSelection(subAnnoList) + )); + } + + /** + * Get the current subscription to display. First check whether intent has {@link + * Settings#EXTRA_SUB_ID} and if so find the subscription with that id. + * If not, select default one based on {@link Function} provided. + * + * @param preferredSubscriptionId preferred subscription id + * @param selectionOfDefault when true current subscription is absent + */ + private static SubscriptionInfo getSubscription(Context context, int preferredSubscriptionId, + Function, SubscriptionAnnotation> selectionOfDefault) { + List subList = + (new SelectableSubscriptions(context, true)).call(); + Log.d(TAG, "get subId=" + preferredSubscriptionId + " from " + subList); + SubscriptionAnnotation currentSubInfo = subList.stream() + .filter(SubscriptionAnnotation::isDisplayAllowed) + .filter(subAnno -> (subAnno.getSubscriptionId() == preferredSubscriptionId)) + .findFirst().orElse(null); + if ((currentSubInfo == null) && (selectionOfDefault != null)) { + currentSubInfo = selectionOfDefault.apply(subList); + } + return (currentSubInfo == null) ? null : currentSubInfo.getSubInfo(); + } } diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java index 554188872ff..87c46974fb9 100644 --- a/src/com/android/settings/network/SubscriptionsPreferenceController.java +++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java @@ -55,7 +55,6 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.network.telephony.DataConnectivityListener; -import com.android.settings.network.telephony.MobileNetworkActivity; import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.network.telephony.SignalStrengthListener; import com.android.settings.network.telephony.TelephonyDisplayInfoListener; @@ -237,7 +236,7 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl }); mSubsGearPref.setOnGearClickListener(p -> - startMobileNetworkActivity(mContext, subInfo.getSubscriptionId())); + MobileNetworkUtils.launchMobileNetworkSettings(mContext, subInfo)); } if (!(mContext.getSystemService(UserManager.class)).isAdminUser()) { @@ -335,14 +334,6 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl mSubsGearPref.setSummary(""); } - private static void startMobileNetworkActivity(Context context, int subId) { - final Intent intent = new Intent(context, MobileNetworkActivity.class); - intent.putExtra(Settings.EXTRA_SUB_ID, subId); - // MobileNetworkActivity is singleTask, set SplitPairRule to show in 2-pane. - MobileNetworkTwoPaneUtils.registerTwoPaneForMobileNetwork(context, intent, null); - context.startActivity(intent); - } - @VisibleForTesting boolean shouldInflateSignalStrength(int subId) { return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId); diff --git a/src/com/android/settings/network/telephony/MobileNetworkActivity.java b/src/com/android/settings/network/telephony/MobileNetworkActivity.java index bbff57d56ec..d3ff5469921 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkActivity.java +++ b/src/com/android/settings/network/telephony/MobileNetworkActivity.java @@ -18,8 +18,8 @@ package com.android.settings.network.telephony; import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY; -import android.app.ActionBar; -import android.content.Context; +import android.app.Activity; +import android.app.settings.SettingsEnums; import android.content.Intent; import android.os.Bundle; import android.os.UserManager; @@ -29,84 +29,27 @@ import android.telephony.SubscriptionManager; import android.telephony.ims.ImsRcsManager; import android.text.TextUtils; import android.util.Log; -import android.view.View; -import android.widget.Toolbar; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import androidx.lifecycle.Lifecycle; -import com.android.settings.R; -import com.android.settings.core.SettingsBaseActivity; -import com.android.settings.network.ProxySubscriptionManager; +import com.android.settings.SettingsActivity; +import com.android.settings.Utils; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.network.SubscriptionUtil; -import com.android.settings.network.helper.SelectableSubscriptions; -import com.android.settings.network.helper.SubscriptionAnnotation; - -import java.util.List; -import java.util.function.Function; /** - * Activity for displaying MobileNetworkSettings + * Activity for displaying MobileNetworkSettings. + * + * @Deprecated The MobileNetworkActivity should be removed in Android U. Instead of using the + * singleTask activity which will cause an additional window transition when users launch the SIMs + * page, using the {@link com.android.settings.Settings.SubscriptionSettingsActivity} which can be + * managed by {@link SettingsActivity} and be migrated into the Settings architecture. */ -public class MobileNetworkActivity extends SettingsBaseActivity - implements ProxySubscriptionManager.OnActiveSubscriptionChangedListener { +@Deprecated +public class MobileNetworkActivity extends Activity { private static final String TAG = "MobileNetworkActivity"; - @VisibleForTesting - static final String MOBILE_SETTINGS_TAG = "mobile_settings:"; - @VisibleForTesting - static final int SUB_ID_NULL = Integer.MIN_VALUE; - - @VisibleForTesting - ProxySubscriptionManager mProxySubscriptionMgr; - - private int mCurSubscriptionId = SUB_ID_NULL; - - // This flag forces subscription information fragment to be re-created. - // Otherwise, fragment will be kept when subscription id has not been changed. - // - // Set initial value to true allows subscription information fragment to be re-created when - // Activity re-create occur. - private boolean mPendingSubscriptionChange = true; - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - validate(intent); - setIntent(intent); - - int updateSubscriptionIndex = mCurSubscriptionId; - if (intent != null) { - updateSubscriptionIndex = intent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL); - } - SubscriptionInfo info = getSubscriptionOrDefault(updateSubscriptionIndex); - if (info == null) { - Log.d(TAG, "Invalid subId request " + mCurSubscriptionId - + " -> " + updateSubscriptionIndex); - return; - } - - int oldSubId = mCurSubscriptionId; - updateSubscriptions(info, null); - - // If the subscription has changed or the new intent doesnt contain the opt in action, - // remove the old discovery dialog. If the activity is being recreated, we will see - // onCreate -> onNewIntent, so the dialog will first be recreated for the old subscription - // and then removed. - if (mCurSubscriptionId != oldSubId || !doesIntentContainOptInAction(intent)) { - removeContactDiscoveryDialog(oldSubId); - } - // evaluate showing the new discovery dialog if this intent contains an action to show the - // opt-in. - if (doesIntentContainOptInAction(intent)) { - maybeShowContactDiscoveryDialog(info); - } - } - + public static final String SHOW_CAPABILITY_DISCOVERY_OPT_IN = + "show_capability_discovery_opt_in"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -116,318 +59,63 @@ public class MobileNetworkActivity extends SettingsBaseActivity return; } - final Toolbar toolbar = findViewById(R.id.action_bar); - toolbar.setVisibility(View.VISIBLE); - setActionBar(toolbar); - - final ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - } - - getProxySubscriptionManager().setLifecycle(getLifecycle()); - - final Intent startIntent = getIntent(); - validate(startIntent); - mCurSubscriptionId = savedInstanceState != null - ? savedInstanceState.getInt(Settings.EXTRA_SUB_ID, SUB_ID_NULL) - : ((startIntent != null) - ? startIntent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL) - : SUB_ID_NULL); - // perform registration after mCurSubscriptionId been configured. - registerActiveSubscriptionsListener(); - - SubscriptionInfo subscription = getSubscriptionOrDefault(mCurSubscriptionId); - if (subscription == null) { - Log.d(TAG, "Invalid subId request " + mCurSubscriptionId); - tryToFinishActivity(); + // TODO: Move these intent's extra into SubscriptionSettingsActivity if the + // MobileNetworkActivity is removed in Android U. + Intent intent = getIntent(); + if (intent == null) { + Log.d(TAG, "onCreate(), intent = null"); + this.finish(); return; } - maybeShowContactDiscoveryDialog(subscription); - - updateSubscriptions(subscription, null); - } - - @VisibleForTesting - ProxySubscriptionManager getProxySubscriptionManager() { - if (mProxySubscriptionMgr == null) { - mProxySubscriptionMgr = ProxySubscriptionManager.getInstance(this); - } - return mProxySubscriptionMgr; - } - - @VisibleForTesting - void registerActiveSubscriptionsListener() { - getProxySubscriptionManager().addActiveSubscriptionsListener(this); - } - - /** - * Implementation of ProxySubscriptionManager.OnActiveSubscriptionChangedListener - */ - public void onChanged() { - mPendingSubscriptionChange = false; - - if (mCurSubscriptionId == SUB_ID_NULL) { - return; + Intent trampolineIntent; + final Intent subscriptionSettingsIntent = new Intent(this, + com.android.settings.Settings.SubscriptionSettingsActivity.class); + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this) || !isTaskRoot()) { + trampolineIntent = subscriptionSettingsIntent; + } else { + trampolineIntent = new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY) + .setPackage(Utils.SETTINGS_PACKAGE_NAME); + trampolineIntent.putExtra( + android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, + subscriptionSettingsIntent.toUri(Intent.URI_INTENT_SCHEME)); } - if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { - mPendingSubscriptionChange = true; - return; + int subId = intent.getIntExtra(Settings.EXTRA_SUB_ID, + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + SubscriptionInfo subInfo = SubscriptionUtil.getSubscriptionOrDefault(this, subId); + CharSequence title = SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, this); + trampolineIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title); + trampolineIntent.putExtra(Settings.EXTRA_SUB_ID, subId); + if (Settings.ACTION_MMS_MESSAGE_SETTING.equals(intent.getAction())) { + // highlight "mms_message" preference. + trampolineIntent.putExtra(EXTRA_FRAGMENT_ARG_KEY, "mms_message"); } - SubscriptionInfo subInfo = getSubscription(mCurSubscriptionId, null); - if (subInfo != null) { - if (mCurSubscriptionId != subInfo.getSubscriptionId()) { - // update based on subscription status change - removeContactDiscoveryDialog(mCurSubscriptionId); - updateSubscriptions(subInfo, null); - } - return; + if (doesIntentContainOptInAction(intent)) { + trampolineIntent.putExtra(SHOW_CAPABILITY_DISCOVERY_OPT_IN, + maybeShowContactDiscoveryDialog(subId)); } - Log.w(TAG, "subId missing: " + mCurSubscriptionId); - - // When UI is not the active one, avoid from destroy it immediately - // but wait until onResume() to see if subscription back online again. - // This is to avoid from glitch behavior of subscription which changes - // the UI when UI is considered as in the background or only partly - // visible. - if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - mPendingSubscriptionChange = true; - return; - } - - // Subscription could be missing - tryToFinishActivity(); - } - - protected void runSubscriptionUpdate(Runnable onUpdateRemaining) { - SubscriptionInfo subInfo = getSubscription(mCurSubscriptionId, null); - if (subInfo == null) { - onUpdateRemaining.run(); - tryToFinishActivity(); - return; - } - if (mCurSubscriptionId != subInfo.getSubscriptionId()) { - removeContactDiscoveryDialog(mCurSubscriptionId); - updateSubscriptions(subInfo, null); - } - onUpdateRemaining.run(); - } - - protected void tryToFinishActivity() { - if ((!isFinishing()) && (!isDestroyed())) { + startActivity(trampolineIntent); + if (isTaskRoot()) { + finishAndRemoveTask(); + } else { finish(); } } - @Override - protected void onStart() { - getProxySubscriptionManager().setLifecycle(getLifecycle()); - if (mPendingSubscriptionChange) { - mPendingSubscriptionChange = false; - runSubscriptionUpdate(() -> super.onStart()); - return; - } - super.onStart(); - } - - @Override - protected void onResume() { - if (mPendingSubscriptionChange) { - mPendingSubscriptionChange = false; - runSubscriptionUpdate(() -> super.onResume()); - return; - } - super.onResume(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mProxySubscriptionMgr == null) { - return; - } - mProxySubscriptionMgr.removeActiveSubscriptionsListener(this); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - saveInstanceState(outState); - } - - @VisibleForTesting - void saveInstanceState(@NonNull Bundle outState) { - outState.putInt(Settings.EXTRA_SUB_ID, mCurSubscriptionId); - } - - private void updateTitleAndNavigation(SubscriptionInfo subscription) { - // Set the title to the name of the subscription. If we don't have subscription info, the - // title will just default to the label for this activity that's already specified in - // AndroidManifest.xml. - if (subscription != null) { - setTitle(SubscriptionUtil.getUniqueSubscriptionDisplayName(subscription, this)); - } - } - - @VisibleForTesting - void updateSubscriptions(SubscriptionInfo subscription, Bundle savedInstanceState) { - if (subscription == null) { - return; - } - final int subscriptionIndex = subscription.getSubscriptionId(); - - updateTitleAndNavigation(subscription); - if (savedInstanceState == null) { - switchFragment(subscription); - } - - mCurSubscriptionId = subscriptionIndex; - } - - /** - * Select one of the subscription as the default subscription. - * @param subAnnoList a list of {@link SubscriptionAnnotation} - * @return ideally the {@link SubscriptionAnnotation} as expected - */ - protected SubscriptionAnnotation defaultSubscriptionSelection( - List subAnnoList) { - return (subAnnoList == null) ? null : - subAnnoList.stream() - .filter(SubscriptionAnnotation::isDisplayAllowed) - .filter(SubscriptionAnnotation::isActive) - .findFirst().orElse(null); - } - - protected SubscriptionInfo getSubscriptionOrDefault(int subscriptionId) { - return getSubscription(subscriptionId, - (subscriptionId != SUB_ID_NULL) ? null : ( - subAnnoList -> defaultSubscriptionSelection(subAnnoList) - )); - } - - /** - * Get the current subscription to display. First check whether intent has {@link - * Settings#EXTRA_SUB_ID} and if so find the subscription with that id. - * If not, select default one based on {@link Function} provided. - * - * @param preferredSubscriptionId preferred subscription id - * @param selectionOfDefault when true current subscription is absent - */ - @VisibleForTesting - protected SubscriptionInfo getSubscription(int preferredSubscriptionId, - Function, SubscriptionAnnotation> selectionOfDefault) { - List subList = - (new SelectableSubscriptions(this, true)).call(); - Log.d(TAG, "get subId=" + preferredSubscriptionId + " from " + subList); - SubscriptionAnnotation currentSubInfo = subList.stream() - .filter(SubscriptionAnnotation::isDisplayAllowed) - .filter(subAnno -> (subAnno.getSubscriptionId() == preferredSubscriptionId)) - .findFirst().orElse(null); - if ((currentSubInfo == null) && (selectionOfDefault != null)) { - currentSubInfo = selectionOfDefault.apply(subList); - } - return (currentSubInfo == null) ? null : currentSubInfo.getSubInfo(); - } - - @VisibleForTesting - SubscriptionInfo getSubscriptionForSubId(int subId) { - return SubscriptionUtil.getAvailableSubscription(this, - getProxySubscriptionManager(), subId); - } - - @VisibleForTesting - void switchFragment(SubscriptionInfo subInfo) { - final FragmentManager fragmentManager = getSupportFragmentManager(); - final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - - final int subId = subInfo.getSubscriptionId(); - final Intent intent = getIntent(); - final Bundle bundle = new Bundle(); - bundle.putInt(Settings.EXTRA_SUB_ID, subId); - if (intent != null && Settings.ACTION_MMS_MESSAGE_SETTING.equals(intent.getAction())) { - // highlight "mms_message" preference. - bundle.putString(EXTRA_FRAGMENT_ARG_KEY, "mms_message"); - } - - final String fragmentTag = buildFragmentTag(subId); - if (fragmentManager.findFragmentByTag(fragmentTag) != null) { - Log.d(TAG, "Construct fragment: " + fragmentTag); - } - - final Fragment fragment = new MobileNetworkSettings(); - fragment.setArguments(bundle); - fragmentTransaction.replace(R.id.content_frame, fragment, fragmentTag); - fragmentTransaction.commitAllowingStateLoss(); - } - - private void removeContactDiscoveryDialog(int subId) { - ContactDiscoveryDialogFragment fragment = getContactDiscoveryFragment(subId); - if (fragment != null) { - fragment.dismiss(); - } - } - - private ContactDiscoveryDialogFragment getContactDiscoveryFragment(int subId) { - // In the case that we are rebuilding this activity after it has been destroyed and - // recreated, look up the dialog in the fragment manager. - return (ContactDiscoveryDialogFragment) getSupportFragmentManager() - .findFragmentByTag(ContactDiscoveryDialogFragment.getFragmentTag(subId)); - } - - private void maybeShowContactDiscoveryDialog(SubscriptionInfo info) { - int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - CharSequence carrierName = ""; - if (info != null) { - subId = info.getSubscriptionId(); - carrierName = SubscriptionUtil.getUniqueSubscriptionDisplayName(info, this); - } + private boolean maybeShowContactDiscoveryDialog(int subId) { // If this activity was launched using ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN, show the // associated dialog only if the opt-in has not been granted yet. - boolean showOptInDialog = doesIntentContainOptInAction(getIntent()) - // has the carrier config enabled capability discovery? - && MobileNetworkUtils.isContactDiscoveryVisible(this, subId) + return MobileNetworkUtils.isContactDiscoveryVisible(this, subId) // has the user already enabled this configuration? && !MobileNetworkUtils.isContactDiscoveryEnabled(this, subId); - ContactDiscoveryDialogFragment fragment = getContactDiscoveryFragment(subId); - if (showOptInDialog) { - if (fragment == null) { - fragment = ContactDiscoveryDialogFragment.newInstance(subId, carrierName); - } - // Only try to show the dialog if it has not already been added, otherwise we may - // accidentally add it multiple times, causing multiple dialogs. - if (!fragment.isAdded()) { - fragment.show(getSupportFragmentManager(), - ContactDiscoveryDialogFragment.getFragmentTag(subId)); - } - } } - private boolean doesIntentContainOptInAction(Intent intent) { + public static boolean doesIntentContainOptInAction(Intent intent) { String intentAction = (intent != null ? intent.getAction() : null); return TextUtils.equals(intentAction, ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN); } - - private void validate(Intent intent) { - // Do not allow ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN without a subscription id specified, - // since we do not want the user to accidentally turn on capability polling for the wrong - // subscription. - if (doesIntentContainOptInAction(intent)) { - if (SUB_ID_NULL == intent.getIntExtra(Settings.EXTRA_SUB_ID, SUB_ID_NULL)) { - throw new IllegalArgumentException("Intent with action " - + "SHOW_CAPABILITY_DISCOVERY_OPT_IN must also include the extra " - + "Settings#EXTRA_SUB_ID"); - } - } - } - - @VisibleForTesting - String buildFragmentTag(int subscriptionId) { - return MOBILE_SETTINGS_TAG + subscriptionId; - } } diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java index ba80a8ce659..cf0f889befd 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java +++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java @@ -23,8 +23,10 @@ import android.content.Intent; import android.os.Bundle; import android.os.UserManager; import android.provider.Settings; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.ims.ImsRcsManager; import android.text.TextUtils; import android.util.Log; import android.view.Menu; @@ -39,6 +41,7 @@ import com.android.settings.datausage.BillingCyclePreferenceController; import com.android.settings.datausage.DataUsageSummaryPreferenceController; import com.android.settings.network.ActiveSubscriptionsListener; import com.android.settings.network.CarrierWifiTogglePreferenceController; +import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.telephony.cdma.CdmaSubscriptionPreferenceController; import com.android.settings.network.telephony.cdma.CdmaSystemSelectPreferenceController; import com.android.settings.network.telephony.gsm.AutoSelectPreferenceController; @@ -115,9 +118,16 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings { @Override protected List createPreferenceControllers(Context context) { - mSubId = getArguments().getInt(Settings.EXTRA_SUB_ID, - MobileNetworkUtils.getSearchableSubscriptionId(context)); - Log.i(LOG_TAG, "display subId: " + mSubId); + Intent intent = getIntent(); + if (intent != null) { + mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID, + MobileNetworkUtils.getSearchableSubscriptionId(context)); + Log.i(LOG_TAG, "display subId from intent: " + mSubId); + } else { + mSubId = getArguments().getInt(Settings.EXTRA_SUB_ID, + MobileNetworkUtils.getSearchableSubscriptionId(context)); + Log.i(LOG_TAG, "display subId from getArguments(): " + mSubId); + } if (!SubscriptionManager.isValidSubscriptionId(mSubId)) { return Arrays.asList(); @@ -131,6 +141,30 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings { public void onAttach(Context context) { super.onAttach(context); + Intent intent = getIntent(); + SubscriptionInfo info = SubscriptionUtil.getSubscriptionOrDefault(context, mSubId); + if (info == null) { + Log.d(LOG_TAG, "Invalid subId request " + mSubId); + return; + } + + int oldSubId = mSubId; + updateSubscriptions(info); + // If the subscription has changed or the new intent does not contain the opt in action, + // remove the old discovery dialog. If the activity is being recreated, we will see + // onCreate -> onNewIntent, so the dialog will first be recreated for the old subscription + // and then removed. + if (!MobileNetworkActivity.doesIntentContainOptInAction(intent)) { + removeContactDiscoveryDialog(oldSubId); + } + + // evaluate showing the new discovery dialog if this intent contains an action to show the + // opt-in. + if (MobileNetworkActivity.doesIntentContainOptInAction(intent)) { + showContactDiscoveryDialog( + SubscriptionUtil.getSubscriptionOrDefault(context, mSubId)); + } + final DataUsageSummaryPreferenceController dataUsageSummaryPreferenceController = use(DataUsageSummaryPreferenceController.class); if (dataUsageSummaryPreferenceController != null) { @@ -339,4 +373,49 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings { return context.getSystemService(UserManager.class).isAdminUser(); } }; + + private ContactDiscoveryDialogFragment getContactDiscoveryFragment(int subId) { + // In the case that we are rebuilding this activity after it has been destroyed and + // recreated, look up the dialog in the fragment manager. + return (ContactDiscoveryDialogFragment) getChildFragmentManager() + .findFragmentByTag(ContactDiscoveryDialogFragment.getFragmentTag(subId)); + } + + + private void removeContactDiscoveryDialog(int subId) { + ContactDiscoveryDialogFragment fragment = getContactDiscoveryFragment(subId); + if (fragment != null) { + fragment.dismiss(); + } + } + + private void showContactDiscoveryDialog(SubscriptionInfo info) { + if (info == null) { + Log.d(LOG_TAG, "Invalid subId request " + mSubId); + onDestroy(); + return; + } + + CharSequence carrierName = SubscriptionUtil.getUniqueSubscriptionDisplayName(info, + getContext()); + ContactDiscoveryDialogFragment fragment = getContactDiscoveryFragment(mSubId); + if (fragment == null) { + fragment = ContactDiscoveryDialogFragment.newInstance(mSubId, carrierName); + } + // Only try to show the dialog if it has not already been added, otherwise we may + // accidentally add it multiple times, causing multiple dialogs. + if (!fragment.isAdded()) { + fragment.show(getChildFragmentManager(), + ContactDiscoveryDialogFragment.getFragmentTag(mSubId)); + } + } + + private void updateSubscriptions(SubscriptionInfo subscription) { + if (subscription == null) { + return; + } + final int subscriptionIndex = subscription.getSubscriptionId(); + + mSubId = subscriptionIndex; + } } diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java index 658f65090a1..e2d158dfe5d 100644 --- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java +++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java @@ -45,6 +45,7 @@ import android.graphics.drawable.LayerDrawable; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; +import android.os.Bundle; import android.os.PersistableBundle; import android.os.SystemClock; import android.os.SystemProperties; @@ -73,9 +74,11 @@ import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.SubSettingLauncher; import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.ims.WifiCallingQueryImsState; import com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants; +import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.utils.ThreadUtils; @@ -1008,4 +1011,21 @@ public class MobileNetworkUtils { return context.getResources().getString(resId); } + public static void launchMobileNetworkSettings(Context context, SubscriptionInfo info) { + final int subId = info.getSubscriptionId(); + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + Log.d(TAG, "launchMobileNetworkSettings fail, subId is invalid"); + return; + } + + final Bundle extra = new Bundle(); + extra.putInt(Settings.EXTRA_SUB_ID, subId); + new SubSettingLauncher(context) + .setTitleText(SubscriptionUtil.getUniqueSubscriptionDisplayName(info, context)) + .setDestination(MobileNetworkSettings.class.getCanonicalName()) + .setSourceMetricsCategory(Instrumentable.METRICS_CATEGORY_UNKNOWN) + .setArguments(extra) + .launch(); + } + } diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkActivityTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkActivityTest.java index 459d77ea867..5729208ecfe 100644 --- a/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkActivityTest.java +++ b/tests/robotests/src/com/android/settings/network/telephony/MobileNetworkActivityTest.java @@ -16,13 +16,10 @@ package com.android.settings.network.telephony; -import static androidx.lifecycle.Lifecycle.State; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.robolectric.Shadows.shadowOf; import android.content.Context; @@ -30,21 +27,17 @@ import android.content.Intent; import android.os.Bundle; import android.os.UserManager; import android.provider.Settings; -import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.android.internal.telephony.TelephonyIntents; -import com.android.settings.network.ProxySubscriptionManager; +import com.android.settings.SettingsActivity; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -58,22 +51,16 @@ import org.robolectric.shadows.ShadowSubscriptionManager.SubscriptionInfoBuilder @RunWith(AndroidJUnit4.class) public class MobileNetworkActivityTest { - private static final int CURRENT_SUB_ID = 3; - private static final int PREV_SUB_ID = 1; + private static final int SUB_ID = 1; + private static final String DISPLAY_NAME = "SUB_ID"; private Context mContext; private ShadowContextImpl mShadowContextImpl; private Intent mTestIntent; - @Mock private UserManager mUserManager; - @Mock - private TelephonyManager mTelephonyManager; - private ShadowSubscriptionManager mSubscriptionManager; - private SubscriptionInfo mSubscriptionInfo1; - private SubscriptionInfo mSubscriptionInfo2; - + private SubscriptionInfo mSubscriptionInfo; private ActivityScenario mMobileNetworkActivity; @Before @@ -82,20 +69,16 @@ public class MobileNetworkActivityTest { mContext = ApplicationProvider.getApplicationContext(); mShadowContextImpl = Shadow.extract(RuntimeEnvironment.application.getBaseContext()); - + mSubscriptionManager = shadowOf(mContext.getSystemService(SubscriptionManager.class)); mShadowContextImpl.setSystemService(Context.USER_SERVICE, mUserManager); doReturn(true).when(mUserManager).isAdminUser(); - mShadowContextImpl.setSystemService(Context.TELEPHONY_SERVICE, mTelephonyManager); - doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); - - mTestIntent = new Intent(mContext, MockMobileNetworkActivity.class); - - mSubscriptionManager = shadowOf(mContext.getSystemService(SubscriptionManager.class)); - mSubscriptionInfo1 = SubscriptionInfoBuilder.newBuilder() - .setId(PREV_SUB_ID).buildSubscriptionInfo(); - mSubscriptionInfo2 = SubscriptionInfoBuilder.newBuilder() - .setId(CURRENT_SUB_ID).buildSubscriptionInfo(); + mTestIntent = new Intent(mContext, MobileNetworkActivity.class); + mSubscriptionInfo = SubscriptionInfoBuilder.newBuilder() + .setId(SUB_ID).setDisplayName(DISPLAY_NAME).buildSubscriptionInfo(); + mTestIntent.putExtra(Settings.EXTRA_SUB_ID, SUB_ID); + mTestIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, + mSubscriptionInfo.getDisplayName()); } @After @@ -105,116 +88,21 @@ public class MobileNetworkActivityTest { } } - private static class MockMobileNetworkActivity extends MobileNetworkActivity { - private MockMobileNetworkActivity() { - super(); - } - - private SubscriptionInfo mSubscriptionInFragment; - - @Override - ProxySubscriptionManager getProxySubscriptionManager() { - if (mProxySubscriptionMgr == null) { - mProxySubscriptionMgr = mock(ProxySubscriptionManager.class); - } - return mProxySubscriptionMgr; - } - - @Override - void registerActiveSubscriptionsListener() { - onChanged(); - } - - @Override - void switchFragment(SubscriptionInfo subInfo) { - mSubscriptionInFragment = subInfo; - } - } - private ActivityScenario createTargetActivity(Intent activityIntent) { return ActivityScenario.launch(activityIntent); } @Test - @Ignore - public void updateBottomNavigationView_oneSubscription_shouldNotCrash() { - mSubscriptionManager.setActiveSubscriptionInfos(mSubscriptionInfo1); - + public void onCreate_getExtraFromIntent() { + mSubscriptionManager.setActiveSubscriptionInfos(mSubscriptionInfo); mMobileNetworkActivity = createTargetActivity(mTestIntent); - mMobileNetworkActivity.moveToState(State.STARTED); - } - - @Test - @Ignore - public void updateBottomNavigationView_twoSubscription_shouldNotCrash() { - mSubscriptionManager.setActiveSubscriptionInfos(mSubscriptionInfo1, mSubscriptionInfo2); - - mMobileNetworkActivity = createTargetActivity(mTestIntent); - - mMobileNetworkActivity.moveToState(State.STARTED); - } - - @Test - @Ignore - public void switchFragment_switchBetweenTwoSubscriptions() { - mSubscriptionManager.setActiveSubscriptionInfos(mSubscriptionInfo1, mSubscriptionInfo2); - - mTestIntent.putExtra(Settings.EXTRA_SUB_ID, PREV_SUB_ID); - mMobileNetworkActivity = createTargetActivity(mTestIntent); - - mMobileNetworkActivity.moveToState(State.STARTED); - - mMobileNetworkActivity.onActivity(activity -> { - final MockMobileNetworkActivity mockActivity = (MockMobileNetworkActivity) activity; - mockActivity.switchFragment(mSubscriptionInfo1); - assertThat(mockActivity.mSubscriptionInFragment).isEqualTo(mSubscriptionInfo1); - }); - } - - @Test - @Ignore - public void switchFragment_subscriptionsUpdate_notifyByIntent() { - mSubscriptionManager.setActiveSubscriptionInfos(mSubscriptionInfo1, mSubscriptionInfo2); - - mTestIntent.putExtra(Settings.EXTRA_SUB_ID, PREV_SUB_ID); - mMobileNetworkActivity = createTargetActivity(mTestIntent); - - mMobileNetworkActivity.moveToState(State.STARTED); - - mMobileNetworkActivity.onActivity(activity -> { - final MockMobileNetworkActivity mockActivity = (MockMobileNetworkActivity) activity; - mockActivity.switchFragment(mSubscriptionInfo1); - assertThat(mockActivity.mSubscriptionInFragment).isEqualTo(mSubscriptionInfo1); - - mContext.sendBroadcast(new Intent( - CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED), null); - - mockActivity.switchFragment(mSubscriptionInfo2); - assertThat(mockActivity.mSubscriptionInFragment).isEqualTo(mSubscriptionInfo2); - - mContext.sendBroadcast(new Intent( - TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED), null); - - mockActivity.switchFragment(mSubscriptionInfo1); - assertThat(mockActivity.mSubscriptionInFragment).isEqualTo(mSubscriptionInfo1); - }); - } - - @Test - @Ignore - public void onSaveInstanceState_saveCurrentSubId() { - mSubscriptionManager.setActiveSubscriptionInfos(mSubscriptionInfo1, mSubscriptionInfo2); - - mTestIntent.putExtra(Settings.EXTRA_SUB_ID, PREV_SUB_ID); - mMobileNetworkActivity = createTargetActivity(mTestIntent); - - mMobileNetworkActivity.moveToState(State.STARTED); - mMobileNetworkActivity.onActivity(activity -> { final Bundle bundle = new Bundle(); - activity.saveInstanceState(bundle); - assertThat(bundle.getInt(Settings.EXTRA_SUB_ID)).isEqualTo(PREV_SUB_ID); + activity.onCreate(bundle); + assertThat(bundle.getInt(Settings.EXTRA_SUB_ID)).isEqualTo(SUB_ID); + assertThat(bundle.getString(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE)).isEqualTo( + DISPLAY_NAME); }); } } diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java new file mode 100644 index 00000000000..a97656c4b71 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appinfo/AppLocaleDetailsTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2021 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.appinfo; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.app.LocaleManager; +import android.content.Context; +import android.os.LocaleList; +import android.os.Looper; +import android.telephony.TelephonyManager; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.collect.Iterables; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Locale; + +@RunWith(AndroidJUnit4.class) +public class AppLocaleDetailsTest { + private static final String APP_PACKAGE_NAME = "app_package_name"; + + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private LocaleManager mLocaleManager; + + private Context mContext; + private LocaleList mSystemLocales; + private Locale mSimLocale; + private LocaleList mAppLocale; + private String[] mAssetLocales; + + @Before + @UiThreadTest + public void setUp() { + MockitoAnnotations.initMocks(this); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mContext.getSystemService(LocaleManager.class)).thenReturn(mLocaleManager); + + setupInitialLocales("en", + "uk", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_localeManagerIsNull_noCrash() { + when(mContext.getSystemService(LocaleManager.class)).thenReturn(null); + + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_1stLocaleOfSuggestedLocaleListIsAppLocale() { + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(mAppLocale.get(0))); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_2ndLocaleOfSuggestedLocaleListIsSimLocale() { + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 1); + assertTrue(locale.equals(mSimLocale)); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_withoutAppLocale_1stLocaleOfSuggestedLocaleListIsSimLocal() { + setupInitialLocales("", + "uk", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(mSimLocale)); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_noAppAndSimLocale_1stLocaleIsFirstOneInSystemLocales() { + setupInitialLocales("", + "", + "en, uk, jp, ne", + new String[]{"en", "ne", "ms", "pa"}); + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + Locale locale = Iterables.get(helper.getSuggestedLocales(), 0); + assertTrue(locale.equals(mSystemLocales.get(0))); + } + + @Test + @UiThreadTest + public void handleAllLocalesData_supportLocaleListIsNotEmpty() { + DummyAppLocaleDetailsHelper helper = + new DummyAppLocaleDetailsHelper(mContext, APP_PACKAGE_NAME); + + helper.handleAllLocalesData(); + + assertFalse(helper.getSupportedLocales().isEmpty()); + } + + /** + * Sets the initial Locale data + * + * @param appLocale Application locale, it shall be a language tag. + * example: "en" + * @param simLocale SIM carrier locale, it shall be a language tag. + * example: "en" + * @param systemLocales System locales, a locale list by a multiple language tags with comma. + * example: "en, uk, jp" + * @param assetLocales Asset locales, a locale list by a multiple language tags with String + * array. + * example: new String[] {"en", "ne", "ms", "pa"} + */ + private void setupInitialLocales(String appLocale, + String simLocale, + String systemLocales, + String[] assetLocales) { + mAppLocale = LocaleList.forLanguageTags(appLocale); + mSimLocale = Locale.forLanguageTag(simLocale); + mSystemLocales = LocaleList.forLanguageTags(systemLocales); + mAssetLocales = assetLocales; + when(mTelephonyManager.getSimLocale()).thenReturn(simLocale.isEmpty() ? null : mSimLocale); + when(mLocaleManager.getApplicationLocales(anyString())).thenReturn(mAppLocale); + } + + private class DummyAppLocaleDetailsHelper + extends AppLocaleDetails.AppLocaleDetailsHelper { + + DummyAppLocaleDetailsHelper(Context context, String packageName) { + super(context, packageName); + } + + @Override + String[] getAssetSystemLocales() { + return mAssetLocales; + } + + @Override + LocaleList getCurrentSystemLocales() { + return mSystemLocales; + } + } + +} diff --git a/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java new file mode 100644 index 00000000000..d7e3f923297 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appinfo/AppLocalePreferenceControllerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class AppLocalePreferenceControllerTest { + + private Context mContext; + private AppLocalePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + + mController = spy(new AppLocalePreferenceController(mContext, "test_key")); + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, true); + } + + @Test + public void getAvailabilityStatus_featureFlagOff_shouldReturnUnavailable() { + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, false); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_featureFlagOn_shouldReturnAvailable() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } +} diff --git a/tests/unit/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceControllerTest.java new file mode 100644 index 00000000000..648c7570304 --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/appinfo/ManageAppLocalePreferenceControllerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.appinfo; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.util.FeatureFlagUtils; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + + +import com.android.settings.core.BasePreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class ManageAppLocalePreferenceControllerTest { + private Context mContext; + private ManageAppLocalePreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + mController = spy(new ManageAppLocalePreferenceController(mContext, "a key")); + + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, true); + } + + @Test + public void getAvailabilityStatus_featureFlagOff_shouldReturnUnavailable() { + FeatureFlagUtils + .setEnabled(mContext, FeatureFlagUtils.SETTINGS_APP_LANGUAGE_SELECTION, false); + + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailabilityStatus_featureFlagOn_shouldReturnAvailable() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } +}