• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.network;
18 
19 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
20 
21 import android.app.Activity;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.os.Bundle;
29 import android.os.SystemClock;
30 import android.telephony.SubscriptionInfo;
31 import android.telephony.SubscriptionManager;
32 import android.telephony.ims.ImsRcsManager;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.settings.Settings.MobileNetworkActivity;
40 import com.android.settings.SettingsActivity;
41 import com.android.settings.network.telephony.MobileNetworkUtils;
42 
43 import java.net.URISyntaxException;
44 import java.util.Arrays;
45 import java.util.concurrent.atomic.AtomicReference;
46 import java.util.function.Function;
47 
48 /**
49  * A Java {@link Function} for conversion between {@link Intent} to Settings,
50  * and within Settings itself.
51  */
52 public class MobileNetworkIntentConverter implements Function<Intent, Intent> {
53     private static final String TAG = "MobileNetworkIntentConverter";
54 
55     private static final ComponentName sTargetComponent = ComponentName
56             .createRelative(SETTINGS_PACKAGE_NAME,
57                     MobileNetworkActivity.class.getTypeName());
58     private static final String INTENT_TRAMPOLINE = "android.settings.SEARCH_RESULT_TRAMPOLINE";
59     /**
60      * These actions has better aligned with definitions within AndroidManifest.xml
61      */
62     private static final String[] sPotentialActions = new String[]{
63             null,
64             Intent.ACTION_MAIN,
65             android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS,
66             android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS,
67             android.provider.Settings.ACTION_MMS_MESSAGE_SETTING,
68             ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN,
69             INTENT_TRAMPOLINE
70     };
71 
72     private static final String RE_ROUTE_TAG = ":reroute:" + TAG;
73     private static final AtomicReference<String> mCachedClassName =
74             new AtomicReference<String>();
75 
76     private final Context mAppContext;
77     private final ComponentName mComponent;
78 
79     /**
80      * Constructor
81      * @param activity which receiving {@link Intent}
82      */
MobileNetworkIntentConverter(@onNull Activity activity)83     public MobileNetworkIntentConverter(@NonNull Activity activity) {
84         mAppContext = activity.getApplicationContext();
85         mComponent = activity.getComponentName();
86     }
87 
88     /**
89      * API defined by {@link Function}.
90      * @param fromIntent is the {@link Intent} for convert.
91      * @return {@link Intent} for sending internally within Settings.
92      *      Return {@code null} when failure.
93      */
apply(Intent fromIntent)94     public Intent apply(Intent fromIntent) {
95         long startTime = SystemClock.elapsedRealtimeNanos();
96 
97         Intent potentialReqIntent = null;
98         if (isAttachedToExposedComponents()) {
99             potentialReqIntent = convertFromDeepLink(fromIntent);
100         } else if (mayRequireConvert(fromIntent)) {
101             potentialReqIntent = fromIntent;
102         } else {
103             return null;
104         }
105 
106         final Intent reqIntent = potentialReqIntent;
107         String action = reqIntent.getAction();
108 
109         // Find out the subscription ID of request.
110         final int subId = extractSubscriptionId(reqIntent);
111 
112         // Prepare the arguments Bundle.
113         Function<Intent, Intent> ops = Function.identity();
114 
115         if (TextUtils.equals(action,
116                 android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS)
117                 || TextUtils.equals(action,
118                 android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS)
119                 || TextUtils.equals(action, INTENT_TRAMPOLINE)) {
120             // Accepted.
121             ops = ops.andThen(intent -> extractArguments(intent, subId))
122                     .andThen(args -> rePackIntent(args, reqIntent))
123                     .andThen(intent -> updateFragment(intent, mAppContext, subId));
124         } else if (TextUtils.equals(action,
125                 android.provider.Settings.ACTION_MMS_MESSAGE_SETTING)) {
126             ops = ops.andThen(intent -> extractArguments(intent, subId))
127                     .andThen(args -> convertMmsArguments(args))
128                     .andThen(args -> rePackIntent(args, reqIntent))
129                     .andThen(intent -> updateFragment(intent, mAppContext, subId));
130         } else if (TextUtils.equals(action,
131                 ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN)) {
132             ops = ops.andThen(intent -> extractArguments(intent, subId))
133                     .andThen(args -> supportContactDiscoveryDialog(args, mAppContext, subId))
134                     .andThen(args -> rePackIntent(args, reqIntent))
135                     .andThen(intent -> updateFragment(intent, mAppContext, subId));
136         } else if ((sTargetComponent.compareTo(mComponent) == 0)
137                 && ((action == null) || Intent.ACTION_MAIN.equals(action))) {
138             Log.d(TAG, "Support default actions direct to this component");
139             ops = ops.andThen(intent -> extractArguments(intent, subId))
140                     .andThen(args -> rePackIntent(args, reqIntent))
141                     .andThen(intent -> replaceIntentAction(intent))
142                     .andThen(intent -> updateFragment(intent, mAppContext, subId));
143         } else {
144             return null;
145         }
146 
147         if (!isAttachedToExposedComponents()) {
148             ops = ops.andThen(intent -> configForReRoute(intent));
149         }
150 
151         Intent result = ops.apply(reqIntent);
152         if (result != null) {
153             long endTime = SystemClock.elapsedRealtimeNanos();
154             Log.d(TAG, mComponent.toString() + " intent conversion: "
155                     + (endTime - startTime) + " ns");
156         }
157         return result;
158     }
159 
160     @VisibleForTesting
isAttachedToExposedComponents()161     protected boolean isAttachedToExposedComponents() {
162         return (sTargetComponent.compareTo(mComponent) == 0);
163     }
164 
extractSubscriptionId(Intent reqIntent)165     protected int extractSubscriptionId(Intent reqIntent) {
166         return reqIntent.getIntExtra(android.provider.Settings.EXTRA_SUB_ID,
167                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
168     }
169 
extractArguments(Intent reqIntent, int subId)170     protected Bundle extractArguments(Intent reqIntent, int subId) {
171         // Duplicate from SettingsActivity#getIntent()
172         Bundle args = reqIntent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
173         Bundle result = (args != null) ? new Bundle(args) : new Bundle();
174         result.putParcelable("intent", reqIntent);
175         result.putInt(android.provider.Settings.EXTRA_SUB_ID, subId);
176         return result;
177     }
178 
convertMmsArguments(Bundle args)179     protected Bundle convertMmsArguments(Bundle args) {
180         // highlight "mms_message" preference.
181         args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
182                 MobileNetworkActivity.EXTRA_MMS_MESSAGE);
183         return args;
184     }
185 
186     @VisibleForTesting
mayShowContactDiscoveryDialog(Context context, int subId)187     protected boolean mayShowContactDiscoveryDialog(Context context, int subId) {
188         // If this activity was launched using ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN, show the
189         // associated dialog only if the opt-in has not been granted yet.
190         return MobileNetworkUtils.isContactDiscoveryVisible(context, subId)
191                 // has the user already enabled this configuration?
192                 && !MobileNetworkUtils.isContactDiscoveryEnabled(context, subId);
193     }
194 
supportContactDiscoveryDialog(Bundle args, Context context, int subId)195     protected Bundle supportContactDiscoveryDialog(Bundle args, Context context, int subId) {
196         boolean showDialog = mayShowContactDiscoveryDialog(context, subId);
197         Log.d(TAG, "maybeShowContactDiscoveryDialog subId=" + subId + ", show=" + showDialog);
198         args.putBoolean(MobileNetworkActivity.EXTRA_SHOW_CAPABILITY_DISCOVERY_OPT_IN,
199                 showDialog);
200         return args;
201     }
202 
rePackIntent(Bundle args, Intent reqIntent)203     protected Intent rePackIntent(Bundle args, Intent reqIntent) {
204         Intent intent = new Intent(reqIntent);
205         intent.setComponent(sTargetComponent);
206         intent.putExtra(android.provider.Settings.EXTRA_SUB_ID,
207                 args.getInt(android.provider.Settings.EXTRA_SUB_ID));
208         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
209         return intent;
210     }
211 
replaceIntentAction(Intent intent)212     protected Intent replaceIntentAction(Intent intent) {
213         intent.setAction(android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
214         return intent;
215     }
216 
217     @VisibleForTesting
getFragmentTitle(Context context, int subId)218     protected CharSequence getFragmentTitle(Context context, int subId) {
219         SubscriptionInfo subInfo = SubscriptionUtil.getSubscriptionOrDefault(context, subId);
220         return SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context);
221     }
222 
updateFragment(Intent intent, Context context, int subId)223     protected Intent updateFragment(Intent intent, Context context, int subId) {
224         if (intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE) == null) {
225             CharSequence title = getFragmentTitle(context, subId);
226             if (title != null) {
227                 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title.toString());
228             }
229         }
230         intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, getFragmentClass(context));
231         return intent;
232     }
233 
getFragmentClass(Context context)234     protected String getFragmentClass(Context context) {
235         String className = mCachedClassName.get();
236         if (className != null) {
237             return className;
238         }
239         try {
240             ActivityInfo ai = context.getPackageManager()
241                     .getActivityInfo(sTargetComponent, PackageManager.GET_META_DATA);
242             if (ai != null && ai.metaData != null) {
243                 className = ai.metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
244                 if (className != null) {
245                     mCachedClassName.set(className);
246                 }
247                 return className;
248             }
249         } catch (NameNotFoundException nnfe) {
250             // No recovery
251             Log.d(TAG, "Cannot get Metadata for: " + sTargetComponent.toString());
252         }
253         return null;
254     }
255 
configForReRoute(Intent intent)256     protected Intent configForReRoute(Intent intent) {
257         if (intent.hasExtra(RE_ROUTE_TAG)) {
258             Log.d(TAG, "Skip re-routed intent " + intent);
259             return null;
260         }
261         return intent.putExtra(RE_ROUTE_TAG, intent.getAction())
262                 .setComponent(null);
263     }
264 
mayRequireConvert(Intent intent)265     protected static boolean mayRequireConvert(Intent intent) {
266         if (intent == null) {
267             return false;
268         }
269         final String action = intent.getAction();
270         return Arrays.stream(sPotentialActions).anyMatch(potentialAction ->
271                         TextUtils.equals(action, potentialAction)
272                 );
273     }
274 
convertFromDeepLink(Intent intent)275     protected Intent convertFromDeepLink(Intent intent) {
276         if (intent == null) {
277             return null;
278         }
279         if (!TextUtils.equals(intent.getAction(),
280                 android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)) {
281             return intent;
282         }
283         try {
284             return Intent.parseUri(intent.getStringExtra(
285                 android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI),
286                 Intent.URI_INTENT_SCHEME);
287         } catch (URISyntaxException exception) {
288             Log.d(TAG, "Intent URI corrupted", exception);
289         }
290         return null;
291     }
292 }
293