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