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