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.SysUiServiceProvider.getComponent; 20 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION; 21 22 import android.app.ActivityManager; 23 import android.app.KeyguardManager; 24 import android.app.PendingIntent; 25 import android.app.StatusBarManager; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.IntentSender; 31 import android.os.Handler; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.view.View; 35 import android.view.ViewParent; 36 37 import com.android.systemui.ActivityIntentHelper; 38 import com.android.systemui.Dependency; 39 import com.android.systemui.plugins.ActivityStarter; 40 import com.android.systemui.plugins.statusbar.StatusBarStateController; 41 import com.android.systemui.statusbar.CommandQueue; 42 import com.android.systemui.statusbar.CommandQueue.Callbacks; 43 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 44 import com.android.systemui.statusbar.NotificationRemoteInputManager; 45 import com.android.systemui.statusbar.NotificationRemoteInputManager.Callback; 46 import com.android.systemui.statusbar.StatusBarState; 47 import com.android.systemui.statusbar.SysuiStatusBarStateController; 48 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 49 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 50 import com.android.systemui.statusbar.policy.KeyguardMonitor; 51 import com.android.systemui.statusbar.policy.RemoteInputView; 52 53 import javax.inject.Inject; 54 import javax.inject.Singleton; 55 56 /** 57 */ 58 @Singleton 59 public class StatusBarRemoteInputCallback implements Callback, Callbacks, 60 StatusBarStateController.StateListener { 61 62 private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); 63 private final SysuiStatusBarStateController mStatusBarStateController = 64 (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); 65 private final NotificationLockscreenUserManager mLockscreenUserManager = 66 Dependency.get(NotificationLockscreenUserManager.class); 67 private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); 68 private final Context mContext; 69 private final ActivityIntentHelper mActivityIntentHelper; 70 private final NotificationGroupManager mGroupManager; 71 private View mPendingWorkRemoteInputView; 72 private View mPendingRemoteInputView; 73 private final ShadeController mShadeController = Dependency.get(ShadeController.class); 74 private KeyguardManager mKeyguardManager; 75 private final CommandQueue mCommandQueue; 76 private int mDisabled2; 77 protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver(); 78 private Handler mMainHandler = new Handler(); 79 80 /** 81 */ 82 @Inject StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager)83 public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager) { 84 mContext = context; 85 mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL, 86 new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null); 87 mStatusBarStateController.addCallback(this); 88 mKeyguardManager = context.getSystemService(KeyguardManager.class); 89 mCommandQueue = getComponent(context, CommandQueue.class); 90 mCommandQueue.addCallback(this); 91 mActivityIntentHelper = new ActivityIntentHelper(mContext); 92 mGroupManager = groupManager; 93 } 94 95 @Override onStateChanged(int state)96 public void onStateChanged(int state) { 97 boolean hasPendingRemoteInput = mPendingRemoteInputView != null; 98 if (state == StatusBarState.SHADE 99 && (mStatusBarStateController.leaveOpenOnKeyguardHide() || hasPendingRemoteInput)) { 100 if (!mStatusBarStateController.isKeyguardRequested()) { 101 if (hasPendingRemoteInput) { 102 mMainHandler.post(mPendingRemoteInputView::callOnClick); 103 } 104 mPendingRemoteInputView = null; 105 } 106 } 107 } 108 109 @Override onLockedRemoteInput(ExpandableNotificationRow row, View clicked)110 public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) { 111 if (!row.isPinned()) { 112 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 113 } 114 mShadeController.showBouncer(true /* scrimmed */); 115 mPendingRemoteInputView = clicked; 116 } 117 onWorkChallengeChanged()118 protected void onWorkChallengeChanged() { 119 mLockscreenUserManager.updatePublicMode(); 120 if (mPendingWorkRemoteInputView != null 121 && !mLockscreenUserManager.isAnyProfilePublicMode()) { 122 // Expand notification panel and the notification row, then click on remote input view 123 final Runnable clickPendingViewRunnable = () -> { 124 final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView; 125 if (pendingWorkRemoteInputView == null) { 126 return; 127 } 128 129 // Climb up the hierarchy until we get to the container for this row. 130 ViewParent p = pendingWorkRemoteInputView.getParent(); 131 while (!(p instanceof ExpandableNotificationRow)) { 132 if (p == null) { 133 return; 134 } 135 p = p.getParent(); 136 } 137 138 final ExpandableNotificationRow row = (ExpandableNotificationRow) p; 139 ViewParent viewParent = row.getParent(); 140 if (viewParent instanceof NotificationStackScrollLayout) { 141 final NotificationStackScrollLayout scrollLayout = 142 (NotificationStackScrollLayout) viewParent; 143 row.makeActionsVisibile(); 144 row.post(() -> { 145 final Runnable finishScrollingCallback = () -> { 146 mPendingWorkRemoteInputView.callOnClick(); 147 mPendingWorkRemoteInputView = null; 148 scrollLayout.setFinishScrollingCallback(null); 149 }; 150 if (scrollLayout.scrollTo(row)) { 151 // It scrolls! So call it when it's finished. 152 scrollLayout.setFinishScrollingCallback(finishScrollingCallback); 153 } else { 154 // It does not scroll, so call it now! 155 finishScrollingCallback.run(); 156 } 157 }); 158 } 159 }; 160 mShadeController.postOnShadeExpanded(clickPendingViewRunnable); 161 mShadeController.instantExpandNotificationsPanel(); 162 } 163 } 164 165 @Override onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView)166 public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, 167 View clickedView) { 168 if (mKeyguardMonitor.isShowing()) { 169 onLockedRemoteInput(row, clickedView); 170 } else { 171 if (row.isChildInGroup() && !row.areChildrenExpanded()) { 172 // The group isn't expanded, let's make sure it's visible! 173 mGroupManager.toggleGroupExpansion(row.getStatusBarNotification()); 174 } 175 row.setUserExpanded(true); 176 row.getPrivateLayout().setOnExpandedVisibleListener(clickedView::performClick); 177 } 178 } 179 180 @Override onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked)181 public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, 182 View clicked) { 183 // Collapse notification and show work challenge 184 mCommandQueue.animateCollapsePanels(); 185 startWorkChallengeIfNecessary(userId, null, null); 186 // Add pending remote input view after starting work challenge, as starting work challenge 187 // will clear all previous pending review view 188 mPendingWorkRemoteInputView = clicked; 189 } 190 startWorkChallengeIfNecessary(int userId, IntentSender intendSender, String notificationKey)191 boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender, 192 String notificationKey) { 193 // Clear pending remote view, as we do not want to trigger pending remote input view when 194 // it's called by other code 195 mPendingWorkRemoteInputView = null; 196 // Begin old BaseStatusBar.startWorkChallengeIfNecessary. 197 final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, 198 null, userId); 199 if (newIntent == null) { 200 return false; 201 } 202 final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); 203 callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender); 204 callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey); 205 callBackIntent.setPackage(mContext.getPackageName()); 206 207 PendingIntent callBackPendingIntent = PendingIntent.getBroadcast( 208 mContext, 209 0, 210 callBackIntent, 211 PendingIntent.FLAG_CANCEL_CURRENT | 212 PendingIntent.FLAG_ONE_SHOT | 213 PendingIntent.FLAG_IMMUTABLE); 214 newIntent.putExtra( 215 Intent.EXTRA_INTENT, 216 callBackPendingIntent.getIntentSender()); 217 try { 218 ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent, 219 null /*options*/); 220 } catch (RemoteException ex) { 221 // ignore 222 } 223 return true; 224 // End old BaseStatusBar.startWorkChallengeIfNecessary. 225 } 226 227 @Override shouldHandleRemoteInput(View view, PendingIntent pendingIntent)228 public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) { 229 // Skip remote input as doing so will expand the notification shade. 230 return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; 231 } 232 233 @Override handleRemoteViewClick(View view, PendingIntent pendingIntent, NotificationRemoteInputManager.ClickHandler defaultHandler)234 public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, 235 NotificationRemoteInputManager.ClickHandler defaultHandler) { 236 final boolean isActivity = pendingIntent.isActivity(); 237 if (isActivity) { 238 final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity( 239 pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId()); 240 mActivityStarter.dismissKeyguardThenExecute(() -> { 241 try { 242 ActivityManager.getService().resumeAppSwitches(); 243 } catch (RemoteException e) { 244 } 245 246 boolean handled = defaultHandler.handleClick(); 247 248 // close the shade if it was open and maybe wait for activity start. 249 return handled && mShadeController.closeShadeIfOpen(); 250 }, null, afterKeyguardGone); 251 return true; 252 } else { 253 return defaultHandler.handleClick(); 254 } 255 } 256 257 @Override disable(int displayId, int state1, int state2, boolean animate)258 public void disable(int displayId, int state1, int state2, boolean animate) { 259 if (displayId == mContext.getDisplayId()) { 260 mDisabled2 = state2; 261 } 262 } 263 264 protected class ChallengeReceiver extends BroadcastReceiver { 265 @Override onReceive(Context context, Intent intent)266 public void onReceive(Context context, Intent intent) { 267 final String action = intent.getAction(); 268 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); 269 if (Intent.ACTION_DEVICE_LOCKED_CHANGED.equals(action)) { 270 if (userId != mLockscreenUserManager.getCurrentUserId() 271 && mLockscreenUserManager.isCurrentProfile(userId)) { 272 onWorkChallengeChanged(); 273 } 274 } 275 } 276 }; 277 } 278