blob: 0709992c07ac9bc410c237af8e8d974d90954aae [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;
Chris Wrenf6e9228b2016-01-26 18:04:35 -050039
Jason Monk5db8a412015-10-21 15:16:23 -070040import com.android.internal.logging.MetricsLogger;
Chris Wrenf6e9228b2016-01-26 18:04:35 -050041import com.android.internal.logging.MetricsProto.MetricsEvent;
Jason Monk94295132016-01-12 11:27:02 -050042import com.android.systemui.R;
Jason Monk5db8a412015-10-21 15:16:23 -070043import com.android.systemui.qs.QSTile;
Jason Monkbbadff82015-11-06 15:47:26 -050044import com.android.systemui.statusbar.phone.QSTileHost;
Jason Monk5db8a412015-10-21 15:16:23 -070045
46public class CustomTile extends QSTile<QSTile.State> {
47 public static final String PREFIX = "custom(";
48
Jason Monk8f7f3182015-11-18 16:35:14 -050049 private static final boolean DEBUG = false;
50
Jason Monkbbadff82015-11-06 15:47:26 -050051 // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
52 // So instead we have a period of waiting.
53 private static final long UNBIND_DELAY = 30000;
Jason Monk5db8a412015-10-21 15:16:23 -070054
Jason Monkbbadff82015-11-06 15:47:26 -050055 private final ComponentName mComponent;
56 private final Tile mTile;
Jason Monk8f7f3182015-11-18 16:35:14 -050057 private final IWindowManager mWindowManager;
58 private final IBinder mToken = new Binder();
Jason Monkd5a204f2015-12-21 08:50:01 -050059 private final IQSTileService mService;
60 private final TileServiceManager mServiceManager;
Jason Monkbbadff82015-11-06 15:47:26 -050061
Jason Monkbbadff82015-11-06 15:47:26 -050062 private boolean mListening;
63 private boolean mBound;
Jason Monk8f7f3182015-11-18 16:35:14 -050064 private boolean mIsTokenGranted;
65 private boolean mIsShowingDialog;
Jason Monkbbadff82015-11-06 15:47:26 -050066
67 private CustomTile(QSTileHost host, String action) {
Jason Monk5db8a412015-10-21 15:16:23 -070068 super(host);
Jason Monk8f7f3182015-11-18 16:35:14 -050069 mWindowManager = WindowManagerGlobal.getWindowManagerService();
Jason Monk5db8a412015-10-21 15:16:23 -070070 mComponent = ComponentName.unflattenFromString(action);
Jason Monkd5a204f2015-12-21 08:50:01 -050071 mServiceManager = host.getTileServices().getTileWrapper(this);
72 mService = mServiceManager.getTileService();
Jason Monkfe8f6822015-12-21 15:12:01 -050073 mTile = new Tile(mComponent);
Jason Monkbbadff82015-11-06 15:47:26 -050074 try {
75 PackageManager pm = mContext.getPackageManager();
76 ServiceInfo info = pm.getServiceInfo(mComponent, 0);
77 mTile.setIcon(android.graphics.drawable.Icon
78 .createWithResource(mComponent.getPackageName(), info.icon));
79 mTile.setLabel(info.loadLabel(pm));
80 } catch (Exception e) {
81 }
Jason Monkfe8f6822015-12-21 15:12:01 -050082 try {
83 mService.setQSTile(mTile);
84 } catch (RemoteException e) {
85 // Called through wrapper, won't happen here.
86 }
Jason Monk5db8a412015-10-21 15:16:23 -070087 }
88
Jason Monkbbadff82015-11-06 15:47:26 -050089 public ComponentName getComponent() {
90 return mComponent;
91 }
92
93 public Tile getQsTile() {
94 return mTile;
95 }
96
97 public void updateState(Tile tile) {
Jason Monkbbadff82015-11-06 15:47:26 -050098 mTile.setIcon(tile.getIcon());
99 mTile.setLabel(tile.getLabel());
100 mTile.setContentDescription(tile.getContentDescription());
Jason Monk94295132016-01-12 11:27:02 -0500101 mTile.setState(tile.getState());
Jason Monk5db8a412015-10-21 15:16:23 -0700102 }
103
Jason Monk8f7f3182015-11-18 16:35:14 -0500104 public void onDialogShown() {
105 mIsShowingDialog = true;
106 }
107
Jason Monk34a5cef2016-01-29 11:28:44 -0500108 public void onDialogHidden() {
109 mIsShowingDialog = false;
110 try {
111 if (DEBUG) Log.d(TAG, "Removing token");
112 mWindowManager.removeWindowToken(mToken);
113 } catch (RemoteException e) {
114 }
115 }
116
Jason Monk5db8a412015-10-21 15:16:23 -0700117 @Override
118 public void setListening(boolean listening) {
Jason Monkbbadff82015-11-06 15:47:26 -0500119 if (mListening == listening) return;
120 mListening = listening;
Jason Monkd5a204f2015-12-21 08:50:01 -0500121 try {
122 if (listening) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500123 if (mServiceManager.getType() == TileService.TILE_MODE_PASSIVE) {
124 mServiceManager.setBindRequested(true);
125 mService.onStartListening();
126 }
Jason Monkdc35dcb2015-12-04 16:36:15 -0500127 } else {
Jason Monkbbadff82015-11-06 15:47:26 -0500128 mService.onStopListening();
Jason Monkd5a204f2015-12-21 08:50:01 -0500129 if (mIsTokenGranted && !mIsShowingDialog) {
130 try {
131 if (DEBUG) Log.d(TAG, "Removing token");
132 mWindowManager.removeWindowToken(mToken);
133 } catch (RemoteException e) {
134 }
135 mIsTokenGranted = false;
Jason Monk8f7f3182015-11-18 16:35:14 -0500136 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500137 mIsShowingDialog = false;
138 mServiceManager.setBindRequested(false);
Jason Monk8f7f3182015-11-18 16:35:14 -0500139 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500140 } catch (RemoteException e) {
141 // Called through wrapper, won't happen here.
Jason Monkbbadff82015-11-06 15:47:26 -0500142 }
143 }
Jason Monk8f7f3182015-11-18 16:35:14 -0500144
Jason Monkbbadff82015-11-06 15:47:26 -0500145 @Override
146 protected void handleDestroy() {
147 super.handleDestroy();
Jason Monk8f7f3182015-11-18 16:35:14 -0500148 if (mIsTokenGranted) {
149 try {
150 if (DEBUG) Log.d(TAG, "Removing token");
151 mWindowManager.removeWindowToken(mToken);
152 } catch (RemoteException e) {
153 }
154 }
Jason Monk66c89c12016-01-06 08:51:26 -0500155 mHost.getTileServices().freeService(this, mServiceManager);
Jason Monk5db8a412015-10-21 15:16:23 -0700156 }
157
158 @Override
Jason Monk62b63a02016-02-02 15:15:31 -0500159 public State newTileState() {
Jason Monk5db8a412015-10-21 15:16:23 -0700160 return new State();
161 }
162
163 @Override
Jason Monk76c67aa2016-02-19 14:49:42 -0500164 public Intent getLongClickIntent() {
Jason Monk724214a2016-02-19 16:43:00 -0500165 Intent i = new Intent(TileService.ACTION_QS_TILE_PREFERENCES);
166 i.setPackage(mComponent.getPackageName());
167 i = resolveIntent(i);
168 if (i != null) {
169 return i;
170 }
Jason Monk76c67aa2016-02-19 14:49:42 -0500171 return new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
172 Uri.fromParts("package", mComponent.getPackageName(), null));
Jason Monk5db8a412015-10-21 15:16:23 -0700173 }
174
Jason Monk724214a2016-02-19 16:43:00 -0500175 private Intent resolveIntent(Intent i) {
176 ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
177 ActivityManager.getCurrentUser());
178 return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
179 .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
180 }
181
Jason Monk5db8a412015-10-21 15:16:23 -0700182 @Override
183 protected void handleClick() {
Jason Monk94295132016-01-12 11:27:02 -0500184 if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
185 return;
186 }
Jason Monkfe8f6822015-12-21 15:12:01 -0500187 try {
188 if (DEBUG) Log.d(TAG, "Adding token");
189 mWindowManager.addWindowToken(mToken, WindowManager.LayoutParams.TYPE_QS_DIALOG);
190 mIsTokenGranted = true;
191 } catch (RemoteException e) {
192 }
193 try {
194 if (mServiceManager.getType() == TileService.TILE_MODE_ACTIVE) {
195 mServiceManager.setBindRequested(true);
196 mService.onStartListening();
Jason Monk8f7f3182015-11-18 16:35:14 -0500197 }
Jason Monkfe8f6822015-12-21 15:12:01 -0500198 mService.onClick(mToken);
199 } catch (RemoteException e) {
200 // Called through wrapper, won't happen here.
Jason Monkbbadff82015-11-06 15:47:26 -0500201 }
Jason Monk5db8a412015-10-21 15:16:23 -0700202 MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
203 }
204
205 @Override
Jason Monk5db8a412015-10-21 15:16:23 -0700206 protected void handleUpdateState(State state, Object arg) {
Jason Monk068cb8b2015-12-02 11:30:36 -0500207 Drawable drawable = mTile.getIcon().loadDrawable(mContext);
Jason Monk94295132016-01-12 11:27:02 -0500208 int color = mContext.getColor(getColor(mTile.getState()));
209 drawable.setTint(color);
Jason Monk068cb8b2015-12-02 11:30:36 -0500210 state.icon = new DrawableIcon(drawable);
Jason Monkbbadff82015-11-06 15:47:26 -0500211 state.label = mTile.getLabel();
Jason Monk94295132016-01-12 11:27:02 -0500212 if (mTile.getState() == Tile.STATE_UNAVAILABLE) {
213 state.label = new SpannableStringBuilder().append(state.label,
214 new ForegroundColorSpan(color),
215 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE);
216 }
Jason Monkbbadff82015-11-06 15:47:26 -0500217 if (mTile.getContentDescription() != null) {
218 state.contentDescription = mTile.getContentDescription();
219 } else {
Jason Monk5db8a412015-10-21 15:16:23 -0700220 state.contentDescription = state.label;
Jason Monk5db8a412015-10-21 15:16:23 -0700221 }
222 }
223
224 @Override
225 public int getMetricsCategory() {
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500226 return MetricsEvent.QS_CUSTOM;
Jason Monk5db8a412015-10-21 15:16:23 -0700227 }
Jason Monkbbadff82015-11-06 15:47:26 -0500228
Jason Monk94295132016-01-12 11:27:02 -0500229 public void startUnlockAndRun() {
230 mHost.startRunnableDismissingKeyguard(new Runnable() {
231 @Override
232 public void run() {
233 try {
234 mService.onUnlockComplete();
235 } catch (RemoteException e) {
236 }
237 }
238 });
239 }
240
241 private static int getColor(int state) {
242 switch (state) {
243 case Tile.STATE_UNAVAILABLE:
244 return R.color.qs_tile_tint_unavailable;
245 case Tile.STATE_INACTIVE:
246 return R.color.qs_tile_tint_inactive;
247 case Tile.STATE_ACTIVE:
248 return R.color.qs_tile_tint_active;
249 }
250 return 0;
251 }
252
Jason Monk7e53f202016-01-28 10:40:20 -0500253 public static String toSpec(ComponentName name) {
254 return PREFIX + name.flattenToShortString() + ")";
255 }
256
Jason Monkbbadff82015-11-06 15:47:26 -0500257 public static ComponentName getComponentFromSpec(String spec) {
258 final String action = spec.substring(PREFIX.length(), spec.length() - 1);
259 if (action.isEmpty()) {
260 throw new IllegalArgumentException("Empty custom tile spec action");
261 }
262 return ComponentName.unflattenFromString(action);
263 }
264
265 public static QSTile<?> create(QSTileHost host, String spec) {
266 if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
267 throw new IllegalArgumentException("Bad custom tile spec: " + spec);
268 }
269 final String action = spec.substring(PREFIX.length(), spec.length() - 1);
270 if (action.isEmpty()) {
271 throw new IllegalArgumentException("Empty custom tile spec action");
272 }
273 return new CustomTile(host, action);
274 }
Jason Monk5db8a412015-10-21 15:16:23 -0700275}