• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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