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.recents;
18 
19 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
20 import static android.view.MotionEvent.ACTION_CANCEL;
21 import static android.view.MotionEvent.ACTION_DOWN;
22 import static android.view.MotionEvent.ACTION_UP;
23 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
24 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
25 
26 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
27 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
28 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
29 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
30 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
35 
36 import android.annotation.FloatRange;
37 import android.app.ActivityTaskManager;
38 import android.content.BroadcastReceiver;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.ServiceConnection;
44 import android.graphics.Bitmap;
45 import android.graphics.Insets;
46 import android.graphics.Rect;
47 import android.graphics.Region;
48 import android.hardware.input.InputManager;
49 import android.os.Binder;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Looper;
54 import android.os.PatternMatcher;
55 import android.os.RemoteException;
56 import android.os.UserHandle;
57 import android.util.Log;
58 import android.view.InputMonitor;
59 import android.view.MotionEvent;
60 import android.view.Surface;
61 import android.view.accessibility.AccessibilityManager;
62 
63 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
64 import com.android.internal.policy.ScreenDecorationsUtils;
65 import com.android.internal.util.ScreenshotHelper;
66 import com.android.systemui.Dumpable;
67 import com.android.systemui.broadcast.BroadcastDispatcher;
68 import com.android.systemui.model.SysUiState;
69 import com.android.systemui.pip.PipAnimationController;
70 import com.android.systemui.pip.PipUI;
71 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
72 import com.android.systemui.settings.CurrentUserTracker;
73 import com.android.systemui.shared.recents.IOverviewProxy;
74 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
75 import com.android.systemui.shared.recents.ISystemUiProxy;
76 import com.android.systemui.shared.recents.model.Task;
77 import com.android.systemui.shared.system.ActivityManagerWrapper;
78 import com.android.systemui.shared.system.QuickStepContract;
79 import com.android.systemui.stackdivider.Divider;
80 import com.android.systemui.statusbar.CommandQueue;
81 import com.android.systemui.statusbar.NavigationBarController;
82 import com.android.systemui.statusbar.phone.NavigationBarFragment;
83 import com.android.systemui.statusbar.phone.NavigationBarView;
84 import com.android.systemui.statusbar.phone.NavigationModeController;
85 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
86 import com.android.systemui.statusbar.phone.StatusBar;
87 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
88 import com.android.systemui.statusbar.policy.CallbackController;
89 
90 import java.io.FileDescriptor;
91 import java.io.PrintWriter;
92 import java.util.ArrayList;
93 import java.util.List;
94 import java.util.Optional;
95 
96 import javax.inject.Inject;
97 import javax.inject.Singleton;
98 
99 import dagger.Lazy;
100 
101 /**
102  * Class to send information from overview to launcher with a binder.
103  */
104 @Singleton
105 public class OverviewProxyService extends CurrentUserTracker implements
106         CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
107         Dumpable {
108 
109     private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
110 
111     public static final String TAG_OPS = "OverviewProxyService";
112     private static final long BACKOFF_MILLIS = 1000;
113     private static final long DEFERRED_CALLBACK_MILLIS = 5000;
114 
115     // Max backoff caps at 5 mins
116     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
117 
118     private final Context mContext;
119     private final PipUI mPipUI;
120     private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
121     private final Optional<Divider> mDividerOptional;
122     private SysUiState mSysUiState;
123     private final Handler mHandler;
124     private final NavigationBarController mNavBarController;
125     private final NotificationShadeWindowController mStatusBarWinController;
126     private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
127     private final ComponentName mRecentsComponentName;
128     private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
129     private final Intent mQuickStepIntent;
130     private final ScreenshotHelper mScreenshotHelper;
131 
132     private Region mActiveNavBarRegion;
133 
134     private IOverviewProxy mOverviewProxy;
135     private int mConnectionBackoffAttempts;
136     private boolean mBound;
137     private boolean mIsEnabled;
138     private int mCurrentBoundedUserId = -1;
139     private float mNavBarButtonAlpha;
140     private boolean mInputFocusTransferStarted;
141     private float mInputFocusTransferStartY;
142     private long mInputFocusTransferStartMillis;
143     private float mWindowCornerRadius;
144     private boolean mSupportsRoundedCornersOnWindows;
145     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
146 
147     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
148 
149         @Override
150         public void startScreenPinning(int taskId) {
151             if (!verifyCaller("startScreenPinning")) {
152                 return;
153             }
154             long token = Binder.clearCallingIdentity();
155             try {
156                 mHandler.post(() -> {
157                     mStatusBarOptionalLazy.ifPresent(
158                             statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
159                                     false /* allowCancel */));
160                 });
161             } finally {
162                 Binder.restoreCallingIdentity(token);
163             }
164         }
165 
166         @Override
167         public void stopScreenPinning() {
168             if (!verifyCaller("stopScreenPinning")) {
169                 return;
170             }
171             long token = Binder.clearCallingIdentity();
172             try {
173                 mHandler.post(() -> {
174                     try {
175                         ActivityTaskManager.getService().stopSystemLockTaskMode();
176                     } catch (RemoteException e) {
177                         Log.e(TAG_OPS, "Failed to stop screen pinning");
178                     }
179                 });
180             } finally {
181                 Binder.restoreCallingIdentity(token);
182             }
183         }
184 
185         // TODO: change the method signature to use (boolean inputFocusTransferStarted)
186         @Override
187         public void onStatusBarMotionEvent(MotionEvent event) {
188             if (!verifyCaller("onStatusBarMotionEvent")) {
189                 return;
190             }
191             long token = Binder.clearCallingIdentity();
192             try {
193                 // TODO move this logic to message queue
194                 mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
195                     mHandler.post(()-> {
196                         StatusBar statusBar = statusBarLazy.get();
197                         int action = event.getActionMasked();
198                         if (action == ACTION_DOWN) {
199                             mInputFocusTransferStarted = true;
200                             mInputFocusTransferStartY = event.getY();
201                             mInputFocusTransferStartMillis = event.getEventTime();
202                             statusBar.onInputFocusTransfer(
203                                     mInputFocusTransferStarted, false /* cancel */,
204                                     0 /* velocity */);
205                         }
206                         if (action == ACTION_UP || action == ACTION_CANCEL) {
207                             mInputFocusTransferStarted = false;
208                             statusBar.onInputFocusTransfer(mInputFocusTransferStarted,
209                                     action == ACTION_CANCEL,
210                                     (event.getY() - mInputFocusTransferStartY)
211                                     / (event.getEventTime() - mInputFocusTransferStartMillis));
212                         }
213                         event.recycle();
214                     });
215                 });
216             } finally {
217                 Binder.restoreCallingIdentity(token);
218             }
219         }
220 
221         @Override
222         public void onSplitScreenInvoked() {
223             if (!verifyCaller("onSplitScreenInvoked")) {
224                 return;
225             }
226             long token = Binder.clearCallingIdentity();
227             try {
228                 mDividerOptional.ifPresent(Divider::onDockedFirstAnimationFrame);
229             } finally {
230                 Binder.restoreCallingIdentity(token);
231             }
232         }
233 
234         @Override
235         public void onOverviewShown(boolean fromHome) {
236             if (!verifyCaller("onOverviewShown")) {
237                 return;
238             }
239             long token = Binder.clearCallingIdentity();
240             try {
241                 mHandler.post(() -> {
242                     for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
243                         mConnectionCallbacks.get(i).onOverviewShown(fromHome);
244                     }
245                 });
246             } finally {
247                 Binder.restoreCallingIdentity(token);
248             }
249         }
250 
251         @Override
252         public Rect getNonMinimizedSplitScreenSecondaryBounds() {
253             if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
254                 return null;
255             }
256             long token = Binder.clearCallingIdentity();
257             try {
258                 return mDividerOptional.map(
259                         divider -> divider.getView().getNonMinimizedSplitScreenSecondaryBounds())
260                         .orElse(null);
261             } finally {
262                 Binder.restoreCallingIdentity(token);
263             }
264         }
265 
266         @Override
267         public void setNavBarButtonAlpha(float alpha, boolean animate) {
268             if (!verifyCaller("setNavBarButtonAlpha")) {
269                 return;
270             }
271             long token = Binder.clearCallingIdentity();
272             try {
273                 mNavBarButtonAlpha = alpha;
274                 mHandler.post(() -> notifyNavBarButtonAlphaChanged(alpha, animate));
275             } finally {
276                 Binder.restoreCallingIdentity(token);
277             }
278         }
279 
280         @Override
281         public void setBackButtonAlpha(float alpha, boolean animate) {
282             setNavBarButtonAlpha(alpha, animate);
283         }
284 
285         @Override
286         public void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
287             if (!verifyCaller("onAssistantProgress")) {
288                 return;
289             }
290             long token = Binder.clearCallingIdentity();
291             try {
292                 mHandler.post(() -> notifyAssistantProgress(progress));
293             } finally {
294                 Binder.restoreCallingIdentity(token);
295             }
296         }
297 
298         @Override
299         public void onAssistantGestureCompletion(float velocity) {
300             if (!verifyCaller("onAssistantGestureCompletion")) {
301                 return;
302             }
303             long token = Binder.clearCallingIdentity();
304             try {
305                 mHandler.post(() -> notifyAssistantGestureCompletion(velocity));
306             } finally {
307                 Binder.restoreCallingIdentity(token);
308             }
309         }
310 
311         @Override
312         public void startAssistant(Bundle bundle) {
313             if (!verifyCaller("startAssistant")) {
314                 return;
315             }
316             long token = Binder.clearCallingIdentity();
317             try {
318                 mHandler.post(() -> notifyStartAssistant(bundle));
319             } finally {
320                 Binder.restoreCallingIdentity(token);
321             }
322         }
323 
324         @Override
325         public Bundle monitorGestureInput(String name, int displayId) {
326             if (!verifyCaller("monitorGestureInput")) {
327                 return null;
328             }
329             long token = Binder.clearCallingIdentity();
330             try {
331                 InputMonitor monitor =
332                         InputManager.getInstance().monitorGestureInput(name, displayId);
333                 Bundle result = new Bundle();
334                 result.putParcelable(KEY_EXTRA_INPUT_MONITOR, monitor);
335                 return result;
336             } finally {
337                 Binder.restoreCallingIdentity(token);
338             }
339         }
340 
341         @Override
342         public void notifyAccessibilityButtonClicked(int displayId) {
343             if (!verifyCaller("notifyAccessibilityButtonClicked")) {
344                 return;
345             }
346             long token = Binder.clearCallingIdentity();
347             try {
348                 AccessibilityManager.getInstance(mContext)
349                         .notifyAccessibilityButtonClicked(displayId);
350             } finally {
351                 Binder.restoreCallingIdentity(token);
352             }
353         }
354 
355         @Override
356         public void notifyAccessibilityButtonLongClicked() {
357             if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
358                 return;
359             }
360             long token = Binder.clearCallingIdentity();
361             try {
362                 final Intent intent =
363                         new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
364                 final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
365                 intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
366                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
367                 mContext.startActivityAsUser(intent, UserHandle.CURRENT);
368             } finally {
369                 Binder.restoreCallingIdentity(token);
370             }
371         }
372 
373         @Override
374         public void setShelfHeight(boolean visible, int shelfHeight) {
375             if (!verifyCaller("setShelfHeight")) {
376                 return;
377             }
378             long token = Binder.clearCallingIdentity();
379             try {
380                 mPipUI.setShelfHeight(visible, shelfHeight);
381             } finally {
382                 Binder.restoreCallingIdentity(token);
383             }
384         }
385 
386         @Override
387         public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
388                 Insets visibleInsets, int taskId) {
389             // Deprecated
390         }
391 
392         @Override
393         public void setSplitScreenMinimized(boolean minimized) {
394             Divider divider = mDividerOptional.get();
395             if (divider != null) {
396                 divider.setMinimized(minimized);
397             }
398         }
399 
400         @Override
401         public void notifySwipeToHomeFinished() {
402             if (!verifyCaller("notifySwipeToHomeFinished")) {
403                 return;
404             }
405             long token = Binder.clearCallingIdentity();
406             try {
407                 mPipUI.setPinnedStackAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
408             } finally {
409                 Binder.restoreCallingIdentity(token);
410             }
411         }
412 
413         @Override
414         public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
415             if (!verifyCaller("setPinnedStackAnimationListener")) {
416                 return;
417             }
418             long token = Binder.clearCallingIdentity();
419             try {
420                 mPipUI.setPinnedStackAnimationListener(listener);
421             } finally {
422                 Binder.restoreCallingIdentity(token);
423             }
424         }
425 
426         @Override
427         public void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {
428             if (!verifyCaller("onQuickSwitchToNewTask")) {
429                 return;
430             }
431             long token = Binder.clearCallingIdentity();
432             try {
433                 mHandler.post(() -> notifyQuickSwitchToNewTask(rotation));
434             } finally {
435                 Binder.restoreCallingIdentity(token);
436             }
437         }
438 
439         @Override
440         public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
441                 Insets visibleInsets, Task.TaskKey task) {
442             mScreenshotHelper.provideScreenshot(
443                     screenImageBundle,
444                     locationInScreen,
445                     visibleInsets,
446                     task.id,
447                     task.userId,
448                     task.sourceComponent,
449                     SCREENSHOT_OVERVIEW,
450                     mHandler,
451                     null);
452         }
453 
454         private boolean verifyCaller(String reason) {
455             final int callerId = Binder.getCallingUserHandle().getIdentifier();
456             if (callerId != mCurrentBoundedUserId) {
457                 Log.w(TAG_OPS, "Launcher called sysui with invalid user: " + callerId + ", reason: "
458                         + reason);
459                 return false;
460             }
461             return true;
462         }
463     };
464 
465     private final Runnable mDeferredConnectionCallback = () -> {
466         Log.w(TAG_OPS, "Binder supposed established connection but actual connection to service "
467             + "timed out, trying again");
468         retryConnectionWithBackoff();
469     };
470 
471     private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
472         @Override
473         public void onReceive(Context context, Intent intent) {
474             updateEnabledState();
475 
476             // Reconnect immediately, instead of waiting for resume to arrive.
477             startConnectionToCurrentUser();
478         }
479     };
480 
481     private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
482         @Override
483         public void onServiceConnected(ComponentName name, IBinder service) {
484             if (SysUiState.DEBUG) {
485                 Log.d(TAG_OPS, "Overview proxy service connected");
486             }
487             mConnectionBackoffAttempts = 0;
488             mHandler.removeCallbacks(mDeferredConnectionCallback);
489             try {
490                 service.linkToDeath(mOverviewServiceDeathRcpt, 0);
491             } catch (RemoteException e) {
492                 // Failed to link to death (process may have died between binding and connecting),
493                 // just unbind the service for now and retry again
494                 Log.e(TAG_OPS, "Lost connection to launcher service", e);
495                 disconnectFromLauncherService();
496                 retryConnectionWithBackoff();
497                 return;
498             }
499 
500             mCurrentBoundedUserId = getCurrentUserId();
501             mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
502 
503             Bundle params = new Bundle();
504             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
505             params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
506             params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
507             try {
508                 mOverviewProxy.onInitialize(params);
509             } catch (RemoteException e) {
510                 mCurrentBoundedUserId = -1;
511                 Log.e(TAG_OPS, "Failed to call onInitialize()", e);
512             }
513             dispatchNavButtonBounds();
514 
515             // Force-update the systemui state flags
516             updateSystemUiStateFlags();
517             notifySystemUiStateFlags(mSysUiState.getFlags());
518 
519             notifyConnectionChanged();
520         }
521 
522         @Override
523         public void onNullBinding(ComponentName name) {
524             Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting");
525             mCurrentBoundedUserId = -1;
526             retryConnectionWithBackoff();
527         }
528 
529         @Override
530         public void onBindingDied(ComponentName name) {
531             Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting");
532             mCurrentBoundedUserId = -1;
533             retryConnectionWithBackoff();
534         }
535 
536         @Override
537         public void onServiceDisconnected(ComponentName name) {
538             // Do nothing
539             mCurrentBoundedUserId = -1;
540         }
541     };
542 
543     private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
544 
545     // This is the death handler for the binder from the launcher service
546     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
547             = this::cleanupAfterDeath;
548 
549     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
550     @Inject
OverviewProxyService(Context context, CommandQueue commandQueue, NavigationBarController navBarController, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI, Optional<Divider> dividerOptional, Optional<Lazy<StatusBar>> statusBarOptionalLazy, BroadcastDispatcher broadcastDispatcher)551     public OverviewProxyService(Context context, CommandQueue commandQueue,
552             NavigationBarController navBarController, NavigationModeController navModeController,
553             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
554             PipUI pipUI, Optional<Divider> dividerOptional,
555             Optional<Lazy<StatusBar>> statusBarOptionalLazy,
556             BroadcastDispatcher broadcastDispatcher) {
557         super(broadcastDispatcher);
558         mContext = context;
559         mPipUI = pipUI;
560         mStatusBarOptionalLazy = statusBarOptionalLazy;
561         mHandler = new Handler();
562         mNavBarController = navBarController;
563         mStatusBarWinController = statusBarWinController;
564         mConnectionBackoffAttempts = 0;
565         mDividerOptional = dividerOptional;
566         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
567                 com.android.internal.R.string.config_recentsComponentName));
568         mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
569                 .setPackage(mRecentsComponentName.getPackageName());
570         mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
571         mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
572                 .supportsRoundedCornersOnWindows(mContext.getResources());
573         mSysUiState = sysUiState;
574         mSysUiState.addCallback(this::notifySystemUiStateFlags);
575 
576         // Assumes device always starts with back button until launcher tells it that it does not
577         mNavBarButtonAlpha = 1.0f;
578 
579         // Listen for nav bar mode changes
580         mNavBarMode = navModeController.addListener(this);
581 
582         // Listen for launcher package changes
583         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
584         filter.addDataScheme("package");
585         filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(),
586                 PatternMatcher.PATTERN_LITERAL);
587         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
588         mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
589 
590         // Listen for status bar state changes
591         statusBarWinController.registerCallback(mStatusBarWindowCallback);
592         mScreenshotHelper = new ScreenshotHelper(context);
593 
594         // Listen for tracing state changes
595         commandQueue.addCallback(new CommandQueue.Callbacks() {
596             @Override
597             public void onTracingStateChanged(boolean enabled) {
598                 mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
599                         .commitUpdate(mContext.getDisplayId());
600             }
601         });
602 
603         // Listen for user setup
604         startTracking();
605 
606         // Connect to the service
607         updateEnabledState();
608         startConnectionToCurrentUser();
609     }
610 
611     @Override
onUserSwitched(int newUserId)612     public void onUserSwitched(int newUserId) {
613         mConnectionBackoffAttempts = 0;
614         internalConnectToCurrentUser();
615     }
616 
notifyBackAction(boolean completed, int downX, int downY, boolean isButton, boolean gestureSwipeLeft)617     public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
618             boolean gestureSwipeLeft) {
619         try {
620             if (mOverviewProxy != null) {
621                 mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
622             }
623         } catch (RemoteException e) {
624             Log.e(TAG_OPS, "Failed to notify back action", e);
625         }
626     }
627 
updateSystemUiStateFlags()628     private void updateSystemUiStateFlags() {
629         final NavigationBarFragment navBarFragment =
630                 mNavBarController.getDefaultNavigationBarFragment();
631         final NavigationBarView navBarView =
632                 mNavBarController.getNavigationBarView(mContext.getDisplayId());
633         if (SysUiState.DEBUG) {
634             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
635                     + " navBarView=" + navBarView);
636         }
637 
638         if (navBarFragment != null) {
639             navBarFragment.updateSystemUiStateFlags(-1);
640         }
641         if (navBarView != null) {
642             navBarView.updatePanelSystemUiStateFlags();
643             navBarView.updateDisabledSystemUiStateFlags();
644         }
645         if (mStatusBarWinController != null) {
646             mStatusBarWinController.notifyStateChangedCallbacks();
647         }
648     }
649 
notifySystemUiStateFlags(int flags)650     private void notifySystemUiStateFlags(int flags) {
651         if (SysUiState.DEBUG) {
652             Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy="
653                     + mOverviewProxy + " flags=" + flags);
654         }
655         try {
656             if (mOverviewProxy != null) {
657                 mOverviewProxy.onSystemUiStateChanged(flags);
658             }
659         } catch (RemoteException e) {
660             Log.e(TAG_OPS, "Failed to notify sysui state change", e);
661         }
662     }
663 
onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing)664     private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
665             boolean bouncerShowing) {
666         mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
667                         keyguardShowing && !keyguardOccluded)
668                 .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
669                         keyguardShowing && keyguardOccluded)
670                 .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
671                 .commitUpdate(mContext.getDisplayId());
672     }
673 
674     /**
675      * Sets the navbar region which can receive touch inputs
676      */
onActiveNavBarRegionChanges(Region activeRegion)677     public void onActiveNavBarRegionChanges(Region activeRegion) {
678         mActiveNavBarRegion = activeRegion;
679         dispatchNavButtonBounds();
680     }
681 
dispatchNavButtonBounds()682     private void dispatchNavButtonBounds() {
683         if (mOverviewProxy != null && mActiveNavBarRegion != null) {
684             try {
685                 mOverviewProxy.onActiveNavBarRegionChanges(mActiveNavBarRegion);
686             } catch (RemoteException e) {
687                 Log.e(TAG_OPS, "Failed to call onActiveNavBarRegionChanges()", e);
688             }
689         }
690     }
691 
cleanupAfterDeath()692     public void cleanupAfterDeath() {
693         if (mInputFocusTransferStarted) {
694             mHandler.post(()-> {
695                 mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
696                     mInputFocusTransferStarted = false;
697                     statusBarLazy.get().onInputFocusTransfer(false, true /* cancel */,
698                             0 /* velocity */);
699                 });
700             });
701         }
702         startConnectionToCurrentUser();
703 
704         // Clean up the minimized state if launcher dies
705         Divider divider = mDividerOptional.get();
706         if (divider != null) {
707             divider.setMinimized(false);
708         }
709     }
710 
startConnectionToCurrentUser()711     public void startConnectionToCurrentUser() {
712         if (mHandler.getLooper() != Looper.myLooper()) {
713             mHandler.post(mConnectionRunnable);
714         } else {
715             internalConnectToCurrentUser();
716         }
717     }
718 
internalConnectToCurrentUser()719     private void internalConnectToCurrentUser() {
720         disconnectFromLauncherService();
721 
722         // If user has not setup yet or already connected, do not try to connect
723         if (!isEnabled()) {
724             Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled());
725             return;
726         }
727         mHandler.removeCallbacks(mConnectionRunnable);
728         Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
729                 .setPackage(mRecentsComponentName.getPackageName());
730         try {
731             mBound = mContext.bindServiceAsUser(launcherServiceIntent,
732                     mOverviewServiceConnection,
733                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
734                     UserHandle.of(getCurrentUserId()));
735         } catch (SecurityException e) {
736             Log.e(TAG_OPS, "Unable to bind because of security error", e);
737         }
738         if (mBound) {
739             // Ensure that connection has been established even if it thinks it is bound
740             mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
741         } else {
742             // Retry after exponential backoff timeout
743             retryConnectionWithBackoff();
744         }
745     }
746 
retryConnectionWithBackoff()747     private void retryConnectionWithBackoff() {
748         if (mHandler.hasCallbacks(mConnectionRunnable)) {
749             return;
750         }
751         final long timeoutMs = (long) Math.min(
752                 Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts), MAX_BACKOFF_MILLIS);
753         mHandler.postDelayed(mConnectionRunnable, timeoutMs);
754         mConnectionBackoffAttempts++;
755         Log.w(TAG_OPS, "Failed to connect on attempt " + mConnectionBackoffAttempts
756                 + " will try again in " + timeoutMs + "ms");
757     }
758 
759     @Override
addCallback(OverviewProxyListener listener)760     public void addCallback(OverviewProxyListener listener) {
761         mConnectionCallbacks.add(listener);
762         listener.onConnectionChanged(mOverviewProxy != null);
763         listener.onNavBarButtonAlphaChanged(mNavBarButtonAlpha, false);
764     }
765 
766     @Override
removeCallback(OverviewProxyListener listener)767     public void removeCallback(OverviewProxyListener listener) {
768         mConnectionCallbacks.remove(listener);
769     }
770 
shouldShowSwipeUpUI()771     public boolean shouldShowSwipeUpUI() {
772         return isEnabled() && !QuickStepContract.isLegacyMode(mNavBarMode);
773     }
774 
isEnabled()775     public boolean isEnabled() {
776         return mIsEnabled;
777     }
778 
getProxy()779     public IOverviewProxy getProxy() {
780         return mOverviewProxy;
781     }
782 
disconnectFromLauncherService()783     private void disconnectFromLauncherService() {
784         if (mBound) {
785             // Always unbind the service (ie. if called through onNullBinding or onBindingDied)
786             mContext.unbindService(mOverviewServiceConnection);
787             mBound = false;
788         }
789 
790         if (mOverviewProxy != null) {
791             mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
792             mOverviewProxy = null;
793             notifyNavBarButtonAlphaChanged(1f, false /* animate */);
794             notifyConnectionChanged();
795         }
796     }
797 
notifyNavBarButtonAlphaChanged(float alpha, boolean animate)798     private void notifyNavBarButtonAlphaChanged(float alpha, boolean animate) {
799         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
800             mConnectionCallbacks.get(i).onNavBarButtonAlphaChanged(alpha, animate);
801         }
802     }
803 
notifyConnectionChanged()804     private void notifyConnectionChanged() {
805         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
806             mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
807         }
808     }
809 
notifyQuickStepStarted()810     public void notifyQuickStepStarted() {
811         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
812             mConnectionCallbacks.get(i).onQuickStepStarted();
813         }
814     }
815 
notifyQuickSwitchToNewTask(@urface.Rotation int rotation)816     private void notifyQuickSwitchToNewTask(@Surface.Rotation int rotation) {
817         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
818             mConnectionCallbacks.get(i).onQuickSwitchToNewTask(rotation);
819         }
820     }
821 
notifyQuickScrubStarted()822     public void notifyQuickScrubStarted() {
823         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
824             mConnectionCallbacks.get(i).onQuickScrubStarted();
825         }
826     }
827 
notifyAssistantProgress(@loatRangefrom = 0.0, to = 1.0) float progress)828     private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
829         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
830             mConnectionCallbacks.get(i).onAssistantProgress(progress);
831         }
832     }
833 
notifyAssistantGestureCompletion(float velocity)834     private void notifyAssistantGestureCompletion(float velocity) {
835         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
836             mConnectionCallbacks.get(i).onAssistantGestureCompletion(velocity);
837         }
838     }
839 
notifyStartAssistant(Bundle bundle)840     private void notifyStartAssistant(Bundle bundle) {
841         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
842             mConnectionCallbacks.get(i).startAssistant(bundle);
843         }
844     }
845 
notifyAssistantVisibilityChanged(float visibility)846     public void notifyAssistantVisibilityChanged(float visibility) {
847         try {
848             if (mOverviewProxy != null) {
849                 mOverviewProxy.onAssistantVisibilityChanged(visibility);
850             } else {
851                 Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility.");
852             }
853         } catch (RemoteException e) {
854             Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e);
855         }
856     }
857 
858     /**
859      * Notifies the Launcher of split screen size changes
860      * @param secondaryWindowBounds Bounds of the secondary window including the insets
861      * @param secondaryWindowInsets stable insets received by the secondary window
862      */
notifySplitScreenBoundsChanged( Rect secondaryWindowBounds, Rect secondaryWindowInsets)863     public void notifySplitScreenBoundsChanged(
864             Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
865         try {
866             if (mOverviewProxy != null) {
867                 mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
868                         secondaryWindowBounds, secondaryWindowInsets);
869             } else {
870                 Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
871             }
872         } catch (RemoteException e) {
873             Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
874         }
875     }
876 
notifyToggleRecentApps()877     void notifyToggleRecentApps() {
878         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
879             mConnectionCallbacks.get(i).onToggleRecentApps();
880         }
881     }
882 
updateEnabledState()883     private void updateEnabledState() {
884         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
885                 MATCH_SYSTEM_ONLY,
886                 ActivityManagerWrapper.getInstance().getCurrentUserId()) != null;
887     }
888 
889     @Override
onNavigationModeChanged(int mode)890     public void onNavigationModeChanged(int mode) {
891         mNavBarMode = mode;
892     }
893 
894     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)895     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
896         pw.println(TAG_OPS + " state:");
897         pw.print("  recentsComponentName="); pw.println(mRecentsComponentName);
898         pw.print("  isConnected="); pw.println(mOverviewProxy != null);
899         pw.print("  connectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
900 
901         pw.print("  quickStepIntent="); pw.println(mQuickStepIntent);
902         pw.print("  quickStepIntentResolved="); pw.println(isEnabled());
903         mSysUiState.dump(fd, pw, args);
904         pw.print(" mInputFocusTransferStarted="); pw.println(mInputFocusTransferStarted);
905     }
906 
907     public interface OverviewProxyListener {
onConnectionChanged(boolean isConnected)908         default void onConnectionChanged(boolean isConnected) {}
onQuickStepStarted()909         default void onQuickStepStarted() {}
onQuickSwitchToNewTask(@urface.Rotation int rotation)910         default void onQuickSwitchToNewTask(@Surface.Rotation int rotation) {}
onOverviewShown(boolean fromHome)911         default void onOverviewShown(boolean fromHome) {}
onQuickScrubStarted()912         default void onQuickScrubStarted() {}
913         /** Notify the recents app (overview) is started by 3-button navigation. */
onToggleRecentApps()914         default void onToggleRecentApps() {}
915         /** Notify changes in the nav bar button alpha */
onNavBarButtonAlphaChanged(float alpha, boolean animate)916         default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
onSystemUiStateChanged(int sysuiStateFlags)917         default void onSystemUiStateChanged(int sysuiStateFlags) {}
onAssistantProgress(@loatRangefrom = 0.0, to = 1.0) float progress)918         default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
onAssistantGestureCompletion(float velocity)919         default void onAssistantGestureCompletion(float velocity) {}
startAssistant(Bundle bundle)920         default void startAssistant(Bundle bundle) {}
921     }
922 }
923