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