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