blob: bee1b3cb7df5e33f70c5b958972231db2bb37cd4 [file] [log] [blame]
Jason Monk744b6362015-11-03 18:24:29 -05001/**
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 *
Fan Zhang83dde972018-07-23 16:51:14 -07008 * http://www.apache.org/licenses/LICENSE-2.0
Jason Monk744b6362015-11-03 18:24:29 -05009 *
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
17package com.android.settingslib.drawer;
18
Fan Zhang4c07a712018-08-06 10:04:14 -070019import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_ORDER;
arangelov24eec2f2018-05-30 18:24:23 +010020import static com.android.settingslib.drawer.TileUtils.META_DATA_KEY_PROFILE;
Fan Zhang83dde972018-07-23 16:51:14 -070021import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON;
22import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_ICON_URI;
Fan Zhang4c07a712018-08-06 10:04:14 -070023import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_KEYHINT;
Fan Zhang4aa6da62018-08-20 14:13:59 -070024import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY;
25import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_SUMMARY_URI;
Fan Zhanga6afef82018-08-17 11:34:27 -070026import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITLE;
arangelov24eec2f2018-05-30 18:24:23 +010027import static com.android.settingslib.drawer.TileUtils.PROFILE_ALL;
28import static com.android.settingslib.drawer.TileUtils.PROFILE_PRIMARY;
29
Fan Zhang0e201232018-07-26 11:26:44 -070030import android.content.Context;
Jason Monk744b6362015-11-03 18:24:29 -050031import android.content.Intent;
Fan Zhang83dde972018-07-23 16:51:14 -070032import android.content.pm.ActivityInfo;
Fan Zhang0e201232018-07-26 11:26:44 -070033import android.content.pm.PackageManager;
34import android.content.pm.ResolveInfo;
Fan Zhanga6afef82018-08-17 11:34:27 -070035import android.content.res.Resources;
Jason Monk744b6362015-11-03 18:24:29 -050036import android.graphics.drawable.Icon;
37import android.os.Bundle;
38import android.os.Parcel;
39import android.os.Parcelable;
40import android.os.UserHandle;
41import android.text.TextUtils;
Fan Zhanga6afef82018-08-17 11:34:27 -070042import android.util.Log;
Jason Monk744b6362015-11-03 18:24:29 -050043
Fan Zhang8f942a42019-03-05 17:04:06 -080044import androidx.annotation.VisibleForTesting;
45
Jason Monk744b6362015-11-03 18:24:29 -050046import java.util.ArrayList;
Fan Zhang23baea92018-08-02 13:20:22 -070047import java.util.Comparator;
Fan Zhang0e201232018-07-26 11:26:44 -070048import java.util.List;
Fan Zhanga6afef82018-08-17 11:34:27 -070049import java.util.Objects;
Jason Monk744b6362015-11-03 18:24:29 -050050
51/**
52 * Description of a single dashboard tile that the user can select.
53 */
Jason Monkf509d7e2016-01-07 16:22:53 -050054public class Tile implements Parcelable {
Jason Monk744b6362015-11-03 18:24:29 -050055
Fan Zhang83dde972018-07-23 16:51:14 -070056 private static final String TAG = "Tile";
Fan Zhang83dde972018-07-23 16:51:14 -070057
Jason Monk744b6362015-11-03 18:24:29 -050058 /**
Jason Monk744b6362015-11-03 18:24:29 -050059 * Optional list of user handles which the intent should be launched on.
60 */
61 public ArrayList<UserHandle> userHandle = new ArrayList<>();
62
Fan Zhang8f942a42019-03-05 17:04:06 -080063 @VisibleForTesting
64 long mLastUpdateTime;
Fan Zhang0e201232018-07-26 11:26:44 -070065 private final String mActivityPackage;
66 private final String mActivityName;
Fan Zhanga79ab482018-08-07 13:04:20 -070067 private final Intent mIntent;
Fan Zhang83961e12018-08-21 18:06:24 -070068
Fan Zhang0e201232018-07-26 11:26:44 -070069 private ActivityInfo mActivityInfo;
Fan Zhang4aa6da62018-08-20 14:13:59 -070070 private CharSequence mSummaryOverride;
Fan Zhang83961e12018-08-21 18:06:24 -070071 private Bundle mMetaData;
Fan Zhanga79ab482018-08-07 13:04:20 -070072 private String mCategory;
Fan Zhang0e201232018-07-26 11:26:44 -070073
Fan Zhangf5c1d762018-08-03 09:14:32 -070074 public Tile(ActivityInfo activityInfo, String category) {
Fan Zhang83dde972018-07-23 16:51:14 -070075 mActivityInfo = activityInfo;
Fan Zhang0e201232018-07-26 11:26:44 -070076 mActivityPackage = mActivityInfo.packageName;
77 mActivityName = mActivityInfo.name;
Fan Zhangf5c1d762018-08-03 09:14:32 -070078 mMetaData = activityInfo.metaData;
79 mCategory = category;
Fan Zhanga79ab482018-08-07 13:04:20 -070080 mIntent = new Intent().setClassName(mActivityPackage, mActivityName);
81 }
82
83 Tile(Parcel in) {
84 mActivityPackage = in.readString();
85 mActivityName = in.readString();
86 mIntent = new Intent().setClassName(mActivityPackage, mActivityName);
Fan Zhanga79ab482018-08-07 13:04:20 -070087 final int N = in.readInt();
88 for (int i = 0; i < N; i++) {
89 userHandle.add(UserHandle.CREATOR.createFromParcel(in));
90 }
91 mCategory = in.readString();
Fan Zhanga79ab482018-08-07 13:04:20 -070092 mMetaData = in.readBundle();
Jason Monk744b6362015-11-03 18:24:29 -050093 }
94
95 @Override
96 public int describeContents() {
97 return 0;
98 }
99
100 @Override
101 public void writeToParcel(Parcel dest, int flags) {
Fan Zhang0e201232018-07-26 11:26:44 -0700102 dest.writeString(mActivityPackage);
103 dest.writeString(mActivityName);
Jason Monk744b6362015-11-03 18:24:29 -0500104 final int N = userHandle.size();
105 dest.writeInt(N);
106 for (int i = 0; i < N; i++) {
107 userHandle.get(i).writeToParcel(dest, flags);
108 }
Fan Zhangf5c1d762018-08-03 09:14:32 -0700109 dest.writeString(mCategory);
Fan Zhangf5c1d762018-08-03 09:14:32 -0700110 dest.writeBundle(mMetaData);
Fan Zhangf5c1d762018-08-03 09:14:32 -0700111 }
112
Fan Zhanga6afef82018-08-17 11:34:27 -0700113 public int getId() {
114 return Objects.hash(mActivityPackage, mActivityName);
115 }
116
117 public String getDescription() {
118 return mActivityPackage + "/" + mActivityName;
119 }
120
Fan Zhanga79ab482018-08-07 13:04:20 -0700121 public String getPackageName() {
122 return mActivityPackage;
123 }
124
125 /**
126 * Intent to launch when the preference is selected.
127 */
128 public Intent getIntent() {
129 return mIntent;
130 }
131
Fan Zhangf5c1d762018-08-03 09:14:32 -0700132 /**
133 * Category in which the tile should be placed.
134 */
135 public String getCategory() {
136 return mCategory;
137 }
138
139 public void setCategory(String newCategoryKey) {
140 mCategory = newCategoryKey;
141 }
142
143 /**
Fan Zhang4c07a712018-08-06 10:04:14 -0700144 * Priority of this tile, used for display ordering.
Fan Zhangf5c1d762018-08-03 09:14:32 -0700145 */
Fan Zhang4c07a712018-08-06 10:04:14 -0700146 public int getOrder() {
147 if (hasOrder()) {
148 return mMetaData.getInt(META_DATA_KEY_ORDER);
149 } else {
150 return 0;
151 }
152 }
153
154 public boolean hasOrder() {
155 return mMetaData.containsKey(META_DATA_KEY_ORDER)
156 && mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
Fan Zhangf5c1d762018-08-03 09:14:32 -0700157 }
158
Fan Zhanga6afef82018-08-17 11:34:27 -0700159 /**
160 * Title of the tile that is shown to the user.
161 */
162 public CharSequence getTitle(Context context) {
163 CharSequence title = null;
Fan Zhang8f942a42019-03-05 17:04:06 -0800164 ensureMetadataNotStale(context);
Fan Zhanga6afef82018-08-17 11:34:27 -0700165 final PackageManager packageManager = context.getPackageManager();
166 if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
167 if (mMetaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
168 try {
169 final Resources res =
170 packageManager.getResourcesForApplication(mActivityPackage);
171 title = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_TITLE));
172 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
173 Log.d(TAG, "Couldn't find info", e);
174 }
175 } else {
176 title = mMetaData.getString(META_DATA_PREFERENCE_TITLE);
177 }
178 }
179 // Set the preference title to the activity's label if no
180 // meta-data is found
181 if (title == null) {
182 title = getActivityInfo(context).loadLabel(packageManager);
183 }
184 return title;
185 }
186
Fan Zhang4aa6da62018-08-20 14:13:59 -0700187 /**
188 * Returns the raw metadata for summary, this is used for comparing 2 summary text without
189 * loading the real string.
190 */
191 public String getSummaryReference() {
192 if (mSummaryOverride != null) {
193 return mSummaryOverride.toString();
194 }
195 if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
196 return mMetaData.get(META_DATA_PREFERENCE_SUMMARY).toString();
197 }
198 return null;
199 }
200
201 /**
202 * Overrides the summary. This can happen when injected tile wants to provide dynamic summary.
203 */
204 public void overrideSummary(CharSequence summaryOverride) {
205 mSummaryOverride = summaryOverride;
206 }
207
208 /**
209 * Optional summary describing what this tile controls.
210 */
211 public CharSequence getSummary(Context context) {
212 if (mSummaryOverride != null) {
213 return mSummaryOverride;
214 }
Fan Zhang8f942a42019-03-05 17:04:06 -0800215 ensureMetadataNotStale(context);
Fan Zhang4aa6da62018-08-20 14:13:59 -0700216 CharSequence summary = null;
217 final PackageManager packageManager = context.getPackageManager();
218 if (mMetaData != null) {
219 if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
220 return null;
221 }
222 if (mMetaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
223 if (mMetaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
224 try {
225 final Resources res =
226 packageManager.getResourcesForApplication(mActivityPackage);
227 summary = res.getString(mMetaData.getInt(META_DATA_PREFERENCE_SUMMARY));
228 } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
229 Log.d(TAG, "Couldn't find info", e);
230 }
231 } else {
232 summary = mMetaData.getString(META_DATA_PREFERENCE_SUMMARY);
233 }
234 }
235 }
236 return summary;
237 }
238
Fan Zhang83961e12018-08-21 18:06:24 -0700239 public void setMetaData(Bundle metaData) {
240 mMetaData = metaData;
241 }
242
243 /**
244 * The metaData from the activity that defines this tile.
245 */
Fan Zhangf5c1d762018-08-03 09:14:32 -0700246 public Bundle getMetaData() {
247 return mMetaData;
Jason Monk744b6362015-11-03 18:24:29 -0500248 }
249
Fan Zhang83dde972018-07-23 16:51:14 -0700250 /**
Fan Zhang4c07a712018-08-06 10:04:14 -0700251 * Optional key to use for this tile.
252 */
253 public String getKey(Context context) {
254 if (!hasKey()) {
255 return null;
256 }
Fan Zhang8f942a42019-03-05 17:04:06 -0800257 ensureMetadataNotStale(context);
Fan Zhang4c07a712018-08-06 10:04:14 -0700258 if (mMetaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
259 return context.getResources().getString(mMetaData.getInt(META_DATA_PREFERENCE_KEYHINT));
260 } else {
261 return mMetaData.getString(META_DATA_PREFERENCE_KEYHINT);
262 }
263 }
264
265 public boolean hasKey() {
266 return mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_KEYHINT);
267 }
268
269 /**
Fan Zhang83dde972018-07-23 16:51:14 -0700270 * Optional icon to show for this tile.
271 *
272 * @attr ref android.R.styleable#PreferenceHeader_icon
273 */
Fan Zhang0e201232018-07-26 11:26:44 -0700274 public Icon getIcon(Context context) {
Fan Zhangf5c1d762018-08-03 09:14:32 -0700275 if (context == null || mMetaData == null) {
Fan Zhang83dde972018-07-23 16:51:14 -0700276 return null;
277 }
Fan Zhang8f942a42019-03-05 17:04:06 -0800278 ensureMetadataNotStale(context);
Fan Zhangf5c1d762018-08-03 09:14:32 -0700279 int iconResId = mMetaData.getInt(META_DATA_PREFERENCE_ICON);
Fan Zhang83dde972018-07-23 16:51:14 -0700280 // Set the icon
281 if (iconResId == 0) {
282 // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
283 // ICON_URI should be loaded in app UI when need the icon object. Handling IPC at this
284 // level is too complex because we don't have a strong threading contract for this class
Fan Zhangf5c1d762018-08-03 09:14:32 -0700285 if (!mMetaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
Fan Zhang0e201232018-07-26 11:26:44 -0700286 iconResId = getActivityInfo(context).icon;
Fan Zhang83dde972018-07-23 16:51:14 -0700287 }
288 }
289 if (iconResId != 0) {
Fan Zhang0e201232018-07-26 11:26:44 -0700290 return Icon.createWithResource(getActivityInfo(context).packageName, iconResId);
Fan Zhang83dde972018-07-23 16:51:14 -0700291 } else {
292 return null;
293 }
294 }
295
Fan Zhangf5c1d762018-08-03 09:14:32 -0700296 /**
297 * Whether the icon can be tinted. This is true when icon needs to be monochrome (single-color)
298 */
299 public boolean isIconTintable(Context context) {
300 if (mMetaData != null
301 && mMetaData.containsKey(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE)) {
302 return mMetaData.getBoolean(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE);
303 }
Fan Zhang8f942a42019-03-05 17:04:06 -0800304 ensureMetadataNotStale(context);
Fan Zhangf5c1d762018-08-03 09:14:32 -0700305 final String pkgName = context.getPackageName();
306 // If this drawable is coming from outside Settings, tint it to match the color.
307 final ActivityInfo activityInfo = getActivityInfo(context);
308 return activityInfo != null
309 && !TextUtils.equals(pkgName, activityInfo.packageName);
310 }
311
Fan Zhang8f942a42019-03-05 17:04:06 -0800312 /**
313 * Ensures metadata is not stale for this tile.
314 */
315 private void ensureMetadataNotStale(Context context) {
316 final PackageManager pm = context.getApplicationContext().getPackageManager();
317
318 try {
319 final long lastUpdateTime = pm.getPackageInfo(mActivityPackage,
320 PackageManager.GET_META_DATA).lastUpdateTime;
321 if (lastUpdateTime == mLastUpdateTime) {
322 // All good. Do nothing
323 return;
324 }
325 // App has been updated since we load metadata last time. Reload metadata.
326 mActivityInfo = null;
327 getActivityInfo(context);
328 mLastUpdateTime = lastUpdateTime;
329 } catch (PackageManager.NameNotFoundException e) {
330 Log.d(TAG, "Can't find package, probably uninstalled.");
331 }
332 }
333
Fan Zhang0e201232018-07-26 11:26:44 -0700334 private ActivityInfo getActivityInfo(Context context) {
335 if (mActivityInfo == null) {
336 final PackageManager pm = context.getApplicationContext().getPackageManager();
337 final Intent intent = new Intent().setClassName(mActivityPackage, mActivityName);
338 final List<ResolveInfo> infoList =
339 pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
340 if (infoList != null && !infoList.isEmpty()) {
341 mActivityInfo = infoList.get(0).activityInfo;
Fan Zhang8f942a42019-03-05 17:04:06 -0800342 mMetaData = mActivityInfo.metaData;
Fan Zhang0e201232018-07-26 11:26:44 -0700343 }
344 }
345 return mActivityInfo;
Jason Monk744b6362015-11-03 18:24:29 -0500346 }
347
Jason Monkf509d7e2016-01-07 16:22:53 -0500348 public static final Creator<Tile> CREATOR = new Creator<Tile>() {
349 public Tile createFromParcel(Parcel source) {
350 return new Tile(source);
Jason Monk744b6362015-11-03 18:24:29 -0500351 }
Fan Zhang83dde972018-07-23 16:51:14 -0700352
Jason Monkf509d7e2016-01-07 16:22:53 -0500353 public Tile[] newArray(int size) {
354 return new Tile[size];
Jason Monk744b6362015-11-03 18:24:29 -0500355 }
356 };
arangelov24eec2f2018-05-30 18:24:23 +0100357
358 public boolean isPrimaryProfileOnly() {
Fan Zhangf5c1d762018-08-03 09:14:32 -0700359 String profile = mMetaData != null ?
360 mMetaData.getString(META_DATA_KEY_PROFILE) : PROFILE_ALL;
arangelov24eec2f2018-05-30 18:24:23 +0100361 profile = (profile != null ? profile : PROFILE_ALL);
362 return TextUtils.equals(profile, PROFILE_PRIMARY);
363 }
Fan Zhang23baea92018-08-02 13:20:22 -0700364
365 public static final Comparator<Tile> TILE_COMPARATOR =
Fan Zhang4c07a712018-08-06 10:04:14 -0700366 (lhs, rhs) -> rhs.getOrder() - lhs.getOrder();
Jason Monk744b6362015-11-03 18:24:29 -0500367}