• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.statusbar.phone;
16 
17 import static android.content.Intent.ACTION_DEVICE_LOCKED_CHANGED;
18 
19 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
20 
21 import android.app.ActivityManager;
22 import android.app.KeyguardManager;
23 import android.app.PendingIntent;
24 import android.app.StatusBarManager;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.IntentSender;
30 import android.os.RemoteException;
31 import android.os.UserHandle;
32 import android.view.View;
33 import android.view.ViewParent;
34 
35 import androidx.annotation.Nullable;
36 
37 import com.android.compose.animation.scene.ObservableTransitionState;
38 import com.android.systemui.ActivityIntentHelper;
39 import com.android.systemui.dagger.SysUISingleton;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
42 import com.android.systemui.plugins.ActivityStarter;
43 import com.android.systemui.plugins.statusbar.StatusBarStateController;
44 import com.android.systemui.scene.domain.interactor.SceneInteractor;
45 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
46 import com.android.systemui.shade.ShadeController;
47 import com.android.systemui.statusbar.ActionClickLogger;
48 import com.android.systemui.statusbar.CommandQueue;
49 import com.android.systemui.statusbar.CommandQueue.Callbacks;
50 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
51 import com.android.systemui.statusbar.NotificationRemoteInputManager;
52 import com.android.systemui.statusbar.NotificationRemoteInputManager.Callback;
53 import com.android.systemui.statusbar.StatusBarState;
54 import com.android.systemui.statusbar.SysuiStatusBarStateController;
55 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
56 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
57 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
58 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
59 import com.android.systemui.statusbar.policy.KeyguardStateController;
60 import com.android.systemui.util.kotlin.JavaAdapter;
61 
62 import dagger.Lazy;
63 
64 import java.util.concurrent.Executor;
65 
66 import javax.inject.Inject;
67 
68 /**
69  */
70 @SysUISingleton
71 public class StatusBarRemoteInputCallback implements Callback, Callbacks,
72         StatusBarStateController.StateListener {
73 
74     private final KeyguardStateController mKeyguardStateController;
75     private final SysuiStatusBarStateController mStatusBarStateController;
76     private final NotificationLockscreenUserManager mLockscreenUserManager;
77     private final ActivityStarter mActivityStarter;
78     private final Context mContext;
79     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
80     private final com.android.systemui.shade.ShadeController mShadeController;
81     private Executor mExecutor;
82     private final ActivityIntentHelper mActivityIntentHelper;
83     private final GroupExpansionManager mGroupExpansionManager;
84     private View mPendingWorkRemoteInputView;
85     private View mPendingRemoteInputView;
86     private KeyguardManager mKeyguardManager;
87     private final CommandQueue mCommandQueue;
88     private final ActionClickLogger mActionClickLogger;
89     private int mDisabled2;
90     protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
91     private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
92     private final Lazy<SceneInteractor> mSceneInteractorLazy;
93 
94     /**
95      */
96     @Inject
StatusBarRemoteInputCallback( Context context, GroupExpansionManager groupExpansionManager, NotificationLockscreenUserManager notificationLockscreenUserManager, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, ActivityStarter activityStarter, ShadeController shadeController, CommandQueue commandQueue, ActionClickLogger clickLogger, @Main Executor executor, Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy, Lazy<SceneInteractor> sceneInteractorLazy, JavaAdapter javaAdapter)97     public StatusBarRemoteInputCallback(
98             Context context,
99             GroupExpansionManager groupExpansionManager,
100             NotificationLockscreenUserManager notificationLockscreenUserManager,
101             KeyguardStateController keyguardStateController,
102             StatusBarStateController statusBarStateController,
103             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
104             ActivityStarter activityStarter,
105             ShadeController shadeController,
106             CommandQueue commandQueue,
107             ActionClickLogger clickLogger,
108             @Main Executor executor,
109             Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
110             Lazy<SceneInteractor> sceneInteractorLazy,
111             JavaAdapter javaAdapter) {
112         mContext = context;
113         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
114         mShadeController = shadeController;
115         mExecutor = executor;
116         mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
117                 new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
118         mLockscreenUserManager = notificationLockscreenUserManager;
119         mKeyguardStateController = keyguardStateController;
120         mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
121         mActivityStarter = activityStarter;
122         mStatusBarStateController.addCallback(this);
123         mKeyguardManager = context.getSystemService(KeyguardManager.class);
124         mCommandQueue = commandQueue;
125         mCommandQueue.addCallback(this);
126         mActionClickLogger = clickLogger;
127         mActivityIntentHelper = new ActivityIntentHelper(mContext);
128         mGroupExpansionManager = groupExpansionManager;
129         mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
130         mSceneInteractorLazy = sceneInteractorLazy;
131 
132         if (SceneContainerFlag.isEnabled()) {
133             javaAdapter.alwaysCollectFlow(
134                     mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
135                     deviceUnlockStatus -> onStateChanged(mStatusBarStateController.getState()));
136             javaAdapter.alwaysCollectFlow(
137                     mSceneInteractorLazy.get().getTransitionState(),
138                     deviceUnlockStatus -> onStateChanged(mStatusBarStateController.getState()));
139         }
140     }
141 
142     @Override
onStateChanged(int state)143     public void onStateChanged(int state) {
144         if (mPendingRemoteInputView == null) {
145             return;
146         }
147 
148         if (state == StatusBarState.SHADE && canRetryPendingRemoteInput()) {
149             mExecutor.execute(mPendingRemoteInputView::callOnClick);
150             mPendingRemoteInputView = null;
151         }
152     }
153 
154     @Override
onLockedRemoteInput(ExpandableNotificationRow row, View clicked)155     public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
156         if (!row.isPinned()) {
157             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
158         }
159         mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */,
160                 "StatusBarRemoteInputCallback#onLockedRemoteInput");
161         mPendingRemoteInputView = clicked;
162     }
163 
onWorkChallengeChanged()164     protected void onWorkChallengeChanged() {
165         mLockscreenUserManager.updatePublicMode();
166         if (mPendingWorkRemoteInputView != null
167                 && !mLockscreenUserManager.isAnyProfilePublicMode()) {
168             // Expand notification panel and the notification row, then click on remote input view
169             final Runnable clickPendingViewRunnable = () -> {
170                 final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
171                 if (pendingWorkRemoteInputView == null) {
172                     return;
173                 }
174 
175                 // Climb up the hierarchy until we get to the container for this row.
176                 ViewParent p = pendingWorkRemoteInputView.getParent();
177                 while (!(p instanceof ExpandableNotificationRow)) {
178                     if (p == null) {
179                         return;
180                     }
181                     p = p.getParent();
182                 }
183 
184                 final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
185                 ViewParent viewParent = row.getParent();
186                 if (viewParent instanceof NotificationStackScrollLayout) {
187                     final NotificationStackScrollLayout scrollLayout =
188                             (NotificationStackScrollLayout) viewParent;
189                     row.makeActionsVisibile();
190                     row.post(() -> {
191                         final Runnable finishScrollingCallback = () -> {
192                             mPendingWorkRemoteInputView.callOnClick();
193                             mPendingWorkRemoteInputView = null;
194                             scrollLayout.setFinishScrollingCallback(null);
195                         };
196                         if (scrollLayout.scrollTo(row)) {
197                             // It scrolls! So call it when it's finished.
198                             scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
199                         } else {
200                             // It does not scroll, so call it now!
201                             finishScrollingCallback.run();
202                         }
203                     });
204                 }
205             };
206             mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
207             mShadeController.instantExpandShade();
208         }
209     }
210 
211     @Override
onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView, boolean deferBouncer, Runnable runnable)212     public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
213             View clickedView, boolean deferBouncer, Runnable runnable) {
214         if (!deferBouncer && mKeyguardStateController.isShowing()) {
215             onLockedRemoteInput(row, clickedView);
216         } else {
217             if (ExpandHeadsUpOnInlineReply.isEnabled()) {
218                 if (row.isChildInGroup() && !row.areChildrenExpanded()) {
219                     // The group isn't expanded, let's make sure it's visible!
220                     if (NotificationBundleUi.isEnabled()) {
221                         mGroupExpansionManager.toggleGroupExpansion(row.getEntryAdapter());
222                     } else {
223                         mGroupExpansionManager.toggleGroupExpansion(row.getEntryLegacy());
224                     }
225                 } else if (!row.isChildInGroup()) {
226                     final boolean expandNotification;
227                     if (row.isPinned()) {
228                         expandNotification = !row.isPinnedAndExpanded();
229                     } else {
230                         expandNotification = !row.isExpanded();
231                     }
232 
233                     if (expandNotification) {
234                         // notification isn't expanded, let's make sure it's expanded!
235                         row.toggleExpansionState();
236                         row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
237                     }
238                 }
239             } else {
240                 if (row.isChildInGroup() && !row.areChildrenExpanded()) {
241                     // The group isn't expanded, let's make sure it's visible!
242                     if (NotificationBundleUi.isEnabled()) {
243                         mGroupExpansionManager.toggleGroupExpansion(row.getEntryAdapter());
244                     } else {
245                         mGroupExpansionManager.toggleGroupExpansion(row.getEntryLegacy());
246                     }
247                 }
248 
249                 if (android.app.Flags.compactHeadsUpNotificationReply()
250                         && row.isCompactConversationHeadsUpOnScreen()) {
251                     // Notification can be system expanded true and it is set user expanded in
252                     // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType
253                     // doesn't change. To expand huning notification properly,
254                     // we need set userExpanded false.
255                     if (!row.isPinned() && row.isExpanded()) {
256                         row.setUserExpanded(false);
257                     }
258                     // expand notification emits expanded information to HUN listener.
259                     row.expandNotification();
260                 } else {
261                     // TODO(b/346976443) Group and normal notification expansions are two different
262                     // concepts. We should never call setUserExpanded for expanding groups.
263 
264                     // Note: Since Normal HUN has remote input view in it, we don't expect to hit
265                     // onMakeExpandedVisibleForRemoteInput from activateRemoteInput for Normal HUN.
266                     row.setUserExpanded(true);
267                 }
268                 row.getPrivateLayout().setOnExpandedVisibleListener(runnable);
269             }
270         }
271     }
272 
273     @Override
onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked)274     public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
275             View clicked) {
276         // Collapse notification and show work challenge
277         mCommandQueue.animateCollapsePanels();
278         startWorkChallengeIfNecessary(userId, null, null);
279         // Add pending remote input view after starting work challenge, as starting work challenge
280         // will clear all previous pending review view
281         mPendingWorkRemoteInputView = clicked;
282     }
283 
startWorkChallengeIfNecessary(int userId, IntentSender intendSender, String notificationKey)284     boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
285             String notificationKey) {
286         // Clear pending remote view, as we do not want to trigger pending remote input view when
287         // it's called by other code
288         mPendingWorkRemoteInputView = null;
289         // Begin old BaseStatusBar.startWorkChallengeIfNecessary.
290         final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
291                 null, userId);
292         if (newIntent == null) {
293             return false;
294         }
295         final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
296         callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
297         callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
298         callBackIntent.setPackage(mContext.getPackageName());
299 
300         PendingIntent callBackPendingIntent = PendingIntent.getBroadcast(
301                 mContext,
302                 0,
303                 callBackIntent,
304                 PendingIntent.FLAG_CANCEL_CURRENT |
305                         PendingIntent.FLAG_ONE_SHOT |
306                         PendingIntent.FLAG_IMMUTABLE);
307         newIntent.putExtra(
308                 Intent.EXTRA_INTENT,
309                 callBackPendingIntent.getIntentSender());
310         try {
311             ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
312                     null /*options*/);
313         } catch (RemoteException ex) {
314             // ignore
315         }
316         return true;
317         // End old BaseStatusBar.startWorkChallengeIfNecessary.
318     }
319 
320     @Override
shouldHandleRemoteInput(View view, PendingIntent pendingIntent)321     public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) {
322         // Skip remote input as doing so will expand the notification shade.
323         return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
324     }
325 
326     @Override
handleRemoteViewClick(View view, PendingIntent pendingIntent, boolean appRequestedAuth, @Nullable Integer actionIndex, NotificationRemoteInputManager.ClickHandler defaultHandler)327     public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
328             boolean appRequestedAuth, @Nullable Integer actionIndex,
329             NotificationRemoteInputManager.ClickHandler defaultHandler) {
330         final boolean isActivity = pendingIntent.isActivity();
331         if (isActivity || appRequestedAuth) {
332             mActionClickLogger.logWaitingToCloseKeyguard(pendingIntent, actionIndex);
333             final boolean afterKeyguardGone = mActivityIntentHelper
334                     .wouldPendingLaunchResolverActivity(pendingIntent,
335                             mLockscreenUserManager.getCurrentUserId());
336             mActivityStarter.dismissKeyguardThenExecute(() -> {
337                 mActionClickLogger.logKeyguardGone(pendingIntent, actionIndex);
338 
339                 try {
340                     ActivityManager.getService().resumeAppSwitches();
341                 } catch (RemoteException e) {
342                 }
343 
344                 boolean handled = defaultHandler.handleClick();
345 
346                 // close the shade if it was open and maybe wait for activity start.
347                 return handled && mShadeController.closeShadeIfOpen();
348             }, null, afterKeyguardGone);
349             return true;
350         } else {
351             return defaultHandler.handleClick();
352         }
353     }
354 
355     @Override
disable(int displayId, int state1, int state2, boolean animate)356     public void disable(int displayId, int state1, int state2, boolean animate) {
357         if (displayId == mContext.getDisplayId()) {
358             mDisabled2 = state2;
359         }
360     }
361 
362     /**
363      * Returns {@code true} if it is safe to retry a pending remote input. The exact criteria for
364      * this vary depending whether the scene container is enabled.
365      */
canRetryPendingRemoteInput()366     private boolean canRetryPendingRemoteInput() {
367         if (SceneContainerFlag.isEnabled()) {
368             final boolean isUnlocked = mDeviceUnlockedInteractorLazy.get()
369                     .getDeviceUnlockStatus().getValue().isUnlocked();
370             final boolean isIdle = mSceneInteractorLazy.get()
371                     .getTransitionState().getValue() instanceof ObservableTransitionState.Idle;
372             return isUnlocked && isIdle;
373         } else {
374             return mKeyguardStateController.isUnlocked()
375                     && !mStatusBarStateController.isKeyguardRequested();
376         }
377     }
378 
379     protected class ChallengeReceiver extends BroadcastReceiver {
380         @Override
onReceive(Context context, Intent intent)381         public void onReceive(Context context, Intent intent) {
382             final String action = intent.getAction();
383             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
384             if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) {
385                 if (userId != mLockscreenUserManager.getCurrentUserId()
386                         && mLockscreenUserManager.isCurrentProfile(userId)) {
387                     onWorkChallengeChanged();
388                 }
389             }
390         }
391     };
392 }
393