[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
This commit is contained in:
Bonian Chen
2022-05-30 19:06:10 +08:00
committed by My Name
parent c45731efdb
commit d10618d489
5 changed files with 509 additions and 31 deletions

View File

@@ -22,8 +22,6 @@ import android.content.ActivityNotFoundException;
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.internal.annotations.VisibleForTesting;
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 class Settings extends SettingsActivity {
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) {

View File

@@ -264,7 +264,10 @@ public class SettingsActivity extends SettingsBaseActivity
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);

View File

@@ -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;
}
}