1 /* 2 * Copyright (C) 2017 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 17 package com.android.systemui; 18 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.ServiceConnection; 25 import android.graphics.Rect; 26 import android.os.Binder; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.PatternMatcher; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.util.Log; 34 import android.view.SurfaceControl; 35 36 import com.android.systemui.OverviewProxyService.OverviewProxyListener; 37 import com.android.systemui.recents.events.EventBus; 38 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent; 39 import com.android.systemui.recents.misc.SystemServicesProxy; 40 import com.android.systemui.shared.recents.IOverviewProxy; 41 import com.android.systemui.shared.recents.ISystemUiProxy; 42 import com.android.systemui.shared.system.ActivityManagerWrapper; 43 import com.android.systemui.shared.system.GraphicBufferCompat; 44 import com.android.systemui.stackdivider.Divider; 45 import com.android.systemui.statusbar.phone.StatusBar; 46 import com.android.systemui.statusbar.policy.CallbackController; 47 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 48 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; 49 50 import java.io.FileDescriptor; 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.List; 54 55 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 56 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; 57 import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; 58 59 /** 60 * Class to send information from overview to launcher with a binder. 61 */ 62 public class OverviewProxyService implements CallbackController<OverviewProxyListener>, Dumpable { 63 64 private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE"; 65 66 public static final String TAG_OPS = "OverviewProxyService"; 67 public static final boolean DEBUG_OVERVIEW_PROXY = false; 68 private static final long BACKOFF_MILLIS = 5000; 69 private static final long DEFERRED_CALLBACK_MILLIS = 5000; 70 71 private final Context mContext; 72 private final Handler mHandler; 73 private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser; 74 private final ComponentName mRecentsComponentName; 75 private final DeviceProvisionedController mDeviceProvisionedController 76 = Dependency.get(DeviceProvisionedController.class); 77 private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>(); 78 private final Intent mQuickStepIntent; 79 80 private IOverviewProxy mOverviewProxy; 81 private int mConnectionBackoffAttempts; 82 private @InteractionType int mInteractionFlags; 83 private boolean mIsEnabled; 84 85 private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { 86 87 public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer, 88 int maxLayer, boolean useIdentityTransform, int rotation) { 89 long token = Binder.clearCallingIdentity(); 90 try { 91 return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width, 92 height, minLayer, maxLayer, useIdentityTransform, rotation)); 93 } finally { 94 Binder.restoreCallingIdentity(token); 95 } 96 } 97 98 public void startScreenPinning(int taskId) { 99 long token = Binder.clearCallingIdentity(); 100 try { 101 mHandler.post(() -> { 102 StatusBar statusBar = ((SystemUIApplication) mContext).getComponent( 103 StatusBar.class); 104 if (statusBar != null) { 105 statusBar.showScreenPinningRequest(taskId, false /* allowCancel */); 106 } 107 }); 108 } finally { 109 Binder.restoreCallingIdentity(token); 110 } 111 } 112 113 public void onSplitScreenInvoked() { 114 long token = Binder.clearCallingIdentity(); 115 try { 116 EventBus.getDefault().post(new DockedFirstAnimationFrameEvent()); 117 } finally { 118 Binder.restoreCallingIdentity(token); 119 } 120 } 121 122 public void onOverviewShown(boolean fromHome) { 123 long token = Binder.clearCallingIdentity(); 124 try { 125 mHandler.post(() -> { 126 for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 127 mConnectionCallbacks.get(i).onOverviewShown(fromHome); 128 } 129 }); 130 } finally { 131 Binder.restoreCallingIdentity(token); 132 } 133 } 134 135 public void setInteractionState(@InteractionType int flags) { 136 long token = Binder.clearCallingIdentity(); 137 try { 138 if (mInteractionFlags != flags) { 139 mInteractionFlags = flags; 140 mHandler.post(() -> { 141 for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 142 mConnectionCallbacks.get(i).onInteractionFlagsChanged(flags); 143 } 144 }); 145 } 146 } finally { 147 Prefs.putInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, mInteractionFlags); 148 Binder.restoreCallingIdentity(token); 149 } 150 } 151 152 public Rect getNonMinimizedSplitScreenSecondaryBounds() { 153 long token = Binder.clearCallingIdentity(); 154 try { 155 Divider divider = ((SystemUIApplication) mContext).getComponent(Divider.class); 156 if (divider != null) { 157 return divider.getView().getNonMinimizedSplitScreenSecondaryBounds(); 158 } 159 return null; 160 } finally { 161 Binder.restoreCallingIdentity(token); 162 } 163 } 164 165 public void setBackButtonAlpha(float alpha, boolean animate) { 166 long token = Binder.clearCallingIdentity(); 167 try { 168 mHandler.post(() -> { 169 notifyBackButtonAlphaChanged(alpha, animate); 170 }); 171 } finally { 172 Binder.restoreCallingIdentity(token); 173 } 174 } 175 }; 176 177 private final Runnable mDeferredConnectionCallback = () -> { 178 Log.w(TAG_OPS, "Binder supposed established connection but actual connection to service " 179 + "timed out, trying again"); 180 internalConnectToCurrentUser(); 181 }; 182 183 private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() { 184 @Override 185 public void onReceive(Context context, Intent intent) { 186 updateEnabledState(); 187 188 // When launcher service is disabled, reset interaction flags because it is inactive 189 if (!isEnabled()) { 190 mInteractionFlags = 0; 191 Prefs.remove(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS); 192 } 193 194 // Reconnect immediately, instead of waiting for resume to arrive. 195 startConnectionToCurrentUser(); 196 } 197 }; 198 199 private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { 200 @Override 201 public void onServiceConnected(ComponentName name, IBinder service) { 202 mHandler.removeCallbacks(mDeferredConnectionCallback); 203 mConnectionBackoffAttempts = 0; 204 mOverviewProxy = IOverviewProxy.Stub.asInterface(service); 205 // Listen for launcher's death 206 try { 207 service.linkToDeath(mOverviewServiceDeathRcpt, 0); 208 } catch (RemoteException e) { 209 Log.e(TAG_OPS, "Lost connection to launcher service", e); 210 } 211 try { 212 mOverviewProxy.onBind(mSysUiProxy); 213 } catch (RemoteException e) { 214 Log.e(TAG_OPS, "Failed to call onBind()", e); 215 } 216 notifyConnectionChanged(); 217 } 218 219 @Override 220 public void onNullBinding(ComponentName name) { 221 Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting"); 222 internalConnectToCurrentUser(); 223 } 224 225 @Override 226 public void onBindingDied(ComponentName name) { 227 Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting"); 228 internalConnectToCurrentUser(); 229 } 230 231 @Override 232 public void onServiceDisconnected(ComponentName name) { 233 // Do nothing 234 } 235 }; 236 237 private final DeviceProvisionedListener mDeviceProvisionedCallback = 238 new DeviceProvisionedListener() { 239 @Override 240 public void onUserSetupChanged() { 241 if (mDeviceProvisionedController.isCurrentUserSetup()) { 242 internalConnectToCurrentUser(); 243 } 244 } 245 246 @Override 247 public void onUserSwitched() { 248 mConnectionBackoffAttempts = 0; 249 internalConnectToCurrentUser(); 250 } 251 }; 252 253 // This is the death handler for the binder from the launcher service 254 private final IBinder.DeathRecipient mOverviewServiceDeathRcpt 255 = this::startConnectionToCurrentUser; 256 OverviewProxyService(Context context)257 public OverviewProxyService(Context context) { 258 mContext = context; 259 mHandler = new Handler(); 260 mConnectionBackoffAttempts = 0; 261 mRecentsComponentName = ComponentName.unflattenFromString(context.getString( 262 com.android.internal.R.string.config_recentsComponentName)); 263 mQuickStepIntent = new Intent(ACTION_QUICKSTEP) 264 .setPackage(mRecentsComponentName.getPackageName()); 265 mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS, 0); 266 267 // Listen for the package update changes. 268 if (SystemServicesProxy.getInstance(context) 269 .isSystemUser(mDeviceProvisionedController.getCurrentUser())) { 270 updateEnabledState(); 271 mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback); 272 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 273 filter.addDataScheme("package"); 274 filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(), 275 PatternMatcher.PATTERN_LITERAL); 276 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 277 mContext.registerReceiver(mLauncherStateChangedReceiver, filter); 278 } 279 } 280 startConnectionToCurrentUser()281 public void startConnectionToCurrentUser() { 282 if (mHandler.getLooper() != Looper.myLooper()) { 283 mHandler.post(mConnectionRunnable); 284 } else { 285 internalConnectToCurrentUser(); 286 } 287 } 288 internalConnectToCurrentUser()289 private void internalConnectToCurrentUser() { 290 disconnectFromLauncherService(); 291 292 // If user has not setup yet or already connected, do not try to connect 293 if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) { 294 Log.v(TAG_OPS, "Cannot attempt connection, is setup " 295 + mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled " 296 + isEnabled()); 297 return; 298 } 299 mHandler.removeCallbacks(mConnectionRunnable); 300 Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP) 301 .setPackage(mRecentsComponentName.getPackageName()); 302 boolean bound = false; 303 try { 304 bound = mContext.bindServiceAsUser(launcherServiceIntent, 305 mOverviewServiceConnection, Context.BIND_AUTO_CREATE, 306 UserHandle.of(mDeviceProvisionedController.getCurrentUser())); 307 } catch (SecurityException e) { 308 Log.e(TAG_OPS, "Unable to bind because of security error", e); 309 } 310 if (bound) { 311 // Ensure that connection has been established even if it thinks it is bound 312 mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS); 313 } else { 314 // Retry after exponential backoff timeout 315 final long timeoutMs = (long) Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts); 316 mHandler.postDelayed(mConnectionRunnable, timeoutMs); 317 mConnectionBackoffAttempts++; 318 Log.w(TAG_OPS, "Failed to connect on attempt " + mConnectionBackoffAttempts 319 + " will try again in " + timeoutMs + "ms"); 320 } 321 } 322 323 @Override addCallback(OverviewProxyListener listener)324 public void addCallback(OverviewProxyListener listener) { 325 mConnectionCallbacks.add(listener); 326 listener.onConnectionChanged(mOverviewProxy != null); 327 listener.onInteractionFlagsChanged(mInteractionFlags); 328 } 329 330 @Override removeCallback(OverviewProxyListener listener)331 public void removeCallback(OverviewProxyListener listener) { 332 mConnectionCallbacks.remove(listener); 333 } 334 shouldShowSwipeUpUI()335 public boolean shouldShowSwipeUpUI() { 336 return isEnabled() && ((mInteractionFlags & FLAG_DISABLE_SWIPE_UP) == 0); 337 } 338 isEnabled()339 public boolean isEnabled() { 340 return mIsEnabled; 341 } 342 getProxy()343 public IOverviewProxy getProxy() { 344 return mOverviewProxy; 345 } 346 getInteractionFlags()347 public int getInteractionFlags() { 348 return mInteractionFlags; 349 } 350 disconnectFromLauncherService()351 private void disconnectFromLauncherService() { 352 if (mOverviewProxy != null) { 353 mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0); 354 mContext.unbindService(mOverviewServiceConnection); 355 mOverviewProxy = null; 356 notifyBackButtonAlphaChanged(1f, false /* animate */); 357 notifyConnectionChanged(); 358 } 359 } 360 notifyBackButtonAlphaChanged(float alpha, boolean animate)361 private void notifyBackButtonAlphaChanged(float alpha, boolean animate) { 362 for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 363 mConnectionCallbacks.get(i).onBackButtonAlphaChanged(alpha, animate); 364 } 365 } 366 notifyConnectionChanged()367 private void notifyConnectionChanged() { 368 for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 369 mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null); 370 } 371 } 372 notifyQuickStepStarted()373 public void notifyQuickStepStarted() { 374 for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 375 mConnectionCallbacks.get(i).onQuickStepStarted(); 376 } 377 } 378 notifyQuickScrubStarted()379 public void notifyQuickScrubStarted() { 380 for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 381 mConnectionCallbacks.get(i).onQuickScrubStarted(); 382 } 383 } 384 updateEnabledState()385 private void updateEnabledState() { 386 mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, 387 MATCH_DIRECT_BOOT_UNAWARE, 388 ActivityManagerWrapper.getInstance().getCurrentUserId()) != null; 389 } 390 391 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)392 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 393 pw.println(TAG_OPS + " state:"); 394 pw.print(" mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts); 395 pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController 396 .isCurrentUserSetup()); 397 pw.print(" isConnected="); pw.println(mOverviewProxy != null); 398 pw.print(" mRecentsComponentName="); pw.println(mRecentsComponentName); 399 pw.print(" mIsEnabled="); pw.println(isEnabled()); 400 pw.print(" mInteractionFlags="); pw.println(mInteractionFlags); 401 pw.print(" mQuickStepIntent="); pw.println(mQuickStepIntent); 402 } 403 404 public interface OverviewProxyListener { onConnectionChanged(boolean isConnected)405 default void onConnectionChanged(boolean isConnected) {} onQuickStepStarted()406 default void onQuickStepStarted() {} onInteractionFlagsChanged(@nteractionType int flags)407 default void onInteractionFlagsChanged(@InteractionType int flags) {} onOverviewShown(boolean fromHome)408 default void onOverviewShown(boolean fromHome) {} onQuickScrubStarted()409 default void onQuickScrubStarted() {} onBackButtonAlphaChanged(float alpha, boolean animate)410 default void onBackButtonAlphaChanged(float alpha, boolean animate) {} 411 } 412 } 413