blob: 0b4e6485551c389b7d64cd01e34b88fe0174e742 [file] [log] [blame]
Jason Monkd5a204f2015-12-21 08:50:01 -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 *
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 */
16package com.android.systemui.qs.external;
17
18import android.app.ActivityManager;
Jason Monk07f55a22016-04-11 15:30:38 -040019import android.content.BroadcastReceiver;
Jason Monkd5a204f2015-12-21 08:50:01 -050020import android.content.ComponentName;
Jason Monk07f55a22016-04-11 15:30:38 -040021import android.content.Context;
Jason Monkd5a204f2015-12-21 08:50:01 -050022import android.content.Intent;
Jason Monk07f55a22016-04-11 15:30:38 -040023import android.content.IntentFilter;
Will Harmon604c2f92016-06-06 12:54:59 -070024import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
Jason Monk07f55a22016-04-11 15:30:38 -040026import android.net.Uri;
Jason Monkd5a204f2015-12-21 08:50:01 -050027import android.os.Handler;
Jason Monkee68fd82016-06-23 13:12:23 -040028import android.os.IBinder;
Jason Monkd5a204f2015-12-21 08:50:01 -050029import android.os.UserHandle;
30import android.service.quicksettings.IQSTileService;
Jason Monka3453b8b2016-06-17 12:42:59 -040031import android.service.quicksettings.Tile;
Will Harmon604c2f92016-06-06 12:54:59 -070032import android.service.quicksettings.TileService;
Jason Monkd5a204f2015-12-21 08:50:01 -050033import android.util.Log;
Will Harmon604c2f92016-06-06 12:54:59 -070034
Gus Prevasab336792018-11-14 13:52:20 -050035import androidx.annotation.VisibleForTesting;
36
Jason Monk624cbe22016-05-02 10:42:17 -040037import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
Will Harmon604c2f92016-06-06 12:54:59 -070038
39import java.util.List;
Narayan Kamath607223f2018-02-19 14:09:02 +000040import java.util.Objects;
Jason Monkd5a204f2015-12-21 08:50:01 -050041
42/**
43 * Manages the priority which lets {@link TileServices} make decisions about which tiles
44 * to bind. Also holds on to and manages the {@link TileLifecycleManager}, informing it
45 * of when it is allowed to bind based on decisions frome the {@link TileServices}.
46 */
47public class TileServiceManager {
48
49 private static final long MIN_BIND_TIME = 5000;
50 private static final long UNBIND_DELAY = 30000;
51
52 public static final boolean DEBUG = true;
53
54 private static final String TAG = "TileServiceManager";
55
Jason Monkfe8f6822015-12-21 15:12:01 -050056 @VisibleForTesting
57 static final String PREFS_FILE = "CustomTileModes";
58
Jason Monkd5a204f2015-12-21 08:50:01 -050059 private final TileServices mServices;
60 private final TileLifecycleManager mStateManager;
61 private final Handler mHandler;
62 private boolean mBindRequested;
63 private boolean mBindAllowed;
64 private boolean mBound;
65 private int mPriority;
66 private boolean mJustBound;
67 private long mLastUpdate;
Jason Monk34a5cef2016-01-29 11:28:44 -050068 private boolean mShowingDialog;
Jason Monk1c2fea82016-03-11 11:33:36 -050069 // Whether we have a pending bind going out to the service without a response yet.
70 // This defaults to true to ensure tiles start out unavailable.
71 private boolean mPendingBind = true;
Fabian Kozynskie6395dd2019-05-02 13:16:02 -040072 private boolean mStarted = false;
Jason Monkd5a204f2015-12-21 08:50:01 -050073
Jason Monka3453b8b2016-06-17 12:42:59 -040074 TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
75 Tile tile) {
Jason Monkd5a204f2015-12-21 08:50:01 -050076 this(tileServices, handler, new TileLifecycleManager(handler,
Jason Monka3453b8b2016-06-17 12:42:59 -040077 tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
Jason Monkd5a204f2015-12-21 08:50:01 -050078 new UserHandle(ActivityManager.getCurrentUser())));
79 }
80
81 @VisibleForTesting
82 TileServiceManager(TileServices tileServices, Handler handler,
83 TileLifecycleManager tileLifecycleManager) {
84 mServices = tileServices;
85 mHandler = handler;
86 mStateManager = tileLifecycleManager;
Jason Monk07f55a22016-04-11 15:30:38 -040087
88 IntentFilter filter = new IntentFilter();
89 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
90 filter.addDataScheme("package");
Jason Monkbaade752016-08-25 15:57:14 -040091 Context context = mServices.getContext();
92 context.registerReceiverAsUser(mUninstallReceiver,
Jason Monk07f55a22016-04-11 15:30:38 -040093 new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
Fabian Kozynskie6395dd2019-05-02 13:16:02 -040094 }
95
96 boolean isLifecycleStarted() {
97 return mStarted;
98 }
99
100 /**
101 * Starts the TileLifecycleManager by adding the corresponding component as a Tile and
102 * binding to it if needed.
103 *
104 * This method should be called after constructing a TileServiceManager to guarantee that the
105 * TileLifecycleManager has added the tile and bound to it at least once.
106 */
107 void startLifecycleManagerAndAddTile() {
108 mStarted = true;
109 ComponentName component = mStateManager.getComponent();
110 Context context = mServices.getContext();
Jason Monkbaade752016-08-25 15:57:14 -0400111 if (!TileLifecycleManager.isTileAdded(context, component)) {
112 TileLifecycleManager.setTileAdded(context, component, true);
113 mStateManager.onTileAdded();
Jason Monk34f6cbc2016-08-30 16:33:48 -0400114 mStateManager.flushMessagesAndUnbind();
Jason Monkbaade752016-08-25 15:57:14 -0400115 }
Jason Monkfe8f6822015-12-21 15:12:01 -0500116 }
117
Jason Monk624cbe22016-05-02 10:42:17 -0400118 public void setTileChangeListener(TileChangeListener changeListener) {
119 mStateManager.setTileChangeListener(changeListener);
120 }
121
Jason Monk97d22722016-04-07 11:41:47 -0400122 public boolean isActiveTile() {
123 return mStateManager.isActiveTile();
Jason Monkd5a204f2015-12-21 08:50:01 -0500124 }
125
Fabian Kozynski05843f02019-06-28 13:19:57 -0400126 public boolean isBooleanTile() {
127 return mStateManager.isBooleanTile();
128 }
129
Jason Monk34a5cef2016-01-29 11:28:44 -0500130 public void setShowingDialog(boolean dialog) {
131 mShowingDialog = dialog;
132 }
133
Jason Monkd5a204f2015-12-21 08:50:01 -0500134 public IQSTileService getTileService() {
135 return mStateManager;
136 }
137
Jason Monkee68fd82016-06-23 13:12:23 -0400138 public IBinder getToken() {
139 return mStateManager.getToken();
140 }
141
Jason Monkd5a204f2015-12-21 08:50:01 -0500142 public void setBindRequested(boolean bindRequested) {
143 if (mBindRequested == bindRequested) return;
144 mBindRequested = bindRequested;
145 if (mBindAllowed && mBindRequested && !mBound) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500146 mHandler.removeCallbacks(mUnbind);
Jason Monkd5a204f2015-12-21 08:50:01 -0500147 bindService();
148 } else {
149 mServices.recalculateBindAllowance();
150 }
151 if (mBound && !mBindRequested) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500152 mHandler.postDelayed(mUnbind, UNBIND_DELAY);
Jason Monkd5a204f2015-12-21 08:50:01 -0500153 }
154 }
155
156 public void setLastUpdate(long lastUpdate) {
157 mLastUpdate = lastUpdate;
Jason Monk97d22722016-04-07 11:41:47 -0400158 if (mBound && isActiveTile()) {
Jason Monkfe8f6822015-12-21 15:12:01 -0500159 mStateManager.onStopListening();
160 setBindRequested(false);
161 }
Jason Monkd5a204f2015-12-21 08:50:01 -0500162 mServices.recalculateBindAllowance();
163 }
164
165 public void handleDestroy() {
Jason Monk88529052016-11-04 13:29:58 -0400166 setBindAllowed(false);
Jason Monk07f55a22016-04-11 15:30:38 -0400167 mServices.getContext().unregisterReceiver(mUninstallReceiver);
Jason Monkd5a204f2015-12-21 08:50:01 -0500168 mStateManager.handleDestroy();
169 }
170
171 public void setBindAllowed(boolean allowed) {
172 if (mBindAllowed == allowed) return;
173 mBindAllowed = allowed;
174 if (!mBindAllowed && mBound) {
175 unbindService();
176 } else if (mBindAllowed && mBindRequested && !mBound) {
177 bindService();
178 }
179 }
180
Jason Monk1c2fea82016-03-11 11:33:36 -0500181 public boolean hasPendingBind() {
182 return mPendingBind;
183 }
184
185 public void clearPendingBind() {
186 mPendingBind = false;
187 }
188
Jason Monkd5a204f2015-12-21 08:50:01 -0500189 private void bindService() {
190 if (mBound) {
191 Log.e(TAG, "Service already bound");
192 return;
193 }
Jason Monk1c2fea82016-03-11 11:33:36 -0500194 mPendingBind = true;
Jason Monkd5a204f2015-12-21 08:50:01 -0500195 mBound = true;
196 mJustBound = true;
197 mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
198 mStateManager.setBindService(true);
199 }
200
201 private void unbindService() {
202 if (!mBound) {
203 Log.e(TAG, "Service not bound");
204 return;
205 }
206 mBound = false;
207 mJustBound = false;
208 mStateManager.setBindService(false);
209 }
210
211 public void calculateBindPriority(long currentTime) {
212 if (mStateManager.hasPendingClick()) {
213 // Pending click is the most important thing, need to put this service at the top of
214 // the list to be bound.
215 mPriority = Integer.MAX_VALUE;
Jason Monk34a5cef2016-01-29 11:28:44 -0500216 } else if (mShowingDialog) {
217 // Hang on to services that are showing dialogs so they don't die.
218 mPriority = Integer.MAX_VALUE - 1;
Jason Monkd5a204f2015-12-21 08:50:01 -0500219 } else if (mJustBound) {
220 // If we just bound, lets not thrash on binding/unbinding too much, this is second most
221 // important.
Jason Monk34a5cef2016-01-29 11:28:44 -0500222 mPriority = Integer.MAX_VALUE - 2;
Jason Monkd5a204f2015-12-21 08:50:01 -0500223 } else if (!mBindRequested) {
224 // Don't care about binding right now, put us last.
225 mPriority = Integer.MIN_VALUE;
226 } else {
227 // Order based on whether this was just updated.
228 long timeSinceUpdate = currentTime - mLastUpdate;
229 // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and
230 // MAX_VALUE - 1 for the more important states above.
Jason Monk34a5cef2016-01-29 11:28:44 -0500231 if (timeSinceUpdate > Integer.MAX_VALUE - 3) {
232 mPriority = Integer.MAX_VALUE - 3;
Jason Monkd5a204f2015-12-21 08:50:01 -0500233 } else {
234 mPriority = (int) timeSinceUpdate;
235 }
236 }
237 }
238
239 public int getBindPriority() {
240 return mPriority;
241 }
242
Jason Monkfe8f6822015-12-21 15:12:01 -0500243 private final Runnable mUnbind = new Runnable() {
244 @Override
245 public void run() {
246 if (mBound && !mBindRequested) {
247 unbindService();
248 }
249 }
250 };
251
Jason Monkd5a204f2015-12-21 08:50:01 -0500252 @VisibleForTesting
253 final Runnable mJustBoundOver = new Runnable() {
254 @Override
255 public void run() {
256 mJustBound = false;
257 mServices.recalculateBindAllowance();
258 }
259 };
Jason Monk07f55a22016-04-11 15:30:38 -0400260
261 private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
262 @Override
263 public void onReceive(Context context, Intent intent) {
264 if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
265 return;
266 }
Will Harmon604c2f92016-06-06 12:54:59 -0700267
Jason Monk07f55a22016-04-11 15:30:38 -0400268 Uri data = intent.getData();
269 String pkgName = data.getEncodedSchemeSpecificPart();
270 final ComponentName component = mStateManager.getComponent();
Narayan Kamath607223f2018-02-19 14:09:02 +0000271 if (!Objects.equals(pkgName, component.getPackageName())) {
Jason Monk07f55a22016-04-11 15:30:38 -0400272 return;
273 }
Will Harmon604c2f92016-06-06 12:54:59 -0700274
275 // If the package is being updated, verify the component still exists.
276 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
277 Intent queryIntent = new Intent(TileService.ACTION_QS_TILE);
278 queryIntent.setPackage(pkgName);
279 PackageManager pm = context.getPackageManager();
280 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
281 queryIntent, 0, ActivityManager.getCurrentUser());
282 for (ResolveInfo info : services) {
Narayan Kamath607223f2018-02-19 14:09:02 +0000283 if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
284 && Objects.equals(info.serviceInfo.name, component.getClassName())) {
Will Harmon604c2f92016-06-06 12:54:59 -0700285 return;
286 }
287 }
288 }
289
Jason Monk07f55a22016-04-11 15:30:38 -0400290 mServices.getHost().removeTile(component);
291 }
292 };
Jason Monkd5a204f2015-12-21 08:50:01 -0500293}