• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.settingslib;
18 
19 import android.app.Activity;
20 import android.content.ActivityNotFoundException;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.content.res.Resources;
27 import android.net.Uri;
28 import android.provider.Settings.Global;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.view.Menu;
32 import android.view.MenuItem;
33 import android.view.MenuItem.OnMenuItemClickListener;
34 
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.settingslib.widget.R;
38 
39 import java.net.URISyntaxException;
40 import java.util.Locale;
41 
42 /**
43  * Functions to easily prepare contextual help menu option items with an intent that opens up the
44  * browser to a particular URL, while taking into account the preferred language and app version.
45  */
46 public class HelpUtils {
47     private final static String TAG = HelpUtils.class.getSimpleName();
48 
49     @VisibleForTesting
50     static final int MENU_HELP = Menu.FIRST + 100;
51 
52     /**
53      * Help URL query parameter key for the preferred language.
54      */
55     private final static String PARAM_LANGUAGE_CODE = "hl";
56 
57     /**
58      * Help URL query parameter key for the app version.
59      */
60     private final static String PARAM_VERSION = "version";
61 
62     // Constants for help intents.
63     private static final String EXTRA_CONTEXT = "EXTRA_CONTEXT";
64     private static final String EXTRA_THEME = "EXTRA_THEME";
65     private static final String EXTRA_BACKUP_URI = "EXTRA_BACKUP_URI";
66 
67     /**
68      * Cached version code to prevent repeated calls to the package manager.
69      */
70     private static String sCachedVersionCode = null;
71 
72     /** Static helper that is not instantiable */
HelpUtils()73     private HelpUtils() {
74     }
75 
prepareHelpMenuItem(Activity activity, Menu menu, String helpUri, String backupContext)76     public static boolean prepareHelpMenuItem(Activity activity, Menu menu, String helpUri,
77             String backupContext) {
78         // menu contains help item, skip it
79         if (menu.findItem(MENU_HELP) != null) {
80             return false;
81         }
82         MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_feedback_label);
83         helpItem.setIcon(R.drawable.ic_help_actionbar);
84         return prepareHelpMenuItem(activity, helpItem, helpUri, backupContext);
85     }
86 
prepareHelpMenuItem(Activity activity, Menu menu, int helpUriResource, String backupContext)87     public static boolean prepareHelpMenuItem(Activity activity, Menu menu, int helpUriResource,
88             String backupContext) {
89         // menu contains help item, skip it
90         if (menu.findItem(MENU_HELP) != null) {
91             return false;
92         }
93         MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_feedback_label);
94         helpItem.setIcon(R.drawable.ic_help_actionbar);
95         return prepareHelpMenuItem(activity, helpItem, activity.getString(helpUriResource),
96                 backupContext);
97     }
98 
99     /**
100      * Prepares the help menu item by doing the following.
101      * - If the helpUrlString is empty or null, the help menu item is made invisible.
102      * - Otherwise, this makes the help menu item visible and sets the intent for the help menu
103      * item to view the URL.
104      *
105      * @return returns whether the help menu item has been made visible.
106      */
107     @VisibleForTesting
prepareHelpMenuItem(final Activity activity, MenuItem helpMenuItem, String helpUriString, String backupContext)108     static boolean prepareHelpMenuItem(final Activity activity, MenuItem helpMenuItem,
109             String helpUriString, String backupContext) {
110         if (Global.getInt(activity.getContentResolver(), Global.DEVICE_PROVISIONED, 0) == 0) {
111             return false;
112         }
113         if (TextUtils.isEmpty(helpUriString)) {
114             // The help url string is empty or null, so set the help menu item to be invisible.
115             helpMenuItem.setVisible(false);
116 
117             // return that the help menu item is not visible (i.e. false)
118             return false;
119         } else {
120             final Intent intent = getHelpIntent(activity, helpUriString, backupContext);
121 
122             // Set the intent to the help menu item, show the help menu item in the overflow
123             // menu, and make it visible.
124             if (intent != null) {
125                 helpMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
126                     @Override
127                     public boolean onMenuItemClick(MenuItem item) {
128                         /**
129                          * TODO: Enable metrics logger for @SystemApi (b/111552654)
130                          *
131                          MetricsLogger.action(activity,
132                          MetricsEvent.ACTION_SETTING_HELP_AND_FEEDBACK,
133                          intent.getStringExtra(EXTRA_CONTEXT));
134                          */
135                         try {
136                             activity.startActivityForResult(intent, 0);
137                         } catch (ActivityNotFoundException exc) {
138                             Log.e(TAG, "No activity found for intent: " + intent);
139                         }
140                         return true;
141                     }
142                 });
143                 helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
144                 helpMenuItem.setVisible(true);
145             } else {
146                 helpMenuItem.setVisible(false);
147                 return false;
148             }
149 
150             // return that the help menu item is visible (i.e., true)
151             return true;
152         }
153     }
154 
getHelpIntent(Context context, String helpUriString, String backupContext)155     public static Intent getHelpIntent(Context context, String helpUriString,
156             String backupContext) {
157         if (Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) == 0) {
158             return null;
159         }
160         // Try to handle as Intent Uri, otherwise just treat as Uri.
161         try {
162             Intent intent = Intent.parseUri(helpUriString,
163                     Intent.URI_ANDROID_APP_SCHEME | Intent.URI_INTENT_SCHEME);
164             addIntentParameters(context, intent, backupContext, true /* sendPackageName */);
165             ComponentName component = intent.resolveActivity(context.getPackageManager());
166             if (component != null) {
167                 return intent;
168             } else if (intent.hasExtra(EXTRA_BACKUP_URI)) {
169                 // This extra contains a backup URI for when the intent isn't available.
170                 return getHelpIntent(context, intent.getStringExtra(EXTRA_BACKUP_URI),
171                         backupContext);
172             } else {
173                 return null;
174             }
175         } catch (URISyntaxException e) {
176         }
177         // The help url string exists, so first add in some extra query parameters.
178         final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUriString));
179 
180         // Then, create an intent that will be fired when the user
181         // selects this help menu item.
182         Intent intent = new Intent(Intent.ACTION_VIEW, fullUri);
183         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
184                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
185         return intent;
186     }
187 
addIntentParameters(Context context, Intent intent, String backupContext, boolean sendPackageName)188     public static void addIntentParameters(Context context, Intent intent, String backupContext,
189             boolean sendPackageName) {
190         if (!intent.hasExtra(EXTRA_CONTEXT)) {
191             // Insert some context if none exists.
192             intent.putExtra(EXTRA_CONTEXT, backupContext);
193         }
194 
195         Resources resources = context.getResources();
196         boolean includePackageName =
197                 resources.getBoolean(android.R.bool.config_sendPackageName);
198 
199         if (sendPackageName && includePackageName) {
200             String[] packageNameKey =
201                     {resources.getString(android.R.string.config_helpPackageNameKey)};
202             String[] packageNameValue =
203                     {resources.getString(android.R.string.config_helpPackageNameValue)};
204             String helpIntentExtraKey =
205                     resources.getString(android.R.string.config_helpIntentExtraKey);
206             String helpIntentNameKey =
207                     resources.getString(android.R.string.config_helpIntentNameKey);
208             String feedbackIntentExtraKey =
209                     resources.getString(android.R.string.config_feedbackIntentExtraKey);
210             String feedbackIntentNameKey =
211                     resources.getString(android.R.string.config_feedbackIntentNameKey);
212             intent.putExtra(helpIntentExtraKey, packageNameKey);
213             intent.putExtra(helpIntentNameKey, packageNameValue);
214             intent.putExtra(feedbackIntentExtraKey, packageNameKey);
215             intent.putExtra(feedbackIntentNameKey, packageNameValue);
216         }
217         intent.putExtra(EXTRA_THEME, 3 /* System Default theme */);
218     }
219 
220     /**
221      * Adds two query parameters into the Uri, namely the language code and the version code
222      * of the app's package as gotten via the context.
223      *
224      * @return the uri with added query parameters
225      */
uriWithAddedParameters(Context context, Uri baseUri)226     private static Uri uriWithAddedParameters(Context context, Uri baseUri) {
227         Uri.Builder builder = baseUri.buildUpon();
228 
229         // Add in the preferred language
230         builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString());
231 
232         // Add in the package version code
233         if (sCachedVersionCode == null) {
234             // There is no cached version code, so try to get it from the package manager.
235             try {
236                 // cache the version code
237                 PackageInfo info = context.getPackageManager().getPackageInfo(
238                         context.getPackageName(), 0);
239                 sCachedVersionCode = Long.toString(info.getLongVersionCode());
240 
241                 // append the version code to the uri
242                 builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode);
243             } catch (NameNotFoundException e) {
244                 // Cannot find the package name, so don't add in the version parameter
245                 // This shouldn't happen.
246                 Log.wtf(TAG, "Invalid package name for context", e);
247             }
248         } else {
249             builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode);
250         }
251 
252         // Build the full uri and return it
253         return builder.build();
254     }
255 }
256