1 /* 2 * Copyright (C) 2015 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.dashboard; 18 19 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI; 20 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY; 21 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI; 22 23 import android.app.Activity; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.IContentProvider; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.graphics.drawable.Icon; 30 import android.os.Bundle; 31 import android.provider.Settings; 32 import android.support.annotation.VisibleForTesting; 33 import android.support.v7.preference.Preference; 34 import android.text.TextUtils; 35 import android.util.ArrayMap; 36 import android.util.Log; 37 import android.util.Pair; 38 39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 40 import com.android.settings.R; 41 import com.android.settings.SettingsActivity; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 44 import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; 45 import com.android.settingslib.drawer.CategoryManager; 46 import com.android.settingslib.drawer.DashboardCategory; 47 import com.android.settingslib.drawer.ProfileSelectDialog; 48 import com.android.settingslib.drawer.Tile; 49 import com.android.settingslib.drawer.TileUtils; 50 import com.android.settingslib.utils.ThreadUtils; 51 52 import java.util.ArrayList; 53 import java.util.List; 54 import java.util.Map; 55 56 /** 57 * Impl for {@code DashboardFeatureProvider}. 58 */ 59 public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { 60 61 private static final String TAG = "DashboardFeatureImpl"; 62 63 private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_"; 64 private static final String META_DATA_KEY_INTENT_ACTION = "com.android.settings.intent.action"; 65 @VisibleForTesting 66 static final String META_DATA_KEY_ORDER = "com.android.settings.order"; 67 68 protected final Context mContext; 69 70 private final MetricsFeatureProvider mMetricsFeatureProvider; 71 private final CategoryManager mCategoryManager; 72 private final PackageManager mPackageManager; 73 74 public DashboardFeatureProviderImpl(Context context) { 75 mContext = context.getApplicationContext(); 76 mCategoryManager = CategoryManager.get(context, getExtraIntentAction()); 77 mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 78 mPackageManager = context.getPackageManager(); 79 } 80 81 @Override 82 public DashboardCategory getTilesForCategory(String key) { 83 return mCategoryManager.getTilesByCategory(mContext, key); 84 } 85 86 @Override 87 public List<Preference> getPreferencesForCategory(Activity activity, Context context, 88 int sourceMetricsCategory, String key) { 89 final DashboardCategory category = getTilesForCategory(key); 90 if (category == null) { 91 Log.d(TAG, "NO dashboard tiles for " + TAG); 92 return null; 93 } 94 final List<Tile> tiles = category.getTiles(); 95 if (tiles == null || tiles.isEmpty()) { 96 Log.d(TAG, "tile list is empty, skipping category " + category.title); 97 return null; 98 } 99 final List<Preference> preferences = new ArrayList<>(); 100 for (Tile tile : tiles) { 101 final Preference pref = new Preference(context); 102 bindPreferenceToTile(activity, sourceMetricsCategory, pref, tile, null /* key */, 103 Preference.DEFAULT_ORDER /* baseOrder */); 104 preferences.add(pref); 105 } 106 return preferences; 107 } 108 109 @Override 110 public List<DashboardCategory> getAllCategories() { 111 return mCategoryManager.getCategories(mContext); 112 } 113 114 @Override 115 public boolean shouldTintIcon() { 116 return mContext.getResources().getBoolean(R.bool.config_tintSettingIcon); 117 } 118 119 @Override 120 public String getDashboardKeyForTile(Tile tile) { 121 if (tile == null || tile.intent == null) { 122 return null; 123 } 124 if (!TextUtils.isEmpty(tile.key)) { 125 return tile.key; 126 } 127 final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX); 128 final ComponentName component = tile.intent.getComponent(); 129 sb.append(component.getClassName()); 130 return sb.toString(); 131 } 132 133 @Override 134 public void bindPreferenceToTile(Activity activity, int sourceMetricsCategory, Preference pref, 135 Tile tile, String key, int baseOrder) { 136 if (pref == null) { 137 return; 138 } 139 pref.setTitle(tile.title); 140 if (!TextUtils.isEmpty(key)) { 141 pref.setKey(key); 142 } else { 143 pref.setKey(getDashboardKeyForTile(tile)); 144 } 145 bindSummary(pref, tile); 146 bindIcon(pref, tile); 147 final Bundle metadata = tile.metaData; 148 String clsName = null; 149 String action = null; 150 Integer order = null; 151 if (metadata != null) { 152 clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS); 153 action = metadata.getString(META_DATA_KEY_INTENT_ACTION); 154 if (metadata.containsKey(META_DATA_KEY_ORDER) 155 && metadata.get(META_DATA_KEY_ORDER) instanceof Integer) { 156 order = metadata.getInt(META_DATA_KEY_ORDER); 157 } 158 } 159 if (!TextUtils.isEmpty(clsName)) { 160 pref.setFragment(clsName); 161 } else if (tile.intent != null) { 162 final Intent intent = new Intent(tile.intent); 163 intent.putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, 164 sourceMetricsCategory); 165 if (action != null) { 166 intent.setAction(action); 167 } 168 pref.setOnPreferenceClickListener(preference -> { 169 launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory); 170 return true; 171 }); 172 } 173 final String skipOffsetPackageName = activity.getPackageName(); 174 // If order is set in the meta data, use that order. Otherwise, check the intent priority. 175 if (order == null && tile.priority != 0) { 176 // Use negated priority for order, because tile priority is based on intent-filter 177 // (larger value has higher priority). However pref order defines smaller value has 178 // higher priority. 179 order = -tile.priority; 180 } 181 if (order != null) { 182 boolean shouldSkipBaseOrderOffset = false; 183 if (tile.intent != null) { 184 shouldSkipBaseOrderOffset = TextUtils.equals( 185 skipOffsetPackageName, tile.intent.getComponent().getPackageName()); 186 } 187 if (shouldSkipBaseOrderOffset || baseOrder == Preference.DEFAULT_ORDER) { 188 pref.setOrder(order); 189 } else { 190 pref.setOrder(order + baseOrder); 191 } 192 } 193 } 194 195 @Override 196 public String getExtraIntentAction() { 197 return null; 198 } 199 200 @Override 201 public void openTileIntent(Activity activity, Tile tile) { 202 if (tile == null) { 203 Intent intent = new Intent(Settings.ACTION_SETTINGS).addFlags( 204 Intent.FLAG_ACTIVITY_CLEAR_TASK); 205 mContext.startActivity(intent); 206 return; 207 } 208 209 if (tile.intent == null) { 210 return; 211 } 212 final Intent intent = new Intent(tile.intent) 213 .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, 214 MetricsEvent.DASHBOARD_SUMMARY) 215 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 216 launchIntentOrSelectProfile(activity, tile, intent, MetricsEvent.DASHBOARD_SUMMARY); 217 } 218 219 private void bindSummary(Preference preference, Tile tile) { 220 if (tile.summary != null) { 221 preference.setSummary(tile.summary); 222 } else if (tile.metaData != null 223 && tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) { 224 // Set a placeholder summary before starting to fetch real summary, this is necessary 225 // to avoid preference height change. 226 preference.setSummary(R.string.summary_placeholder); 227 228 ThreadUtils.postOnBackgroundThread(() -> { 229 final Map<String, IContentProvider> providerMap = new ArrayMap<>(); 230 final String uri = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI); 231 final String summary = TileUtils.getTextFromUri( 232 mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY); 233 ThreadUtils.postOnMainThread(() -> preference.setSummary(summary)); 234 }); 235 } else { 236 preference.setSummary(R.string.summary_placeholder); 237 } 238 } 239 240 @VisibleForTesting 241 void bindIcon(Preference preference, Tile tile) { 242 if (tile.icon != null) { 243 preference.setIcon(tile.icon.loadDrawable(preference.getContext())); 244 } else if (tile.metaData != null 245 && tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) { 246 ThreadUtils.postOnBackgroundThread(() -> { 247 String packageName = null; 248 if (tile.intent != null) { 249 Intent intent = tile.intent; 250 if (!TextUtils.isEmpty(intent.getPackage())) { 251 packageName = intent.getPackage(); 252 } else if (intent.getComponent() != null) { 253 packageName = intent.getComponent().getPackageName(); 254 } 255 } 256 final Map<String, IContentProvider> providerMap = new ArrayMap<>(); 257 final String uri = tile.metaData.getString(META_DATA_PREFERENCE_ICON_URI); 258 final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri( 259 mContext, packageName, uri, providerMap); 260 if (iconInfo == null) { 261 Log.w(TAG, "Failed to get icon from uri " + uri); 262 return; 263 } 264 final Icon icon = Icon.createWithResource(iconInfo.first, iconInfo.second); 265 ThreadUtils.postOnMainThread(() -> 266 preference.setIcon(icon.loadDrawable(preference.getContext())) 267 ); 268 }); 269 } 270 } 271 272 private void launchIntentOrSelectProfile(Activity activity, Tile tile, Intent intent, 273 int sourceMetricCategory) { 274 if (!isIntentResolvable(intent)) { 275 Log.w(TAG, "Cannot resolve intent, skipping. " + intent); 276 return; 277 } 278 ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile); 279 if (tile.userHandle == null) { 280 mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory); 281 activity.startActivityForResult(intent, 0); 282 } else if (tile.userHandle.size() == 1) { 283 mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory); 284 activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0)); 285 } else { 286 ProfileSelectDialog.show(activity.getFragmentManager(), tile); 287 } 288 } 289 290 private boolean isIntentResolvable(Intent intent) { 291 return mPackageManager.resolveActivity(intent, 0) != null; 292 } 293 } 294