Loading AndroidManifest.xml +3 −1 Original line number Diff line number Diff line Loading @@ -249,12 +249,14 @@ android:name=".Settings$MobileNetworkActivity" android:label="@string/network_settings_title" android:configChanges="orientation|screenSize|keyboardHidden" android:launchMode="singleInstance" android:launchMode="singleTop" android:exported="true"> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.android.settings.network.telephony.MobileNetworkSettings"/> <intent-filter android:priority="1"> <!-- Displays the MobileNetworkActivity and opt-in dialog for capability discovery. --> <!-- Please sync with a list created within MobileNetworkIntentConverter.java --> <action android:name="android.intent.action.MAIN" /> <action android:name="android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN" /> <action android:name="android.settings.NETWORK_OPERATOR_SETTINGS" /> <action android:name="android.settings.DATA_ROAMING_SETTINGS" /> Loading src/com/android/settings/Settings.java +23 −30 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.d(TAG, "Starting onNewIntent"); createUiFromIntent(null /* savedState */, convertIntent(intent)); } @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); } if (doesIntentContainOptInAction(intent)) { intent.putExtra(EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN, maybeShowContactDiscoveryDialog(subId)); } return 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); 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) { Loading src/com/android/settings/SettingsActivity.java +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading src/com/android/settings/network/MobileNetworkIntentConverter.java 0 → 100644 +288 −0 Original line number Diff line number Diff line /* * 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; } } tests/unit/src/com/android/settings/network/MobileNetworkIntentConverterTest.java 0 → 100644 +192 −0 Original line number Diff line number Diff line /* * 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 static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.provider.Settings; import android.telephony.ims.ImsRcsManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.Settings.MobileNetworkActivity; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.network.telephony.MobileNetworkSettings; import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class MobileNetworkIntentConverterTest { private static final String ACTIONS_ALLOWED [] = { Intent.ACTION_MAIN, Settings.ACTION_NETWORK_OPERATOR_SETTINGS, Settings.ACTION_DATA_ROAMING_SETTINGS, Settings.ACTION_MMS_MESSAGE_SETTING, ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN }; private static final int TEST_SUBSCRIPTION_ID = 3; private static final CharSequence TEST_TITLE_CHAR_SEQUENCE = "Test Title".subSequence(0, 10); @Mock private Activity mActivity; private MobileNetworkIntentConverter mIntentConverter; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = spy(ApplicationProvider.getApplicationContext()); ComponentName componentName = ComponentName.createRelative(Utils.SETTINGS_PACKAGE_NAME, MobileNetworkActivity.class.getTypeName()); doReturn(context).when(mActivity).getApplicationContext(); doReturn(componentName).when(mActivity).getComponentName(); mIntentConverter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return false; } }; } @Test public void converter_returnNull_whenNotInterested() { Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE); assertThat(mIntentConverter.apply(intent)).isEqualTo(null); } @Test public void converter_acceptableIntent_whenInterested() { Arrays.stream(ACTIONS_ALLOWED).forEach(action -> { Intent intent = new Intent(action); assertThat(mIntentConverter.apply(intent)).isNotEqualTo(null); }); } @Test public void convertSubscriptionId_fromIntentExtra_copyToBundleArgument() { Intent intent = new Intent(ACTIONS_ALLOWED[0]); intent.putExtra(Settings.EXTRA_SUB_ID, TEST_SUBSCRIPTION_ID); Intent convertedIntent = mIntentConverter.apply(intent); Bundle args = convertedIntent.getBundleExtra( SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(args.getInt(Settings.EXTRA_SUB_ID)).isEqualTo(TEST_SUBSCRIPTION_ID); int intExtra = convertedIntent.getIntExtra( Settings.EXTRA_SUB_ID, TEST_SUBSCRIPTION_ID - 1); assertThat(intExtra).isEqualTo(TEST_SUBSCRIPTION_ID); } @Test public void supportMms_addExtra_whenIntentForMms() { Intent intent = new Intent(Settings.ACTION_MMS_MESSAGE_SETTING); Intent convertedIntent = mIntentConverter.apply(intent); Bundle args = convertedIntent.getBundleExtra( SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(args).isNotEqualTo(null); assertThat(args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY)).isEqualTo( MobileNetworkActivity.EXTRA_MMS_MESSAGE); } @Test public void supportContacts_addExtra_whenIntentForContacts() { Intent intent = new Intent(ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN); MobileNetworkIntentConverter converter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return false; } @Override protected boolean mayShowContactDiscoveryDialog(Context context, int subId) { return true; } }; Intent convertedIntent = converter.apply(intent); Bundle args = convertedIntent.getBundleExtra( SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(args).isNotEqualTo(null); assertThat(args.getBoolean(MobileNetworkActivity.EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN)) .isEqualTo(true); } @Test public void convertFormat_forSettings_fragmentPresentation() { MobileNetworkIntentConverter converter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return false; } @Override protected CharSequence getFragmentTitle(Context context, int subId) { return TEST_TITLE_CHAR_SEQUENCE; } }; Intent intent = new Intent(ACTIONS_ALLOWED[0]); Intent convertedIntent = converter.apply(intent); assertThat(convertedIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE)) .isEqualTo(TEST_TITLE_CHAR_SEQUENCE.toString()); assertThat(convertedIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) .isEqualTo(MobileNetworkSettings.class.getTypeName()); } @Test public void convertFormat_deepLink_unwrapIntent() { MobileNetworkIntentConverter converter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return true; } }; Intent intent = new Intent(ACTIONS_ALLOWED[0]); String intentUri = intent.toUri(Intent.URI_INTENT_SCHEME); Intent deepLinkIntent = new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); deepLinkIntent.putExtra(Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, intentUri); assertThat(converter.apply(deepLinkIntent)).isNotEqualTo(null); } } Loading
AndroidManifest.xml +3 −1 Original line number Diff line number Diff line Loading @@ -249,12 +249,14 @@ android:name=".Settings$MobileNetworkActivity" android:label="@string/network_settings_title" android:configChanges="orientation|screenSize|keyboardHidden" android:launchMode="singleInstance" android:launchMode="singleTop" android:exported="true"> <meta-data android:name="com.android.settings.FRAGMENT_CLASS" android:value="com.android.settings.network.telephony.MobileNetworkSettings"/> <intent-filter android:priority="1"> <!-- Displays the MobileNetworkActivity and opt-in dialog for capability discovery. --> <!-- Please sync with a list created within MobileNetworkIntentConverter.java --> <action android:name="android.intent.action.MAIN" /> <action android:name="android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN" /> <action android:name="android.settings.NETWORK_OPERATOR_SETTINGS" /> <action android:name="android.settings.DATA_ROAMING_SETTINGS" /> Loading
src/com/android/settings/Settings.java +23 −30 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 protected void onNewIntent(Intent intent) { super.onNewIntent(intent); Log.d(TAG, "Starting onNewIntent"); createUiFromIntent(null /* savedState */, convertIntent(intent)); } @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); } if (doesIntentContainOptInAction(intent)) { intent.putExtra(EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN, maybeShowContactDiscoveryDialog(subId)); } return 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); 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) { Loading
src/com/android/settings/SettingsActivity.java +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
src/com/android/settings/network/MobileNetworkIntentConverter.java 0 → 100644 +288 −0 Original line number Diff line number Diff line /* * 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; } }
tests/unit/src/com/android/settings/network/MobileNetworkIntentConverterTest.java 0 → 100644 +192 −0 Original line number Diff line number Diff line /* * 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 static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.provider.Settings; import android.telephony.ims.ImsRcsManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.Settings.MobileNetworkActivity; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.network.telephony.MobileNetworkSettings; import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class MobileNetworkIntentConverterTest { private static final String ACTIONS_ALLOWED [] = { Intent.ACTION_MAIN, Settings.ACTION_NETWORK_OPERATOR_SETTINGS, Settings.ACTION_DATA_ROAMING_SETTINGS, Settings.ACTION_MMS_MESSAGE_SETTING, ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN }; private static final int TEST_SUBSCRIPTION_ID = 3; private static final CharSequence TEST_TITLE_CHAR_SEQUENCE = "Test Title".subSequence(0, 10); @Mock private Activity mActivity; private MobileNetworkIntentConverter mIntentConverter; @Before public void setUp() { MockitoAnnotations.initMocks(this); Context context = spy(ApplicationProvider.getApplicationContext()); ComponentName componentName = ComponentName.createRelative(Utils.SETTINGS_PACKAGE_NAME, MobileNetworkActivity.class.getTypeName()); doReturn(context).when(mActivity).getApplicationContext(); doReturn(componentName).when(mActivity).getComponentName(); mIntentConverter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return false; } }; } @Test public void converter_returnNull_whenNotInterested() { Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE); assertThat(mIntentConverter.apply(intent)).isEqualTo(null); } @Test public void converter_acceptableIntent_whenInterested() { Arrays.stream(ACTIONS_ALLOWED).forEach(action -> { Intent intent = new Intent(action); assertThat(mIntentConverter.apply(intent)).isNotEqualTo(null); }); } @Test public void convertSubscriptionId_fromIntentExtra_copyToBundleArgument() { Intent intent = new Intent(ACTIONS_ALLOWED[0]); intent.putExtra(Settings.EXTRA_SUB_ID, TEST_SUBSCRIPTION_ID); Intent convertedIntent = mIntentConverter.apply(intent); Bundle args = convertedIntent.getBundleExtra( SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(args.getInt(Settings.EXTRA_SUB_ID)).isEqualTo(TEST_SUBSCRIPTION_ID); int intExtra = convertedIntent.getIntExtra( Settings.EXTRA_SUB_ID, TEST_SUBSCRIPTION_ID - 1); assertThat(intExtra).isEqualTo(TEST_SUBSCRIPTION_ID); } @Test public void supportMms_addExtra_whenIntentForMms() { Intent intent = new Intent(Settings.ACTION_MMS_MESSAGE_SETTING); Intent convertedIntent = mIntentConverter.apply(intent); Bundle args = convertedIntent.getBundleExtra( SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(args).isNotEqualTo(null); assertThat(args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY)).isEqualTo( MobileNetworkActivity.EXTRA_MMS_MESSAGE); } @Test public void supportContacts_addExtra_whenIntentForContacts() { Intent intent = new Intent(ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN); MobileNetworkIntentConverter converter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return false; } @Override protected boolean mayShowContactDiscoveryDialog(Context context, int subId) { return true; } }; Intent convertedIntent = converter.apply(intent); Bundle args = convertedIntent.getBundleExtra( SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(args).isNotEqualTo(null); assertThat(args.getBoolean(MobileNetworkActivity.EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN)) .isEqualTo(true); } @Test public void convertFormat_forSettings_fragmentPresentation() { MobileNetworkIntentConverter converter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return false; } @Override protected CharSequence getFragmentTitle(Context context, int subId) { return TEST_TITLE_CHAR_SEQUENCE; } }; Intent intent = new Intent(ACTIONS_ALLOWED[0]); Intent convertedIntent = converter.apply(intent); assertThat(convertedIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE)) .isEqualTo(TEST_TITLE_CHAR_SEQUENCE.toString()); assertThat(convertedIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)) .isEqualTo(MobileNetworkSettings.class.getTypeName()); } @Test public void convertFormat_deepLink_unwrapIntent() { MobileNetworkIntentConverter converter = new MobileNetworkIntentConverter(mActivity) { @Override protected boolean isAttachedToExposedComponents() { return true; } }; Intent intent = new Intent(ACTIONS_ALLOWED[0]); String intentUri = intent.toUri(Intent.URI_INTENT_SCHEME); Intent deepLinkIntent = new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); deepLinkIntent.putExtra(Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, intentUri); assertThat(converter.apply(deepLinkIntent)).isNotEqualTo(null); } }