1 /* 2 * Copyright (C) 2020 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.people.widget; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.LauncherApps; 23 import android.os.Bundle; 24 import android.os.ServiceManager; 25 import android.os.UserHandle; 26 import android.os.UserManager; 27 import android.service.notification.NotificationStats; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.app.UnlaunchableAppActivity; 33 import com.android.internal.logging.UiEventLogger; 34 import com.android.internal.logging.UiEventLoggerImpl; 35 import com.android.internal.statusbar.IStatusBarService; 36 import com.android.internal.statusbar.NotificationVisibility; 37 import com.android.systemui.people.PeopleSpaceUtils; 38 import com.android.systemui.statusbar.CommandQueue; 39 import com.android.systemui.statusbar.notification.NotificationEntryManager; 40 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 41 import com.android.systemui.wmshell.BubblesManager; 42 import com.android.wm.shell.bubbles.Bubble; 43 44 import java.util.Optional; 45 46 import javax.inject.Inject; 47 48 /** Proxy activity to launch ShortcutInfo's conversation. */ 49 public class LaunchConversationActivity extends Activity { 50 private static final String TAG = "PeopleSpaceLaunchConv"; 51 private static final boolean DEBUG = PeopleSpaceUtils.DEBUG; 52 private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); 53 private NotificationEntryManager mNotificationEntryManager; 54 private final Optional<BubblesManager> mBubblesManagerOptional; 55 private final UserManager mUserManager; 56 private boolean mIsForTesting; 57 private IStatusBarService mIStatusBarService; 58 private CommandQueue mCommandQueue; 59 private Bubble mBubble; 60 private NotificationEntry mEntryToBubble; 61 62 @Inject LaunchConversationActivity(NotificationEntryManager notificationEntryManager, Optional<BubblesManager> bubblesManagerOptional, UserManager userManager, CommandQueue commandQueue)63 public LaunchConversationActivity(NotificationEntryManager notificationEntryManager, 64 Optional<BubblesManager> bubblesManagerOptional, UserManager userManager, 65 CommandQueue commandQueue) { 66 super(); 67 mNotificationEntryManager = notificationEntryManager; 68 mBubblesManagerOptional = bubblesManagerOptional; 69 mUserManager = userManager; 70 mCommandQueue = commandQueue; 71 mCommandQueue.addCallback(new CommandQueue.Callbacks() { 72 // (b/190833924) Wait for the app transition to finish before showing the bubble, 73 // opening the bubble while the transition is happening can mess with the placement 74 // of the bubble's surface. 75 @Override 76 public void appTransitionFinished(int displayId) { 77 if (mBubblesManagerOptional.isPresent()) { 78 if (mBubble != null) { 79 mBubblesManagerOptional.get().expandStackAndSelectBubble(mBubble); 80 } else if (mEntryToBubble != null) { 81 mBubblesManagerOptional.get().expandStackAndSelectBubble(mEntryToBubble); 82 } 83 } 84 mCommandQueue.removeCallback(this); 85 } 86 }); 87 } 88 89 @Override onCreate(Bundle savedInstanceState)90 public void onCreate(Bundle savedInstanceState) { 91 if (!mIsForTesting) { 92 super.onCreate(savedInstanceState); 93 } 94 if (DEBUG) Log.d(TAG, "onCreate called"); 95 96 Intent intent = getIntent(); 97 String tileId = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID); 98 String packageName = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME); 99 UserHandle userHandle = intent.getParcelableExtra( 100 PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE); 101 String notificationKey = 102 intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY); 103 104 if (!TextUtils.isEmpty(tileId)) { 105 if (DEBUG) { 106 Log.d(TAG, "Launching conversation with shortcutInfo id " + tileId); 107 } 108 mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_CLICKED); 109 try { 110 111 if (mUserManager.isQuietModeEnabled(userHandle)) { 112 if (DEBUG) Log.d(TAG, "Cannot launch app when quieted"); 113 final Intent dialogIntent = 114 UnlaunchableAppActivity.createInQuietModeDialogIntent( 115 userHandle.getIdentifier()); 116 this.getApplicationContext().startActivity(dialogIntent); 117 finish(); 118 return; 119 } 120 121 // We can potentially bubble without a notification, so rather than rely on 122 // notificationKey here (which could be null if there's no notification or if the 123 // bubble is suppressing the notification), so we'll use the shortcutId for lookups. 124 // This misses one specific case: a bubble that was never opened & still has a 125 // visible notification, but the bubble was dismissed & aged out of the overflow. 126 // So it wouldn't exist in the stack or overflow to be looked up BUT the notif entry 127 // would still exist & be bubbleable. So if we don't get a bubble from the 128 // shortcutId, fallback to notificationKey if it exists. 129 if (mBubblesManagerOptional.isPresent()) { 130 mBubble = mBubblesManagerOptional.get().getBubbleWithShortcutId(tileId); 131 NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif( 132 notificationKey); 133 if (mBubble != null || (entry != null && entry.canBubble())) { 134 mEntryToBubble = entry; 135 if (DEBUG) { 136 Log.d(TAG, 137 "Opening bubble: " + mBubble + ", entry: " + mEntryToBubble); 138 } 139 // Just opt-out and don't cancel the notification for bubbles. 140 finish(); 141 return; 142 } 143 } 144 145 if (mIStatusBarService == null) { 146 mIStatusBarService = IStatusBarService.Stub.asInterface( 147 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 148 } 149 clearNotificationIfPresent(notificationKey, packageName, userHandle); 150 LauncherApps launcherApps = 151 getApplicationContext().getSystemService(LauncherApps.class); 152 launcherApps.startShortcut( 153 packageName, tileId, null, null, userHandle); 154 } catch (Exception e) { 155 Log.e(TAG, "Exception launching shortcut:" + e); 156 } 157 } else { 158 if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo."); 159 } 160 finish(); 161 } 162 clearNotificationIfPresent(String notifKey, String packageName, UserHandle userHandle)163 void clearNotificationIfPresent(String notifKey, String packageName, UserHandle userHandle) { 164 if (TextUtils.isEmpty(notifKey)) { 165 if (DEBUG) Log.d(TAG, "Skipping clear notification: notification key is empty"); 166 return; 167 } 168 169 try { 170 if (mIStatusBarService == null || mNotificationEntryManager == null) { 171 if (DEBUG) { 172 Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey); 173 } 174 return; 175 } 176 177 NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(notifKey); 178 if (entry == null || entry.getRanking() == null) { 179 if (DEBUG) { 180 Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking" 181 + " is null, key: " + notifKey); 182 } 183 return; 184 } 185 186 int count = mNotificationEntryManager.getActiveNotificationsCount(); 187 int rank = entry.getRanking().getRank(); 188 NotificationVisibility notifVisibility = NotificationVisibility.obtain(notifKey, 189 rank, count, true); 190 191 if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank); 192 mIStatusBarService.onNotificationClear( 193 packageName, userHandle.getIdentifier(), notifKey, 194 NotificationStats.DISMISSAL_OTHER, 195 NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility); 196 } catch (Exception e) { 197 Log.e(TAG, "Exception cancelling notification:" + e); 198 } 199 } 200 201 @VisibleForTesting setIsForTesting(boolean isForTesting, IStatusBarService statusBarService)202 void setIsForTesting(boolean isForTesting, IStatusBarService statusBarService) { 203 mIsForTesting = isForTesting; 204 mIStatusBarService = statusBarService; 205 } 206 } 207