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