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; |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 27 | import android.content.res.Resources.Theme; |
Andrew Sapperstein | 0114dc5 | 2016-06-11 12:20:50 -0700 | [diff] [blame] | 28 | import android.content.res.TypedArray; |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 29 | import android.net.Uri; |
Jason Monk | 64600cf | 2016-06-30 13:15:48 -0400 | [diff] [blame] | 30 | import android.provider.Settings.Global; |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 31 | import android.text.TextUtils; |
| 32 | import android.util.Log; |
| 33 | import android.util.TypedValue; |
| 34 | import android.view.Menu; |
| 35 | import android.view.MenuItem; |
| 36 | import android.view.MenuItem.OnMenuItemClickListener; |
Doris Ling | 319c806 | 2016-08-08 16:17:43 -0700 | [diff] [blame] | 37 | import com.android.internal.logging.MetricsLogger; |
Tamas Berghammer | cbd3f0c | 2016-06-22 15:21:38 +0100 | [diff] [blame^] | 38 | import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 39 | |
| 40 | import java.net.URISyntaxException; |
| 41 | import java.util.Locale; |
| 42 | |
| 43 | /** |
| 44 | * Functions to easily prepare contextual help menu option items with an intent that opens up the |
| 45 | * browser to a particular URL, while taking into account the preferred language and app version. |
| 46 | */ |
| 47 | public class HelpUtils { |
| 48 | private final static String TAG = HelpUtils.class.getSimpleName(); |
| 49 | |
| 50 | private 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_PRIMARY_COLOR = "EXTRA_PRIMARY_COLOR"; |
| 66 | private static final String EXTRA_BACKUP_URI = "EXTRA_BACKUP_URI"; |
| 67 | |
| 68 | /** |
| 69 | * Cached version code to prevent repeated calls to the package manager. |
| 70 | */ |
| 71 | private static String sCachedVersionCode = null; |
| 72 | |
| 73 | /** Static helper that is not instantiable*/ |
| 74 | private HelpUtils() { } |
| 75 | |
| 76 | public static boolean prepareHelpMenuItem(Activity activity, Menu menu, String helpUri, |
| 77 | String backupContext) { |
| 78 | MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_feedback_label); |
| 79 | return prepareHelpMenuItem(activity, helpItem, helpUri, backupContext); |
| 80 | } |
| 81 | |
| 82 | public static boolean prepareHelpMenuItem(Activity activity, Menu menu, int helpUriResource, |
| 83 | String backupContext) { |
| 84 | MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_feedback_label); |
| 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) { |
Doris Ling | 319c806 | 2016-08-08 16:17:43 -0700 | [diff] [blame] | 117 | MetricsLogger.action(activity, |
| 118 | MetricsEvent.ACTION_SETTING_HELP_AND_FEEDBACK, |
| 119 | intent.getStringExtra(EXTRA_CONTEXT)); |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 120 | try { |
| 121 | activity.startActivityForResult(intent, 0); |
| 122 | } catch (ActivityNotFoundException exc) { |
| 123 | Log.e(TAG, "No activity found for intent: " + intent); |
| 124 | } |
| 125 | return true; |
| 126 | } |
| 127 | }); |
| 128 | helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); |
| 129 | helpMenuItem.setVisible(true); |
| 130 | } else { |
| 131 | helpMenuItem.setVisible(false); |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | // return that the help menu item is visible (i.e., true) |
| 136 | return true; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | public static Intent getHelpIntent(Context context, String helpUriString, |
| 141 | String backupContext) { |
Jason Monk | 64600cf | 2016-06-30 13:15:48 -0400 | [diff] [blame] | 142 | if (Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) == 0) { |
| 143 | return null; |
| 144 | } |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 145 | // Try to handle as Intent Uri, otherwise just treat as Uri. |
| 146 | try { |
| 147 | Intent intent = Intent.parseUri(helpUriString, |
| 148 | Intent.URI_ANDROID_APP_SCHEME | Intent.URI_INTENT_SCHEME); |
Matthew Fritze | e1398ea | 2016-08-24 16:53:50 -0700 | [diff] [blame] | 149 | addIntentParameters(context, intent, backupContext, true /* sendPackageName */); |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 150 | ComponentName component = intent.resolveActivity(context.getPackageManager()); |
| 151 | if (component != null) { |
| 152 | return intent; |
| 153 | } else if (intent.hasExtra(EXTRA_BACKUP_URI)) { |
| 154 | // This extra contains a backup URI for when the intent isn't available. |
| 155 | return getHelpIntent(context, intent.getStringExtra(EXTRA_BACKUP_URI), |
| 156 | backupContext); |
| 157 | } else { |
| 158 | return null; |
| 159 | } |
| 160 | } catch (URISyntaxException e) { |
| 161 | } |
| 162 | // The help url string exists, so first add in some extra query parameters. |
| 163 | final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUriString)); |
| 164 | |
| 165 | // Then, create an intent that will be fired when the user |
| 166 | // selects this help menu item. |
| 167 | Intent intent = new Intent(Intent.ACTION_VIEW, fullUri); |
| 168 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| 169 | | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); |
| 170 | return intent; |
| 171 | } |
| 172 | |
Matthew Fritze | e1398ea | 2016-08-24 16:53:50 -0700 | [diff] [blame] | 173 | public static void addIntentParameters(Context context, Intent intent, String backupContext, |
| 174 | boolean sendPackageName) { |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 175 | if (!intent.hasExtra(EXTRA_CONTEXT)) { |
| 176 | // Insert some context if none exists. |
| 177 | intent.putExtra(EXTRA_CONTEXT, backupContext); |
| 178 | } |
Matthew Fritze | e1398ea | 2016-08-24 16:53:50 -0700 | [diff] [blame] | 179 | |
| 180 | Resources resources = context.getResources(); |
| 181 | boolean includePackageName = resources.getBoolean(R.bool.config_sendPackageName); |
| 182 | |
| 183 | if (sendPackageName && includePackageName) { |
| 184 | String[] packageNameKey = |
| 185 | {resources.getString(R.string.config_helpPackageNameKey)}; |
| 186 | String[] packageNameValue = |
| 187 | {resources.getString(R.string.config_helpPackageNameValue)}; |
| 188 | String intentExtraKey = |
| 189 | resources.getString(R.string.config_helpIntentExtraKey); |
| 190 | String intentNameKey = |
| 191 | resources.getString(R.string.config_helpIntentNameKey); |
| 192 | intent.putExtra(intentExtraKey, packageNameKey); |
| 193 | intent.putExtra(intentNameKey, packageNameValue); |
| 194 | } |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 195 | intent.putExtra(EXTRA_THEME, 1 /* Light, dark action bar */); |
Andrew Sapperstein | 0114dc5 | 2016-06-11 12:20:50 -0700 | [diff] [blame] | 196 | TypedArray array = context.obtainStyledAttributes(new int[]{android.R.attr.colorPrimary}); |
| 197 | intent.putExtra(EXTRA_PRIMARY_COLOR, array.getColor(0, 0)); |
| 198 | array.recycle(); |
Suprabh Shukla | c15c1be | 2016-04-04 15:04:09 -0700 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Adds two query parameters into the Uri, namely the language code and the version code |
| 203 | * of the app's package as gotten via the context. |
| 204 | * @return the uri with added query parameters |
| 205 | */ |
| 206 | public static Uri uriWithAddedParameters(Context context, Uri baseUri) { |
| 207 | Uri.Builder builder = baseUri.buildUpon(); |
| 208 | |
| 209 | // Add in the preferred language |
| 210 | builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString()); |
| 211 | |
| 212 | // Add in the package version code |
| 213 | if (sCachedVersionCode == null) { |
| 214 | // There is no cached version code, so try to get it from the package manager. |
| 215 | try { |
| 216 | // cache the version code |
| 217 | PackageInfo info = context.getPackageManager().getPackageInfo( |
| 218 | context.getPackageName(), 0); |
| 219 | sCachedVersionCode = Integer.toString(info.versionCode); |
| 220 | |
| 221 | // append the version code to the uri |
| 222 | builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); |
| 223 | } catch (NameNotFoundException e) { |
| 224 | // Cannot find the package name, so don't add in the version parameter |
| 225 | // This shouldn't happen. |
| 226 | Log.wtf(TAG, "Invalid package name for context", e); |
| 227 | } |
| 228 | } else { |
| 229 | builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); |
| 230 | } |
| 231 | |
| 232 | // Build the full uri and return it |
| 233 | return builder.build(); |
| 234 | } |
| 235 | } |