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