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