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