• 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 package com.android.systemui.qs.external;
17 
18 import android.content.BroadcastReceiver;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.net.Uri;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.service.quicksettings.IQSTileService;
29 import android.service.quicksettings.Tile;
30 import android.service.quicksettings.TileService;
31 import android.util.Log;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.systemui.broadcast.BroadcastDispatcher;
36 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
37 import com.android.systemui.settings.UserTracker;
38 
39 import java.util.List;
40 import java.util.Objects;
41 
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  */
47 public 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 
56     @VisibleForTesting
57     static final String PREFS_FILE = "CustomTileModes";
58 
59     private final TileServices mServices;
60     private final TileLifecycleManager mStateManager;
61     private final Handler mHandler;
62     private final UserTracker mUserTracker;
63     private boolean mBindRequested;
64     private boolean mBindAllowed;
65     private boolean mBound;
66     private int mPriority;
67     private boolean mJustBound;
68     private long mLastUpdate;
69     private boolean mShowingDialog;
70     // Whether we have a pending bind going out to the service without a response yet.
71     // This defaults to true to ensure tiles start out unavailable.
72     private boolean mPendingBind = true;
73     private boolean mStarted = false;
74 
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker)75     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
76             Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
77         this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
78                 tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
79                 userTracker.getUserHandle(), broadcastDispatcher));
80     }
81 
82     @VisibleForTesting
TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker, TileLifecycleManager tileLifecycleManager)83     TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
84             TileLifecycleManager tileLifecycleManager) {
85         mServices = tileServices;
86         mHandler = handler;
87         mStateManager = tileLifecycleManager;
88         mUserTracker = userTracker;
89 
90         IntentFilter filter = new IntentFilter();
91         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
92         filter.addDataScheme("package");
93         Context context = mServices.getContext();
94         context.registerReceiverAsUser(mUninstallReceiver, userTracker.getUserHandle(), filter,
95                 null, mHandler);
96     }
97 
isLifecycleStarted()98     boolean isLifecycleStarted() {
99         return mStarted;
100     }
101 
102     /**
103      * Starts the TileLifecycleManager by adding the corresponding component as a Tile and
104      * binding to it if needed.
105      *
106      * This method should be called after constructing a TileServiceManager to guarantee that the
107      * TileLifecycleManager has added the tile and bound to it at least once.
108      */
startLifecycleManagerAndAddTile()109     void startLifecycleManagerAndAddTile() {
110         mStarted = true;
111         ComponentName component = mStateManager.getComponent();
112         Context context = mServices.getContext();
113         if (!TileLifecycleManager.isTileAdded(context, component)) {
114             TileLifecycleManager.setTileAdded(context, component, true);
115             mStateManager.onTileAdded();
116             mStateManager.flushMessagesAndUnbind();
117         }
118     }
119 
setTileChangeListener(TileChangeListener changeListener)120     public void setTileChangeListener(TileChangeListener changeListener) {
121         mStateManager.setTileChangeListener(changeListener);
122     }
123 
isActiveTile()124     public boolean isActiveTile() {
125         return mStateManager.isActiveTile();
126     }
127 
isToggleableTile()128     public boolean isToggleableTile() {
129         return mStateManager.isToggleableTile();
130     }
131 
setShowingDialog(boolean dialog)132     public void setShowingDialog(boolean dialog) {
133         mShowingDialog = dialog;
134     }
135 
getTileService()136     public IQSTileService getTileService() {
137         return mStateManager;
138     }
139 
getToken()140     public IBinder getToken() {
141         return mStateManager.getToken();
142     }
143 
setBindRequested(boolean bindRequested)144     public void setBindRequested(boolean bindRequested) {
145         if (mBindRequested == bindRequested) return;
146         mBindRequested = bindRequested;
147         if (mBindAllowed && mBindRequested && !mBound) {
148             mHandler.removeCallbacks(mUnbind);
149             bindService();
150         } else {
151             mServices.recalculateBindAllowance();
152         }
153         if (mBound && !mBindRequested) {
154             mHandler.postDelayed(mUnbind, UNBIND_DELAY);
155         }
156     }
157 
setLastUpdate(long lastUpdate)158     public void setLastUpdate(long lastUpdate) {
159         mLastUpdate = lastUpdate;
160         if (mBound && isActiveTile()) {
161             mStateManager.onStopListening();
162             setBindRequested(false);
163         }
164         mServices.recalculateBindAllowance();
165     }
166 
handleDestroy()167     public void handleDestroy() {
168         setBindAllowed(false);
169         mServices.getContext().unregisterReceiver(mUninstallReceiver);
170         mStateManager.handleDestroy();
171     }
172 
setBindAllowed(boolean allowed)173     public void setBindAllowed(boolean allowed) {
174         if (mBindAllowed == allowed) return;
175         mBindAllowed = allowed;
176         if (!mBindAllowed && mBound) {
177             unbindService();
178         } else if (mBindAllowed && mBindRequested && !mBound) {
179             bindService();
180         }
181     }
182 
hasPendingBind()183     public boolean hasPendingBind() {
184         return mPendingBind;
185     }
186 
clearPendingBind()187     public void clearPendingBind() {
188         mPendingBind = false;
189     }
190 
bindService()191     private void bindService() {
192         if (mBound) {
193             Log.e(TAG, "Service already bound");
194             return;
195         }
196         mPendingBind = true;
197         mBound = true;
198         mJustBound = true;
199         mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
200         mStateManager.setBindService(true);
201     }
202 
unbindService()203     private void unbindService() {
204         if (!mBound) {
205             Log.e(TAG, "Service not bound");
206             return;
207         }
208         mBound = false;
209         mJustBound = false;
210         mStateManager.setBindService(false);
211     }
212 
calculateBindPriority(long currentTime)213     public void calculateBindPriority(long currentTime) {
214         if (mStateManager.hasPendingClick()) {
215             // Pending click is the most important thing, need to put this service at the top of
216             // the list to be bound.
217             mPriority = Integer.MAX_VALUE;
218         } else if (mShowingDialog) {
219             // Hang on to services that are showing dialogs so they don't die.
220             mPriority = Integer.MAX_VALUE - 1;
221         } else if (mJustBound) {
222             // If we just bound, lets not thrash on binding/unbinding too much, this is second most
223             // important.
224             mPriority = Integer.MAX_VALUE - 2;
225         } else if (!mBindRequested) {
226             // Don't care about binding right now, put us last.
227             mPriority = Integer.MIN_VALUE;
228         } else {
229             // Order based on whether this was just updated.
230             long timeSinceUpdate = currentTime - mLastUpdate;
231             // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and
232             // MAX_VALUE - 1 for the more important states above.
233             if (timeSinceUpdate > Integer.MAX_VALUE - 3) {
234                 mPriority = Integer.MAX_VALUE - 3;
235             } else {
236                 mPriority = (int) timeSinceUpdate;
237             }
238         }
239     }
240 
getBindPriority()241     public int getBindPriority() {
242         return mPriority;
243     }
244 
245     private final Runnable mUnbind = new Runnable() {
246         @Override
247         public void run() {
248             if (mBound && !mBindRequested) {
249                 unbindService();
250             }
251         }
252     };
253 
254     @VisibleForTesting
255     final Runnable mJustBoundOver = new Runnable() {
256         @Override
257         public void run() {
258             mJustBound = false;
259             mServices.recalculateBindAllowance();
260         }
261     };
262 
263     private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
264         @Override
265         public void onReceive(Context context, Intent intent) {
266             if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
267                 return;
268             }
269 
270             Uri data = intent.getData();
271             String pkgName = data.getEncodedSchemeSpecificPart();
272             final ComponentName component = mStateManager.getComponent();
273             if (!Objects.equals(pkgName, component.getPackageName())) {
274                 return;
275             }
276 
277             // If the package is being updated, verify the component still exists.
278             if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
279                 Intent queryIntent = new Intent(TileService.ACTION_QS_TILE);
280                 queryIntent.setPackage(pkgName);
281                 PackageManager pm = context.getPackageManager();
282                 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
283                         queryIntent, 0, mUserTracker.getUserId());
284                 for (ResolveInfo info : services) {
285                     if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
286                             && Objects.equals(info.serviceInfo.name, component.getClassName())) {
287                         return;
288                     }
289                 }
290             }
291 
292             mServices.getHost().removeTile(component);
293         }
294     };
295 }
296