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 android.content.Intent.EXTRA_USER; 20 21 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI; 22 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY; 23 import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI; 24 25 import android.app.settings.SettingsEnums; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.IContentProvider; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.graphics.drawable.Drawable; 32 import android.graphics.drawable.Icon; 33 import android.os.Bundle; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 import android.util.ArrayMap; 38 import android.util.Log; 39 import android.util.Pair; 40 41 import androidx.annotation.VisibleForTesting; 42 import androidx.fragment.app.FragmentActivity; 43 import androidx.preference.Preference; 44 45 import com.android.settings.R; 46 import com.android.settings.SettingsActivity; 47 import com.android.settings.dashboard.profileselector.ProfileSelectDialog; 48 import com.android.settings.overlay.FeatureFactory; 49 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 50 import com.android.settingslib.drawer.DashboardCategory; 51 import com.android.settingslib.drawer.Tile; 52 import com.android.settingslib.drawer.TileUtils; 53 import com.android.settingslib.utils.ThreadUtils; 54 import com.android.settingslib.widget.AdaptiveIcon; 55 56 import java.util.List; 57 import java.util.Map; 58 59 /** 60 * Impl for {@code DashboardFeatureProvider}. 61 */ 62 public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { 63 64 private static final String TAG = "DashboardFeatureImpl"; 65 private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_"; 66 private static final String META_DATA_KEY_INTENT_ACTION = "com.android.settings.intent.action"; 67 68 protected final Context mContext; 69 70 private final MetricsFeatureProvider mMetricsFeatureProvider; 71 private final CategoryManager mCategoryManager; 72 private final PackageManager mPackageManager; 73 DashboardFeatureProviderImpl(Context context)74 public DashboardFeatureProviderImpl(Context context) { 75 mContext = context.getApplicationContext(); 76 mCategoryManager = CategoryManager.get(context); 77 mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 78 mPackageManager = context.getPackageManager(); 79 } 80 81 @Override getTilesForCategory(String key)82 public DashboardCategory getTilesForCategory(String key) { 83 return mCategoryManager.getTilesByCategory(mContext, key); 84 } 85 86 @Override getAllCategories()87 public List<DashboardCategory> getAllCategories() { 88 return mCategoryManager.getCategories(mContext); 89 } 90 91 @Override getDashboardKeyForTile(Tile tile)92 public String getDashboardKeyForTile(Tile tile) { 93 if (tile == null) { 94 return null; 95 } 96 if (tile.hasKey()) { 97 return tile.getKey(mContext); 98 } 99 final StringBuilder sb = new StringBuilder(DASHBOARD_TILE_PREF_KEY_PREFIX); 100 final ComponentName component = tile.getIntent().getComponent(); 101 sb.append(component.getClassName()); 102 return sb.toString(); 103 } 104 105 @Override bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder)106 public void bindPreferenceToTile(FragmentActivity activity, boolean forceRoundedIcon, 107 int sourceMetricsCategory, Preference pref, Tile tile, String key, int baseOrder) { 108 if (pref == null) { 109 return; 110 } 111 pref.setTitle(tile.getTitle(activity.getApplicationContext())); 112 if (!TextUtils.isEmpty(key)) { 113 pref.setKey(key); 114 } else { 115 pref.setKey(getDashboardKeyForTile(tile)); 116 } 117 bindSummary(pref, tile); 118 bindIcon(pref, tile, forceRoundedIcon); 119 final Bundle metadata = tile.getMetaData(); 120 String clsName = null; 121 String action = null; 122 123 if (metadata != null) { 124 clsName = metadata.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS); 125 action = metadata.getString(META_DATA_KEY_INTENT_ACTION); 126 } 127 if (!TextUtils.isEmpty(clsName)) { 128 pref.setFragment(clsName); 129 } else { 130 final Intent intent = new Intent(tile.getIntent()); 131 intent.putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 132 sourceMetricsCategory); 133 if (action != null) { 134 intent.setAction(action); 135 } 136 pref.setOnPreferenceClickListener(preference -> { 137 launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory); 138 return true; 139 }); 140 } 141 final String skipOffsetPackageName = activity.getPackageName(); 142 143 144 if (tile.hasOrder()) { 145 final int order = tile.getOrder(); 146 boolean shouldSkipBaseOrderOffset = TextUtils.equals( 147 skipOffsetPackageName, tile.getIntent().getComponent().getPackageName()); 148 if (shouldSkipBaseOrderOffset || baseOrder == Preference.DEFAULT_ORDER) { 149 pref.setOrder(order); 150 } else { 151 pref.setOrder(order + baseOrder); 152 } 153 } 154 } 155 156 @Override openTileIntent(FragmentActivity activity, Tile tile)157 public void openTileIntent(FragmentActivity activity, Tile tile) { 158 if (tile == null) { 159 Intent intent = new Intent(Settings.ACTION_SETTINGS).addFlags( 160 Intent.FLAG_ACTIVITY_CLEAR_TASK); 161 mContext.startActivity(intent); 162 return; 163 } 164 final Intent intent = new Intent(tile.getIntent()) 165 .putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 166 SettingsEnums.DASHBOARD_SUMMARY) 167 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 168 launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY); 169 } 170 bindSummary(Preference preference, Tile tile)171 private void bindSummary(Preference preference, Tile tile) { 172 final CharSequence summary = tile.getSummary(mContext); 173 if (summary != null) { 174 preference.setSummary(summary); 175 } else if (tile.getMetaData() != null 176 && tile.getMetaData().containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) { 177 // Set a placeholder summary before starting to fetch real summary, this is necessary 178 // to avoid preference height change. 179 preference.setSummary(R.string.summary_placeholder); 180 181 ThreadUtils.postOnBackgroundThread(() -> { 182 final Map<String, IContentProvider> providerMap = new ArrayMap<>(); 183 final String uri = tile.getMetaData().getString(META_DATA_PREFERENCE_SUMMARY_URI); 184 final String summaryFromUri = TileUtils.getTextFromUri( 185 mContext, uri, providerMap, META_DATA_PREFERENCE_SUMMARY); 186 ThreadUtils.postOnMainThread(() -> preference.setSummary(summaryFromUri)); 187 }); 188 } else { 189 preference.setSummary(R.string.summary_placeholder); 190 } 191 } 192 193 @VisibleForTesting bindIcon(Preference preference, Tile tile, boolean forceRoundedIcon)194 void bindIcon(Preference preference, Tile tile, boolean forceRoundedIcon) { 195 // Use preference context instead here when get icon from Tile, as we are using the context 196 // to get the style to tint the icon. Using mContext here won't get the correct style. 197 final Icon tileIcon = tile.getIcon(preference.getContext()); 198 if (tileIcon != null) { 199 Drawable iconDrawable = tileIcon.loadDrawable(preference.getContext()); 200 if (forceRoundedIcon 201 && !TextUtils.equals(mContext.getPackageName(), tile.getPackageName())) { 202 iconDrawable = new AdaptiveIcon(mContext, iconDrawable); 203 ((AdaptiveIcon) iconDrawable).setBackgroundColor(mContext, tile); 204 } 205 preference.setIcon(iconDrawable); 206 } else if (tile.getMetaData() != null 207 && tile.getMetaData().containsKey(META_DATA_PREFERENCE_ICON_URI)) { 208 ThreadUtils.postOnBackgroundThread(() -> { 209 final Intent intent = tile.getIntent(); 210 String packageName = null; 211 if (!TextUtils.isEmpty(intent.getPackage())) { 212 packageName = intent.getPackage(); 213 } else if (intent.getComponent() != null) { 214 packageName = intent.getComponent().getPackageName(); 215 } 216 final Map<String, IContentProvider> providerMap = new ArrayMap<>(); 217 final String uri = tile.getMetaData().getString(META_DATA_PREFERENCE_ICON_URI); 218 final Pair<String, Integer> iconInfo = TileUtils.getIconFromUri( 219 mContext, packageName, uri, providerMap); 220 if (iconInfo == null) { 221 Log.w(TAG, "Failed to get icon from uri " + uri); 222 return; 223 } 224 final Icon icon = Icon.createWithResource(iconInfo.first, iconInfo.second); 225 ThreadUtils.postOnMainThread(() -> 226 preference.setIcon(icon.loadDrawable(preference.getContext())) 227 ); 228 }); 229 } 230 } 231 launchIntentOrSelectProfile(FragmentActivity activity, Tile tile, Intent intent, int sourceMetricCategory)232 private void launchIntentOrSelectProfile(FragmentActivity activity, Tile tile, Intent intent, 233 int sourceMetricCategory) { 234 if (!isIntentResolvable(intent)) { 235 Log.w(TAG, "Cannot resolve intent, skipping. " + intent); 236 return; 237 } 238 ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile); 239 240 if (tile.userHandle == null || tile.isPrimaryProfileOnly()) { 241 mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory); 242 activity.startActivityForResult(intent, 0); 243 } else if (tile.userHandle.size() == 1) { 244 mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory); 245 activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0)); 246 } else { 247 final UserHandle userHandle = intent.getParcelableExtra(EXTRA_USER); 248 if (userHandle != null && tile.userHandle.contains(userHandle)) { 249 mMetricsFeatureProvider.logDashboardStartIntent( 250 mContext, intent, sourceMetricCategory); 251 activity.startActivityForResultAsUser(intent, 0, userHandle); 252 } else { 253 ProfileSelectDialog.show(activity.getSupportFragmentManager(), tile); 254 } 255 } 256 } 257 isIntentResolvable(Intent intent)258 private boolean isIntentResolvable(Intent intent) { 259 return mPackageManager.resolveActivity(intent, 0) != null; 260 } 261 } 262