[Settings] Adjusted the API of Settings app
The API of Settings app get changed in order to support large screen.
This is a fix to adopt the change related to this work.
A short brief:
1. Accept ACTION_MAIN for launching MobileNetworkActivity.
2. Support deep-link intent while MobileNetworkActivity in foreground.
3. Avoid from binding MobileNetworkActivity as a single instance.
Bug: 230047450
Bug: 234406562
Bug: 229371407
Test: local & unittest
Change-Id: Ifcb9d4c564839199d998bd503f390f021c6bf3ad
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index ee0743a..c3ab8e2 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -22,8 +22,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsRcsManager;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
@@ -33,8 +31,7 @@
import com.android.settings.biometrics.face.FaceSettings;
import com.android.settings.core.FeatureFlags;
import com.android.settings.enterprise.EnterprisePrivacySettings;
-import com.android.settings.network.SubscriptionUtil;
-import com.android.settings.network.telephony.MobileNetworkUtils;
+import com.android.settings.network.MobileNetworkIntentConverter;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
import com.android.settings.security.SecuritySettingsFeatureProvider;
@@ -370,41 +367,37 @@
public static class PowerMenuSettingsActivity extends SettingsActivity {}
public static class MobileNetworkActivity extends SettingsActivity {
+ public static final String TAG = "MobileNetworkActivity";
public static final String EXTRA_MMS_MESSAGE = "mms_message";
public static final String EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN =
"show_capability_discovery_opt_in";
+ private MobileNetworkIntentConverter mIntentConverter;
+
+ /**
+ * Override of #onNewIntent() requires Activity to have "singleTop" launch mode within
+ * AndroidManifest.xml
+ */
@Override
- public Intent getIntent() {
- final Intent intent = new Intent(super.getIntent());
- int subId = intent.getIntExtra(android.provider.Settings.EXTRA_SUB_ID,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- SubscriptionInfo subInfo = SubscriptionUtil.getSubscriptionOrDefault(
- getApplicationContext(), subId);
- CharSequence title = SubscriptionUtil.getUniqueSubscriptionDisplayName(
- subInfo, getApplicationContext());
- intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
- intent.putExtra(android.provider.Settings.EXTRA_SUB_ID, subId);
- if (android.provider.Settings.ACTION_MMS_MESSAGE_SETTING.equals(intent.getAction())) {
- // highlight "mms_message" preference.
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, EXTRA_MMS_MESSAGE);
- }
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
- if (doesIntentContainOptInAction(intent)) {
- intent.putExtra(EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN,
- maybeShowContactDiscoveryDialog(subId));
- }
+ Log.d(TAG, "Starting onNewIntent");
- return intent;
+ createUiFromIntent(null /* savedState */, convertIntent(intent));
}
- 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.
- return MobileNetworkUtils.isContactDiscoveryVisible(getApplicationContext(), subId)
- // has the user already enabled this configuration?
- && !MobileNetworkUtils.isContactDiscoveryEnabled(
- getApplicationContext(), subId);
+ @Override
+ public Intent getIntent() {
+ return convertIntent(super.getIntent());
+ }
+
+ private Intent convertIntent(Intent copyFrom) {
+ if (mIntentConverter == null) {
+ mIntentConverter = new MobileNetworkIntentConverter(this);
+ }
+ Intent intent = mIntentConverter.apply(copyFrom);
+ return (intent == null) ? copyFrom : intent;
}
public static boolean doesIntentContainOptInAction(Intent intent) {
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 1a9bdc7..f951501 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -264,7 +264,10 @@
super.onCreate(savedState);
Log.d(LOG_TAG, "Starting onCreate");
+ createUiFromIntent(savedState, intent);
+ }
+ protected void createUiFromIntent(Bundle savedState, Intent intent) {
long startTime = System.currentTimeMillis();
final FeatureFactory factory = FeatureFactory.getFactory(this);
diff --git a/src/com/android/settings/network/MobileNetworkIntentConverter.java b/src/com/android/settings/network/MobileNetworkIntentConverter.java
new file mode 100644
index 0000000..648ac61
--- /dev/null
+++ b/src/com/android/settings/network/MobileNetworkIntentConverter.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2022 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.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsRcsManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.Settings.MobileNetworkActivity;
+import com.android.settings.SettingsActivity;
+import com.android.settings.network.telephony.MobileNetworkUtils;
+
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+/**
+ * A Java {@link Function} for conversion between {@link Intent} to Settings,
+ * and within Settings itself.
+ */
+public class MobileNetworkIntentConverter implements Function<Intent, Intent> {
+ private static final String TAG = "MobileNetworkIntentConverter";
+
+ private static final ComponentName sTargetComponent = ComponentName
+ .createRelative("com.android.settings",
+ MobileNetworkActivity.class.getTypeName());
+
+ /**
+ * These actions has better aligned with definitions within AndroidManifest.xml
+ */
+ private static final String [] sPotentialActions = new String [] {
+ null,
+ Intent.ACTION_MAIN,
+ android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS,
+ android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS,
+ android.provider.Settings.ACTION_MMS_MESSAGE_SETTING,
+ ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN
+ };
+
+ private static final String RE_ROUTE_TAG = ":reroute:" + TAG;
+ private static final AtomicReference<String> mCachedClassName =
+ new AtomicReference<String>();
+
+ private final Context mAppContext;
+ private final ComponentName mComponent;
+
+ /**
+ * Constructor
+ * @param activity which receiving {@link Intent}
+ */
+ public MobileNetworkIntentConverter(@NonNull Activity activity) {
+ mAppContext = activity.getApplicationContext();
+ mComponent = activity.getComponentName();
+ }
+
+ /**
+ * API defined by {@link Function}.
+ * @param fromIntent is the {@link Intent} for convert.
+ * @return {@link Intent} for sending internally within Settings.
+ * Return {@code null} when failure.
+ */
+ public Intent apply(Intent fromIntent) {
+ long startTime = SystemClock.elapsedRealtimeNanos();
+
+ Intent potentialReqIntent = null;
+ if (isAttachedToExposedComponents()) {
+ potentialReqIntent = convertFromDeepLink(fromIntent);
+ } else if (mayRequireConvert(fromIntent)) {
+ potentialReqIntent = fromIntent;
+ } else {
+ return null;
+ }
+
+ final Intent reqIntent = potentialReqIntent;
+ String action = reqIntent.getAction();
+
+ // Find out the subscription ID of request.
+ final int subId = extractSubscriptionId(reqIntent);
+
+ // Prepare the arguments Bundle.
+ Function<Intent, Intent> ops = Function.identity();
+
+ if (TextUtils.equals(action,
+ android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS)
+ || TextUtils.equals(action,
+ android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS)) {
+ // Accepted.
+ ops = ops.andThen(intent -> extractArguments(intent, subId))
+ .andThen(args -> rePackIntent(args, reqIntent))
+ .andThen(intent -> updateFragment(intent, mAppContext, subId));
+ } else if (TextUtils.equals(action,
+ android.provider.Settings.ACTION_MMS_MESSAGE_SETTING)) {
+ ops = ops.andThen(intent -> extractArguments(intent, subId))
+ .andThen(args -> convertMmsArguments(args))
+ .andThen(args -> rePackIntent(args, reqIntent))
+ .andThen(intent -> updateFragment(intent, mAppContext, subId));
+ } else if (TextUtils.equals(action,
+ ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN)) {
+ ops = ops.andThen(intent -> extractArguments(intent, subId))
+ .andThen(args -> supportContactDiscoveryDialog(args, mAppContext, subId))
+ .andThen(args -> rePackIntent(args, reqIntent))
+ .andThen(intent -> updateFragment(intent, mAppContext, subId));
+ } else if ((sTargetComponent.compareTo(mComponent) == 0)
+ && ((action == null) || Intent.ACTION_MAIN.equals(action))) {
+ Log.d(TAG, "Support default actions direct to this component");
+ ops = ops.andThen(intent -> extractArguments(intent, subId))
+ .andThen(args -> rePackIntent(args, reqIntent))
+ .andThen(intent -> replaceIntentAction(intent))
+ .andThen(intent -> updateFragment(intent, mAppContext, subId));
+ } else {
+ return null;
+ }
+
+ if (!isAttachedToExposedComponents()) {
+ ops = ops.andThen(intent -> configForReRoute(intent));
+ }
+
+ Intent result = ops.apply(reqIntent);
+ if (result != null) {
+ long endTime = SystemClock.elapsedRealtimeNanos();
+ Log.d(TAG, mComponent.toString() + " intent conversion: "
+ + (endTime - startTime) + " ns");
+ }
+ return result;
+ }
+
+ @VisibleForTesting
+ protected boolean isAttachedToExposedComponents() {
+ return (sTargetComponent.compareTo(mComponent) == 0);
+ }
+
+ protected int extractSubscriptionId(Intent reqIntent) {
+ return reqIntent.getIntExtra(android.provider.Settings.EXTRA_SUB_ID,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ protected Bundle extractArguments(Intent reqIntent, int subId) {
+ // Duplicate from SettingsActivity#getIntent()
+ Bundle args = reqIntent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ Bundle result = (args != null) ? new Bundle(args) : new Bundle();
+ result.putParcelable("intent", reqIntent);
+ result.putInt(android.provider.Settings.EXTRA_SUB_ID, subId);
+ return result;
+ }
+
+ protected Bundle convertMmsArguments(Bundle args) {
+ // highlight "mms_message" preference.
+ args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
+ MobileNetworkActivity.EXTRA_MMS_MESSAGE);
+ return args;
+ }
+
+ @VisibleForTesting
+ protected boolean mayShowContactDiscoveryDialog(Context context, 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.
+ return MobileNetworkUtils.isContactDiscoveryVisible(context, subId)
+ // has the user already enabled this configuration?
+ && !MobileNetworkUtils.isContactDiscoveryEnabled(context, subId);
+ }
+
+ protected Bundle supportContactDiscoveryDialog(Bundle args, Context context, int subId) {
+ boolean showDialog = mayShowContactDiscoveryDialog(context, subId);
+ Log.d(TAG, "maybeShowContactDiscoveryDialog subId=" + subId + ", show=" + showDialog);
+ args.putBoolean(MobileNetworkActivity.EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN,
+ showDialog);
+ return args;
+ }
+
+ protected Intent rePackIntent(Bundle args, Intent reqIntent) {
+ Intent intent = new Intent(reqIntent);
+ intent.setComponent(sTargetComponent);
+ intent.putExtra(android.provider.Settings.EXTRA_SUB_ID,
+ args.getInt(android.provider.Settings.EXTRA_SUB_ID));
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+ return intent;
+ }
+
+ protected Intent replaceIntentAction(Intent intent) {
+ intent.setAction(android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
+ return intent;
+ }
+
+ @VisibleForTesting
+ protected CharSequence getFragmentTitle(Context context, int subId) {
+ SubscriptionInfo subInfo = SubscriptionUtil.getSubscriptionOrDefault(context, subId);
+ return SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context);
+ }
+
+ protected Intent updateFragment(Intent intent, Context context, int subId) {
+ if (intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE) == null) {
+ CharSequence title = getFragmentTitle(context, subId);
+ if (title != null) {
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title.toString());
+ }
+ }
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, getFragmentClass(context));
+ return intent;
+ }
+
+ protected String getFragmentClass(Context context) {
+ String className = mCachedClassName.get();
+ if (className != null) {
+ return className;
+ }
+ try {
+ ActivityInfo ai = context.getPackageManager()
+ .getActivityInfo(sTargetComponent, PackageManager.GET_META_DATA);
+ if (ai != null && ai.metaData != null) {
+ className = ai.metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
+ if (className != null) {
+ mCachedClassName.set(className);
+ }
+ return className;
+ }
+ } catch (NameNotFoundException nnfe) {
+ // No recovery
+ Log.d(TAG, "Cannot get Metadata for: " + sTargetComponent.toString());
+ }
+ return null;
+ }
+
+ protected Intent configForReRoute(Intent intent) {
+ if (intent.hasExtra(RE_ROUTE_TAG)) {
+ Log.d(TAG, "Skip re-routed intent " + intent);
+ return null;
+ }
+ return intent.putExtra(RE_ROUTE_TAG, intent.getAction())
+ .setComponent(null);
+ }
+
+ protected static boolean mayRequireConvert(Intent intent) {
+ if (intent == null) {
+ return false;
+ }
+ final String action = intent.getAction();
+ return Arrays.stream(sPotentialActions).anyMatch(potentialAction ->
+ TextUtils.equals(action, potentialAction)
+ );
+ }
+
+ protected Intent convertFromDeepLink(Intent intent) {
+ if (intent == null) {
+ return null;
+ }
+ if (!TextUtils.equals(intent.getAction(),
+ android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)) {
+ return intent;
+ }
+ try {
+ return Intent.parseUri(intent.getStringExtra(
+ android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI),
+ Intent.URI_INTENT_SCHEME);
+ } catch (URISyntaxException exception) {
+ Log.d(TAG, "Intent URI corrupted", exception);
+ }
+ return null;
+ }
+}