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.qs.external.TileLifecycleManager.TileChangeListener;
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 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, Tile tile)74     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
75             Tile tile) {
76         this(tileServices, handler, new TileLifecycleManager(handler,
77                 tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
78                 new UserHandle(ActivityManager.getCurrentUser())));
79     }
80 
81     @VisibleForTesting
TileServiceManager(TileServices tileServices, Handler handler, TileLifecycleManager tileLifecycleManager)82     TileServiceManager(TileServices tileServices, Handler handler,
83             TileLifecycleManager tileLifecycleManager) {
84         mServices = tileServices;
85         mHandler = handler;
86         mStateManager = tileLifecycleManager;
87 
88         IntentFilter filter = new IntentFilter();
89         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
90         filter.addDataScheme("package");
91         Context context = mServices.getContext();
92         context.registerReceiverAsUser(mUninstallReceiver,
93                 new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
94     }
95 
isLifecycleStarted()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      */
startLifecycleManagerAndAddTile()107     void startLifecycleManagerAndAddTile() {
108         mStarted = true;
109         ComponentName component = mStateManager.getComponent();
110         Context context = mServices.getContext();
111         if (!TileLifecycleManager.isTileAdded(context, component)) {
112             TileLifecycleManager.setTileAdded(context, component, true);
113             mStateManager.onTileAdded();
114             mStateManager.flushMessagesAndUnbind();
115         }
116     }
117 
setTileChangeListener(TileChangeListener changeListener)118     public void setTileChangeListener(TileChangeListener changeListener) {
119         mStateManager.setTileChangeListener(changeListener);
120     }
121 
isActiveTile()122     public boolean isActiveTile() {
123         return mStateManager.isActiveTile();
124     }
125 
setShowingDialog(boolean dialog)126     public void setShowingDialog(boolean dialog) {
127         mShowingDialog = dialog;
128     }
129 
getTileService()130     public IQSTileService getTileService() {
131         return mStateManager;
132     }
133 
getToken()134     public IBinder getToken() {
135         return mStateManager.getToken();
136     }
137 
setBindRequested(boolean bindRequested)138     public void setBindRequested(boolean bindRequested) {
139         if (mBindRequested == bindRequested) return;
140         mBindRequested = bindRequested;
141         if (mBindAllowed && mBindRequested && !mBound) {
142             mHandler.removeCallbacks(mUnbind);
143             bindService();
144         } else {
145             mServices.recalculateBindAllowance();
146         }
147         if (mBound && !mBindRequested) {
148             mHandler.postDelayed(mUnbind, UNBIND_DELAY);
149         }
150     }
151 
setLastUpdate(long lastUpdate)152     public void setLastUpdate(long lastUpdate) {
153         mLastUpdate = lastUpdate;
154         if (mBound && isActiveTile()) {
155             mStateManager.onStopListening();
156             setBindRequested(false);
157         }
158         mServices.recalculateBindAllowance();
159     }
160 
handleDestroy()161     public void handleDestroy() {
162         setBindAllowed(false);
163         mServices.getContext().unregisterReceiver(mUninstallReceiver);
164         mStateManager.handleDestroy();
165     }
166 
setBindAllowed(boolean allowed)167     public void setBindAllowed(boolean allowed) {
168         if (mBindAllowed == allowed) return;
169         mBindAllowed = allowed;
170         if (!mBindAllowed && mBound) {
171             unbindService();
172         } else if (mBindAllowed && mBindRequested && !mBound) {
173             bindService();
174         }
175     }
176 
hasPendingBind()177     public boolean hasPendingBind() {
178         return mPendingBind;
179     }
180 
clearPendingBind()181     public void clearPendingBind() {
182         mPendingBind = false;
183     }
184 
bindService()185     private void bindService() {
186         if (mBound) {
187             Log.e(TAG, "Service already bound");
188             return;
189         }
190         mPendingBind = true;
191         mBound = true;
192         mJustBound = true;
193         mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
194         mStateManager.setBindService(true);
195     }
196 
unbindService()197     private void unbindService() {
198         if (!mBound) {
199             Log.e(TAG, "Service not bound");
200             return;
201         }
202         mBound = false;
203         mJustBound = false;
204         mStateManager.setBindService(false);
205     }
206 
calculateBindPriority(long currentTime)207     public void calculateBindPriority(long currentTime) {
208         if (mStateManager.hasPendingClick()) {
209             // Pending click is the most important thing, need to put this service at the top of
210             // the list to be bound.
211             mPriority = Integer.MAX_VALUE;
212         } else if (mShowingDialog) {
213             // Hang on to services that are showing dialogs so they don't die.
214             mPriority = Integer.MAX_VALUE - 1;
215         } else if (mJustBound) {
216             // If we just bound, lets not thrash on binding/unbinding too much, this is second most
217             // important.
218             mPriority = Integer.MAX_VALUE - 2;
219         } else if (!mBindRequested) {
220             // Don't care about binding right now, put us last.
221             mPriority = Integer.MIN_VALUE;
222         } else {
223             // Order based on whether this was just updated.
224             long timeSinceUpdate = currentTime - mLastUpdate;
225             // Fit compare into integer space for simplicity. Make sure to leave MAX_VALUE and
226             // MAX_VALUE - 1 for the more important states above.
227             if (timeSinceUpdate > Integer.MAX_VALUE - 3) {
228                 mPriority = Integer.MAX_VALUE - 3;
229             } else {
230                 mPriority = (int) timeSinceUpdate;
231             }
232         }
233     }
234 
getBindPriority()235     public int getBindPriority() {
236         return mPriority;
237     }
238 
239     private final Runnable mUnbind = new Runnable() {
240         @Override
241         public void run() {
242             if (mBound && !mBindRequested) {
243                 unbindService();
244             }
245         }
246     };
247 
248     @VisibleForTesting
249     final Runnable mJustBoundOver = new Runnable() {
250         @Override
251         public void run() {
252             mJustBound = false;
253             mServices.recalculateBindAllowance();
254         }
255     };
256 
257     private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
258         @Override
259         public void onReceive(Context context, Intent intent) {
260             if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
261                 return;
262             }
263 
264             Uri data = intent.getData();
265             String pkgName = data.getEncodedSchemeSpecificPart();
266             final ComponentName component = mStateManager.getComponent();
267             if (!Objects.equals(pkgName, component.getPackageName())) {
268                 return;
269             }
270 
271             // If the package is being updated, verify the component still exists.
272             if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
273                 Intent queryIntent = new Intent(TileService.ACTION_QS_TILE);
274                 queryIntent.setPackage(pkgName);
275                 PackageManager pm = context.getPackageManager();
276                 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
277                         queryIntent, 0, ActivityManager.getCurrentUser());
278                 for (ResolveInfo info : services) {
279                     if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
280                             && Objects.equals(info.serviceInfo.name, component.getClassName())) {
281                         return;
282                     }
283                 }
284             }
285 
286             mServices.getHost().removeTile(component);
287         }
288     };
289 }
290