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