blob: e737b54247cf1dce9827c22408a3a505591846b8 [file] [log] [blame]
Jason Monk5db8a412015-10-21 15:16:23 -07001/*
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
Jason Monkd5a204f2015-12-21 08:50:01 -050014 * limitations under the License
Jason Monk5db8a412015-10-21 15:16:23 -070015 */
Jason Monkd5a204f2015-12-21 08:50:01 -050016package com.android.systemui.qs.external;
Jason Monk5db8a412015-10-21 15:16:23 -070017
Jason Monk724214a2016-02-19 16:43:00 -050018import android.app.ActivityManager;
Jason Monk5db8a412015-10-21 15:16:23 -070019import android.content.ComponentName;
Jason Monk76c67aa2016-02-19 14:49:42 -050020import android.content.Intent;
Jason Monk5db8a412015-10-21 15:16:23 -070021import android.content.pm.PackageManager;
Jason Monk724214a2016-02-19 16:43:00 -050022import android.content.pm.ResolveInfo;
Jason Monk5db8a412015-10-21 15:16:23 -070023import android.content.pm.ServiceInfo;
Jason Monk068cb8b2015-12-02 11:30:36 -050024import android.graphics.drawable.Drawable;
Jason Monk76c67aa2016-02-19 14:49:42 -050025import android.net.Uri;
Jason Monk8f7f3182015-11-18 16:35:14 -050026import android.os.Binder;
Jason Monkbbadff82015-11-06 15:47:26 -050027import android.os.IBinder;
Jason Monk8f7f3182015-11-18 16:35:14 -050028import android.os.RemoteException;
Jason Monk76c67aa2016-02-19 14:49:42 -050029import android.provider.Settings;
Jason Monkbbadff82015-11-06 15:47:26 -050030import android.service.quicksettings.IQSTileService;
31import android.service.quicksettings.Tile;
Jason Monkfe8f6822015-12-21 15:12:01 -050032import android.service.quicksettings.TileService;
Jason Monk94295132016-01-12 11:27:02 -050033import android.text.SpannableStringBuilder;
34import android.text.style.ForegroundColorSpan;
Jason Monkbbadff82015-11-06 15:47:26 -050035import android.util.Log;
Jason Monk8f7f3182015-11-18 16:35:14 -050036import android.view.IWindowManager;
37import android.view.WindowManager;
38import android.view.WindowManagerGlobal;
Jason Monk5db8a412015-10-21 15:16:23 -070039import com.android.internal.logging.MetricsLogger;
Tamas Berghammercbd3f0c2016-06-22 15:21:38 +010040import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Jason Monk94295132016-01-12 11:27:02 -050041import com.android.systemui.R;
Jason Monk5db8a412015-10-21 15:16:23 -070042import com.android.systemui.qs.QSTile;
Jason Monk624cbe22016-05-02 10:42:17 -040043import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
Jason Monkbbadff82015-11-06 15:47:26 -050044import com.android.systemui.statusbar.phone.QSTileHost;
Jason Monk624cbe22016-05-02 10:42:17 -040045import libcore.util.Objects;
Jason Monk5db8a412015-10-21 15:16:23 -070046
Jason Monk624cbe22016-05-02 10:42:17 -040047public class CustomTile extends QSTile<QSTile.State> implements TileChangeListener {
Jason Monk5db8a412015-10-21 15:16:23 -070048 public static final String PREFIX = "custom(";
49
Jason Monk8f7f3182015-11-18 16:35:14 -050050 private static final boolean DEBUG = false;
51
Jason Monkbbadff82015-11-06 15:47:26 -050052 // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
53 // So instead we have a period of waiting.
54 private static final long UNBIND_DELAY = 30000;
Jason Monk5db8a412015-10-21 15:16:23 -070055
Jason Monkbbadff82015-11-06 15:47:26 -050056 private final ComponentName mComponent;
57 private final Tile mTile;
Jason Monk8f7f3182015-11-18 16:35:14 -050058 private final IWindowManager mWindowManager;
59 private final IBinder mToken = new Binder();
Jason Monkd5a204f2015-12-21 08:50:01 -050060 private final IQSTileService mService;
61 private final TileServiceManager mServiceManager;
Jason Monk1ffa11b2016-03-08 14:44:23 -050062 private final int mUser;
Jason Monk624cbe22016-05-02 10:42:17 -040063 private android.graphics.drawable.Icon mDefaultIcon;
Jason Monkbbadff82015-11-06 15:47:26 -050064
Jason Monkbbadff82015-11-06 15:47:26 -050065 private boolean mListening;
66 private boolean mBound;
Jason Monk8f7f3182015-11-18 16:35:14 -050067 private boolean mIsTokenGranted;
68 private boolean mIsShowingDialog;
Jason Monkbbadff82015-11-06 15:47:26 -050069
70 private CustomTile(QSTileHost host, String action) {
Jason Monk5db8a412015-10-21 15:16:23 -070071 super(host);
Jason Monk8f7f3182015-11-18 16:35:14 -050072 mWindowManager = WindowManagerGlobal.getWindowManagerService();
Jason Monk5db8a412015-10-21 15:16:23 -070073 mComponent = ComponentName.unflattenFromString(action);
Jason Monkee68fd82016-06-23 13:12:23 -040074 mTile = new Tile();
Jason Monka3453b8b2016-06-17 12:42:59 -040075 setTileIcon();
Jason Monkd5a204f2015-12-21 08:50:01 -050076 mServiceManager = host.getTileServices().getTileWrapper(this);
77 mService = mServiceManager.getTileService();
Jason Monk624cbe22016-05-02 10:42:17 -040078 mServiceManager.setTileChangeListener(this);
Jason Monk1ffa11b2016-03-08 14:44:23 -050079 mUser = ActivityManager.getCurrentUser();
Jason Monk5db8a412015-10-21 15:16:23 -070080 }
81
Jason Monk624cbe22016-05-02 10:42:17 -040082 private void setTileIcon() {
83 try {
84 PackageManager pm = mContext.getPackageManager();
Will Harmon294af232016-06-24 17:02:34 -070085 int flags = PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE;
86 if (isSystemApp(pm)) {
87 flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
88 }
89 ServiceInfo info = pm.getServiceInfo(mComponent, flags);
Jason Monka5f6ed32016-06-22 09:58:46 -040090 int icon = info.icon != 0 ? info.icon
91 : info.applicationInfo.icon;
Jason Monk624cbe22016-05-02 10:42:17 -040092 // Update the icon if its not set or is the default icon.
93 boolean updateIcon = mTile.getIcon() == null
94 || iconEquals(mTile.getIcon(), mDefaultIcon);
Jason Monka5f6ed32016-06-22 09:58:46 -040095 mDefaultIcon = icon != 0 ? android.graphics.drawable.Icon
96 .createWithResource(mComponent.getPackageName(), icon) : null;
Jason Monk624cbe22016-05-02 10:42:17 -040097 if (updateIcon) {
98 mTile.setIcon(mDefaultIcon);
99 }
100 // Update the label if there is no label.
101 if (mTile.getLabel() == null) {
102 mTile.setLabel(info.loadLabel(pm));
103 }
104 } catch (Exception e) {
105 mDefaultIcon = null;
106 }
107 }
108
Will Harmon294af232016-06-24 17:02:34 -0700109 private boolean isSystemApp(PackageManager pm) throws PackageManager.NameNotFoundException {
110 return pm.getApplicationInfo(mComponent.getPackageName(), 0).isSystemApp();
111 }
112
Jason Monk624cbe22016-05-02 10:42:17 -0400113 /**
114 * Compare two icons, only works for resources.
115 */
116 private boolean iconEquals(android.graphics.drawable.Icon icon1,
117 android.graphics.drawable.Icon icon2) {
118 if (icon1 == icon2) {
119 return true;
120 }
121 if (icon1 == null || icon2 == null) {
122 return false;
123 }
124 if (icon1.getType() != android.graphics.drawable.Icon.TYPE_RESOURCE
125 || icon2.getType() != android.graphics.drawable.Icon.TYPE_RESOURCE) {
126 return false;
127 }
128 if (icon1.getResId() != icon2.getResId()) {
129 return false;
130 }
131 if (!Objects.equal(icon1.getResPackage(), icon2.getResPackage())) {
132 return false;
133 }
134 return true;
135 }
136
137 @Override
138 public void onTileChanged(ComponentName tile) {
139 setTileIcon();
140 }
141
Jason Monk1c2fea82016-03-11 11:33:36 -0500142 @Override
143 public boolean isAvailable() {
Jason Monk4a906f92016-04-20 10:54:55 -0400144 return mDefaultIcon != null;
Jason Monk1c2fea82016-03-11 11:33:36 -0500145 }
146
Jason Monk1ffa11b2016-03-08 14:44:23 -0500147 public int getUser() {
148 return mUser;
149 }
150
Jason Monkbbadff82015-11-06 15:47:26 -0500151 public ComponentName getComponent() {
152 return mComponent;
153 }
154
155 public Tile getQsTile() {
156 return mTile;
157 }
158
159 public void updateState(Tile tile) {
Jason Monkbbadff82015-11-06 15:47:26 -0500160 mTile.setIcon(tile.getIcon());
161 mTile.setLabel(tile.getLabel());
162 mTile.setContentDescription(tile.getContentDescription());
Jason Monk94295132016-01-12 11:27:02 -0500163 mTile.setState(tile.getState());
Jason Monk5db8a412015-10-21 15:16:23 -0700164 }
165
Jason Monk8f7f3182015-11-18 16:35:14 -0500166 public void onDialogShown() {
167 mIsShowingDialog = true;
168 }
169
Jason Monk34a5cef2016-01-29 11:28:44 -0500170 public void onDialogHidden() {
171 mIsShowingDialog = false;
172 try {
173 if (DEBUG) Log.d(TAG, "Removing token");
174 mWindowManager.removeWindowToken(mToken);
175 } catch (RemoteException e) {
176 }
177 }
178
Jason Monk5db8a412015-10-21 15:16:23 -0700179 @Override
180 public void setListening(boolean listening) {
Jason Monkbbadff82015-11-06 15:47:26 -0500181 if (mListening == listening) return;
182 mListening = listening;
Jason Monkd5a204f2015-12-21 08:50:01 -0500183 try {
184 if (listening) {
Jason Monk624cbe22016-05-02 10:42:17 -0400185 setTileIcon();
186 refreshState();
Jason Monk97d22722016-04-07 11:41:47 -0400187 if (!mServiceManager.isActiveTile()) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500188 mServiceManager.setBindRequested(true);
189 mService.onStartListening();
190 }
Jason Monkdc35dcb2015-12-04 16:36:15 -0500191 } else {
Jason Monkbbadff82015-11-06 15:47:26 -0500192 mService.onStopListening();
Jason Monkd5a204f2015-12-21 08:50:01 -0500193 if (mIsTokenGranted && !mIsShowingDialog) {
194 try {
195 if (DEBUG) Log.d(TAG, "Removing token");
196 mWindowManager.removeWindowToken(mToken);
197 } catch (RemoteException e) {
198 }
199 mIsTokenGranted = false;
Jason Monk8f7f3182015-11-18 16:35:14 -0500200 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500201 mIsShowingDialog = false;
202 mServiceManager.setBindRequested(false);
Jason Monk8f7f3182015-11-18 16:35:14 -0500203 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500204 } catch (RemoteException e) {
205 // Called through wrapper, won't happen here.
Jason Monkbbadff82015-11-06 15:47:26 -0500206 }
207 }
Jason Monk8f7f3182015-11-18 16:35:14 -0500208
Jason Monkbbadff82015-11-06 15:47:26 -0500209 @Override
210 protected void handleDestroy() {
211 super.handleDestroy();
Jason Monk8f7f3182015-11-18 16:35:14 -0500212 if (mIsTokenGranted) {
213 try {
214 if (DEBUG) Log.d(TAG, "Removing token");
215 mWindowManager.removeWindowToken(mToken);
216 } catch (RemoteException e) {
217 }
218 }
Jason Monk66c89c12016-01-06 08:51:26 -0500219 mHost.getTileServices().freeService(this, mServiceManager);
Jason Monk5db8a412015-10-21 15:16:23 -0700220 }
221
222 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500223 public State newTileState() {
Yoshinori Hiranoeb093622016-08-17 14:09:58 +0900224 State state = new State();
225 state.autoMirrorDrawable = false;
226 return state;
Jason Monk5db8a412015-10-21 15:16:23 -0700227 }
228
229 @Override
Jason Monk76c67aa2016-02-19 14:49:42 -0500230 public Intent getLongClickIntent() {
Jason Monk724214a2016-02-19 16:43:00 -0500231 Intent i = new Intent(TileService.ACTION_QS_TILE_PREFERENCES);
232 i.setPackage(mComponent.getPackageName());
233 i = resolveIntent(i);
234 if (i != null) {
235 return i;
236 }
Jason Monk76c67aa2016-02-19 14:49:42 -0500237 return new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
238 Uri.fromParts("package", mComponent.getPackageName(), null));
Jason Monk5db8a412015-10-21 15:16:23 -0700239 }
240
Jason Monk724214a2016-02-19 16:43:00 -0500241 private Intent resolveIntent(Intent i) {
242 ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
243 ActivityManager.getCurrentUser());
244 return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
245 .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
246 }
247
Jason Monk5db8a412015-10-21 15:16:23 -0700248 @Override
249 protected void handleClick() {
Jason Monk94295132016-01-12 11:27:02 -0500250 if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
251 return;
252 }
Jason Monkfe8f6822015-12-21 15:12:01 -0500253 try {
254 if (DEBUG) Log.d(TAG, "Adding token");
255 mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
256 mIsTokenGranted = true;
257 } catch (RemoteException e) {
258 }
259 try {
Jason Monk97d22722016-04-07 11:41:47 -0400260 if (mServiceManager.isActiveTile()) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500261 mServiceManager.setBindRequested(true);
262 mService.onStartListening();
Jason Monk8f7f3182015-11-18 16:35:14 -0500263 }
Jason Monkfe8f6822015-12-21 15:12:01 -0500264 mService.onClick(mToken);
265 } catch (RemoteException e) {
266 // Called through wrapper, won't happen here.
Jason Monkbbadff82015-11-06 15:47:26 -0500267 }
Jason Monk5db8a412015-10-21 15:16:23 -0700268 MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
269 }
270
271 @Override
Jason Monk39c98e62016-03-16 09:18:35 -0400272 public CharSequence getTileLabel() {
273 return getState().label;
274 }
275
276 @Override
Jason Monk5db8a412015-10-21 15:16:23 -0700277 protected void handleUpdateState(State state, Object arg) {
Jason Monk1c2fea82016-03-11 11:33:36 -0500278 int tileState = mTile.getState();
279 if (mServiceManager.hasPendingBind()) {
280 tileState = Tile.STATE_UNAVAILABLE;
281 }
Jason Monk4a906f92016-04-20 10:54:55 -0400282 Drawable drawable;
283 try {
284 drawable = mTile.getIcon().loadDrawable(mContext);
285 } catch (Exception e) {
286 Log.w(TAG, "Invalid icon, forcing into unavailable state");
287 tileState = Tile.STATE_UNAVAILABLE;
288 drawable = mDefaultIcon.loadDrawable(mContext);
289 }
Jason Monk1c2fea82016-03-11 11:33:36 -0500290 int color = mContext.getColor(getColor(tileState));
Jason Monk94295132016-01-12 11:27:02 -0500291 drawable.setTint(color);
Jason Monk068cb8b2015-12-02 11:30:36 -0500292 state.icon = new DrawableIcon(drawable);
Jason Monkbbadff82015-11-06 15:47:26 -0500293 state.label = mTile.getLabel();
Jason Monk1c2fea82016-03-11 11:33:36 -0500294 if (tileState == Tile.STATE_UNAVAILABLE) {
Jason Monk94295132016-01-12 11:27:02 -0500295 state.label = new SpannableStringBuilder().append(state.label,
296 new ForegroundColorSpan(color),
297 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
298 }
Jason Monkbbadff82015-11-06 15:47:26 -0500299 if (mTile.getContentDescription() != null) {
300 state.contentDescription = mTile.getContentDescription();
301 } else {
Jason Monk5db8a412015-10-21 15:16:23 -0700302 state.contentDescription = state.label;
Jason Monk5db8a412015-10-21 15:16:23 -0700303 }
304 }
305
306 @Override
307 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500308 return MetricsEvent.QS_CUSTOM;
Jason Monk5db8a412015-10-21 15:16:23 -0700309 }
Jason Monkbbadff82015-11-06 15:47:26 -0500310
Jason Monk94295132016-01-12 11:27:02 -0500311 public void startUnlockAndRun() {
312 mHost.startRunnableDismissingKeyguard(new Runnable() {
313 @Override
314 public void run() {
315 try {
316 mService.onUnlockComplete();
317 } catch (RemoteException e) {
318 }
319 }
320 });
321 }
322
323 private static int getColor(int state) {
324 switch (state) {
325 case Tile.STATE_UNAVAILABLE:
326 return R.color.qs_tile_tint_unavailable;
327 case Tile.STATE_INACTIVE:
328 return R.color.qs_tile_tint_inactive;
329 case Tile.STATE_ACTIVE:
330 return R.color.qs_tile_tint_active;
331 }
332 return 0;
333 }
334
Jason Monk7e53f202016-01-28 10:40:20 -0500335 public static String toSpec(ComponentName name) {
336 return PREFIX + name.flattenToShortString() + ")";
337 }
338
Jason Monkbbadff82015-11-06 15:47:26 -0500339 public static ComponentName getComponentFromSpec(String spec) {
340 final String action = spec.substring(PREFIX.length(), spec.length() - 1);
341 if (action.isEmpty()) {
342 throw new IllegalArgumentException("Empty custom tile spec action");
343 }
344 return ComponentName.unflattenFromString(action);
345 }
346
347 public static QSTile<?> create(QSTileHost host, String spec) {
348 if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
349 throw new IllegalArgumentException("Bad custom tile spec: " + spec);
350 }
351 final String action = spec.substring(PREFIX.length(), spec.length() - 1);
352 if (action.isEmpty()) {
353 throw new IllegalArgumentException("Empty custom tile spec action");
354 }
355 return new CustomTile(host, action);
356 }
Jason Monk5db8a412015-10-21 15:16:23 -0700357}