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