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