blob: afe1f4696633f689c4e307aa54cd12835b61e34c [file] [log] [blame]
Mady Mellordea7ecf2018-12-10 15:47:40 -08001/*
2 * Copyright (C) 2018 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
17package com.android.systemui.bubbles;
18
Mady Mellor3dff9e62019-02-05 18:12:53 -080019import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
20import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
21import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
22import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS;
Mady Mellor390bff42019-04-05 15:09:01 -070023import static android.view.Display.INVALID_DISPLAY;
Mady Mellor3dff9e62019-02-05 18:12:53 -080024
Mady Mellordea7ecf2018-12-10 15:47:40 -080025import android.annotation.Nullable;
Mady Mellor60101c92019-04-11 19:04:00 -070026import android.app.ActivityOptions;
Mady Mellor3dff9e62019-02-05 18:12:53 -080027import android.app.ActivityView;
Mady Mellore8e07712019-01-23 12:45:33 -080028import android.app.INotificationManager;
Mady Mellor9801e852019-01-22 14:50:28 -080029import android.app.Notification;
30import android.app.PendingIntent;
Mady Mellordea7ecf2018-12-10 15:47:40 -080031import android.content.Context;
Mady Mellor9801e852019-01-22 14:50:28 -080032import android.content.Intent;
Mady Mellor3dff9e62019-02-05 18:12:53 -080033import android.content.pm.ActivityInfo;
Mady Mellor9801e852019-01-22 14:50:28 -080034import android.content.pm.ApplicationInfo;
35import android.content.pm.PackageManager;
Mady Mellordea7ecf2018-12-10 15:47:40 -080036import android.content.res.Resources;
Mady Mellordd497052019-01-30 17:23:48 -080037import android.content.res.TypedArray;
Mady Mellordea7ecf2018-12-10 15:47:40 -080038import android.graphics.Color;
Mady Mellor3dff9e62019-02-05 18:12:53 -080039import android.graphics.Insets;
40import android.graphics.Point;
Mady Mellore8e07712019-01-23 12:45:33 -080041import android.graphics.drawable.Drawable;
Mady Mellordea7ecf2018-12-10 15:47:40 -080042import android.graphics.drawable.ShapeDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080043import android.os.ServiceManager;
Mady Mellor7af771a2019-03-07 15:04:54 -080044import android.os.UserHandle;
Mady Mellor9801e852019-01-22 14:50:28 -080045import android.provider.Settings;
Steven Wub00225b2019-02-08 14:27:42 -050046import android.service.notification.StatusBarNotification;
Mady Mellordea7ecf2018-12-10 15:47:40 -080047import android.util.AttributeSet;
Mady Mellor9801e852019-01-22 14:50:28 -080048import android.util.Log;
Steven Wub00225b2019-02-08 14:27:42 -050049import android.util.StatsLog;
Mady Mellordea7ecf2018-12-10 15:47:40 -080050import android.view.View;
Mady Mellor5029fa62019-03-05 12:16:21 -080051import android.view.ViewGroup;
Mady Mellor3dff9e62019-02-05 18:12:53 -080052import android.view.WindowInsets;
Mady Mellordea7ecf2018-12-10 15:47:40 -080053import android.widget.LinearLayout;
54
Mark Renouf34d04f32019-05-13 15:53:18 -040055import com.android.internal.policy.ScreenDecorationsUtils;
Mady Mellor3dff9e62019-02-05 18:12:53 -080056import com.android.systemui.Dependency;
Mady Mellordea7ecf2018-12-10 15:47:40 -080057import com.android.systemui.R;
58import com.android.systemui.recents.TriangleShape;
Lyn Han754e77b2019-04-30 14:34:49 -070059import com.android.systemui.statusbar.AlphaOptimizedButton;
Mady Mellor9801e852019-01-22 14:50:28 -080060import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080061import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
62import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080063
64/**
Lyn Han02cca812019-04-02 16:27:32 -070065 * Container for the expanded bubble view, handles rendering the caret and settings icon.
Mady Mellordea7ecf2018-12-10 15:47:40 -080066 */
Mady Mellor3d82e682019-02-05 13:34:48 -080067public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080068 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080069
70 // The triangle pointing to the expanded view
71 private View mPointerView;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080072 private int mPointerMargin;
Mady Mellore8e07712019-01-23 12:45:33 -080073
Lyn Han754e77b2019-04-30 14:34:49 -070074 private AlphaOptimizedButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080075
Mady Mellor3dff9e62019-02-05 18:12:53 -080076 // Views for expanded state
77 private ExpandableNotificationRow mNotifRow;
78 private ActivityView mActivityView;
79
80 private boolean mActivityViewReady = false;
81 private PendingIntent mBubbleIntent;
82
Mady Mellor5d8f1402019-02-21 18:23:52 -080083 private boolean mKeyboardVisible;
84 private boolean mNeedsNewHeight;
85
Mady Mellorfe7ec032019-01-30 17:32:49 -080086 private int mMinHeight;
Lyn Han02cca812019-04-02 16:27:32 -070087 private int mSettingsIconHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080088 private int mBubbleHeight;
Lyn Han02cca812019-04-02 16:27:32 -070089 private int mPointerWidth;
90 private int mPointerHeight;
Mark Renouf34d04f32019-05-13 15:53:18 -040091 private ShapeDrawable mPointerDrawable;
Mady Mellordea7ecf2018-12-10 15:47:40 -080092
Mady Mellor9801e852019-01-22 14:50:28 -080093 private NotificationEntry mEntry;
94 private PackageManager mPm;
95 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -080096 private Drawable mAppIcon;
97
98 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -080099 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -0800100
Mady Mellor9801e852019-01-22 14:50:28 -0800101 private BubbleStackView mStackView;
102
Mady Mellor3dff9e62019-02-05 18:12:53 -0800103 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
104
105 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
106 @Override
107 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800108 if (!mActivityViewReady) {
109 mActivityViewReady = true;
Mady Mellor60101c92019-04-11 19:04:00 -0700110 // Custom options so there is no activity transition animation
111 ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
112 0 /* enterResId */, 0 /* exitResId */);
113 // Post to keep the lifecycle normal
114 post(() -> mActivityView.startActivity(mBubbleIntent, options));
Mady Mellor6d002032019-02-13 13:45:17 -0800115 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800116 }
117
118 @Override
119 public void onActivityViewDestroyed(ActivityView view) {
120 mActivityViewReady = false;
121 }
122
123 /**
124 * This is only called for tasks on this ActivityView, which is also set to
125 * single-task mode -- meaning never more than one task on this display. If a task
126 * is being removed, it's the top Activity finishing and this bubble should
127 * be removed or collapsed.
128 */
129 @Override
130 public void onTaskRemovalStarted(int taskId) {
131 if (mEntry != null) {
132 // Must post because this is called from a binder thread.
Mark Renouf08bc42a2019-03-07 13:01:59 -0500133 post(() -> mBubbleController.removeBubble(mEntry.key,
134 BubbleController.DISMISS_TASK_FINISHED));
Mady Mellor3dff9e62019-02-05 18:12:53 -0800135 }
136 }
137 };
Mady Mellore8e07712019-01-23 12:45:33 -0800138
Mady Mellor3d82e682019-02-05 13:34:48 -0800139 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800140 this(context, null);
141 }
142
Mady Mellor3d82e682019-02-05 13:34:48 -0800143 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800144 this(context, attrs, 0);
145 }
146
Mady Mellor3d82e682019-02-05 13:34:48 -0800147 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800148 this(context, attrs, defStyleAttr, 0);
149 }
150
Mady Mellor3d82e682019-02-05 13:34:48 -0800151 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800152 int defStyleRes) {
153 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800154 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800155 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800156 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800157 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800158 try {
159 mNotificationManagerService = INotificationManager.Stub.asInterface(
160 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
161 } catch (ServiceManager.ServiceNotFoundException e) {
162 Log.w(TAG, e);
163 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800164 }
165
166 @Override
167 protected void onFinishInflate() {
168 super.onFinishInflate();
169
170 Resources res = getResources();
171 mPointerView = findViewById(R.id.pointer_view);
Lyn Han02cca812019-04-02 16:27:32 -0700172 mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
173 mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800174
Mady Mellordd497052019-01-30 17:23:48 -0800175
Mark Renouf34d04f32019-05-13 15:53:18 -0400176 mPointerDrawable = new ShapeDrawable(TriangleShape.create(
Lyn Han02cca812019-04-02 16:27:32 -0700177 mPointerWidth, mPointerHeight, false /* pointUp */));
Mark Renouf34d04f32019-05-13 15:53:18 -0400178 mPointerView.setBackground(mPointerDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800179
Lyn Han02cca812019-04-02 16:27:32 -0700180 mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
Mady Mellorfe7ec032019-01-30 17:32:49 -0800181 R.dimen.bubble_expanded_header_height);
Lyn Han02cca812019-04-02 16:27:32 -0700182 mSettingsIcon = findViewById(R.id.settings_button);
Lyn Han02cca812019-04-02 16:27:32 -0700183 mSettingsIcon.setOnClickListener(this);
Lyn Han02cca812019-04-02 16:27:32 -0700184
Mady Mellor3dff9e62019-02-05 18:12:53 -0800185 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
186 true /* singleTaskInstance */);
187 addView(mActivityView);
188
Mady Mellor52b1ac62019-04-10 16:59:03 -0700189 // Make sure pointer is below activity view
190 bringChildToFront(mPointerView);
191
Mark Renouf34d04f32019-05-13 15:53:18 -0400192 applyThemeAttrs();
193
Mady Mellor5d8f1402019-02-21 18:23:52 -0800194 setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
195 // Keep track of IME displaying because we should not make any adjustments that might
196 // cause a config change while the IME is displayed otherwise it'll loose focus.
Mady Mellor3dff9e62019-02-05 18:12:53 -0800197 final int keyboardHeight = insets.getSystemWindowInsetBottom()
198 - insets.getStableInsetBottom();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800199 mKeyboardVisible = keyboardHeight != 0;
200 if (!mKeyboardVisible && mNeedsNewHeight) {
201 updateHeight();
202 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800203 return view.onApplyWindowInsets(insets);
204 });
Mady Mellore8e07712019-01-23 12:45:33 -0800205 }
206
Mark Renouf34d04f32019-05-13 15:53:18 -0400207 void applyThemeAttrs() {
208 TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView);
209 int bgColor = ta.getColor(
210 R.styleable.BubbleExpandedView_android_colorBackgroundFloating, Color.WHITE);
211 float cornerRadius = ta.getDimension(
212 R.styleable.BubbleExpandedView_android_dialogCornerRadius, 0);
213 ta.recycle();
214
215 // Update triangle color.
216 mPointerDrawable.setTint(bgColor);
217
218 // Update ActivityView cornerRadius
219 if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) {
220 mActivityView.setCornerRadius(cornerRadius);
221 }
222 }
223
Mady Mellor5d8f1402019-02-21 18:23:52 -0800224 @Override
225 protected void onDetachedFromWindow() {
226 super.onDetachedFromWindow();
227 mKeyboardVisible = false;
228 mNeedsNewHeight = false;
229 if (mActivityView != null) {
230 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
231 }
232 }
233
234 /**
235 * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
236 * This should be done post-move and post-animation.
237 */
238 void updateInsets(WindowInsets insets) {
239 if (usingActivityView()) {
240 Point displaySize = new Point();
241 mActivityView.getContext().getDisplay().getSize(displaySize);
242 int[] windowLocation = mActivityView.getLocationOnScreen();
243 final int windowBottom = windowLocation[1] + mActivityView.getHeight();
244 final int keyboardHeight = insets.getSystemWindowInsetBottom()
245 - insets.getStableInsetBottom();
246 final int insetsBottom = Math.max(0,
247 windowBottom + keyboardHeight - displaySize.y);
248 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
249 }
250 }
251
Mady Mellore8e07712019-01-23 12:45:33 -0800252 /**
253 * Sets the listener to notify when a bubble has been blocked.
254 */
255 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
256 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800257 }
258
259 /**
260 * Sets the notification entry used to populate this view.
261 */
262 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
263 mStackView = stackView;
264 mEntry = entry;
265
266 ApplicationInfo info;
267 try {
268 info = mPm.getApplicationInfo(
269 entry.notification.getPackageName(),
270 PackageManager.MATCH_UNINSTALLED_PACKAGES
271 | PackageManager.MATCH_DISABLED_COMPONENTS
272 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
273 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
274 if (info != null) {
275 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800276 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800277 }
278 } catch (PackageManager.NameNotFoundException e) {
279 // Ahh... just use package name
280 mAppName = entry.notification.getPackageName();
281 }
Mady Mellore8e07712019-01-23 12:45:33 -0800282 if (mAppIcon == null) {
283 mAppIcon = mPm.getDefaultActivityIcon();
284 }
Mark Renouf34d04f32019-05-13 15:53:18 -0400285 applyThemeAttrs();
Lyn Han69149122019-04-30 12:03:12 -0700286 showSettingsIcon();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800287 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800288 }
289
290 /**
291 * Lets activity view know it should be shown / populated.
292 */
Mady Mellor5029fa62019-03-05 12:16:21 -0800293 public void populateExpandedView() {
294 if (usingActivityView()) {
295 mActivityView.setCallback(mStateCallback);
296 } else {
297 // We're using notification template
298 ViewGroup parent = (ViewGroup) mNotifRow.getParent();
299 if (parent == this) {
300 // Already added
301 return;
302 } else if (parent != null) {
303 // Still in the shade... remove it
304 parent.removeView(mNotifRow);
305 }
Lyn Han3f5c3a42019-04-01 15:59:56 -0700306 addView(mNotifRow, 1 /* index */);
Mady Mellor5029fa62019-03-05 12:16:21 -0800307 }
Mady Mellor9801e852019-01-22 14:50:28 -0800308 }
309
Mady Mellorfe7ec032019-01-30 17:32:49 -0800310 /**
311 * Updates the entry backing this view. This will not re-populate ActivityView, it will
Lyn Han02cca812019-04-02 16:27:32 -0700312 * only update the deep-links in the title, and the height of the view.
Mady Mellorfe7ec032019-01-30 17:32:49 -0800313 */
314 public void update(NotificationEntry entry) {
315 if (entry.key.equals(mEntry.key)) {
316 mEntry = entry;
Lyn Han02cca812019-04-02 16:27:32 -0700317 updateSettingsContentDescription();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800318 updateHeight();
319 } else {
320 Log.w(TAG, "Trying to update entry with different key, new entry: "
321 + entry.key + " old entry: " + mEntry.key);
322 }
323 }
324
Mady Mellor3dff9e62019-02-05 18:12:53 -0800325 private void updateExpandedView() {
326 mBubbleIntent = getBubbleIntent(mEntry);
327 if (mBubbleIntent != null) {
328 if (mNotifRow != null) {
329 // Clear out the row if we had it previously
330 removeView(mNotifRow);
331 mNotifRow = null;
332 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800333 mActivityView.setVisibility(VISIBLE);
334 } else {
335 // Hide activity view if we had it previously
336 mActivityView.setVisibility(GONE);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800337 mNotifRow = mEntry.getRow();
Mady Mellor5029fa62019-03-05 12:16:21 -0800338
Mady Mellor3dff9e62019-02-05 18:12:53 -0800339 }
340 updateView();
341 }
342
Mark Renouf041d7262019-02-06 12:09:41 -0500343 boolean performBackPressIfNeeded() {
Mady Mellor323fb0b2019-03-25 12:15:22 -0700344 if (!usingActivityView()) {
Mark Renouf041d7262019-02-06 12:09:41 -0500345 return false;
346 }
347 mActivityView.performBackPress();
348 return true;
349 }
350
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800351 /**
352 * @return total height that the expanded view occupies.
353 */
354 int getExpandedSize() {
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800355 return mBubbleHeight + mPointerView.getHeight() + mPointerMargin
Lyn Han69149122019-04-30 12:03:12 -0700356 + mSettingsIconHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800357 }
358
Mady Mellorfe7ec032019-01-30 17:32:49 -0800359 void updateHeight() {
360 if (usingActivityView()) {
361 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
Mady Mellor7af771a2019-03-07 15:04:54 -0800362 float desiredHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800363 if (data == null) {
364 // This is a contentIntent based bubble, lets allow it to be the max height
365 // as it was forced into this mode and not prepared to be small
366 desiredHeight = mStackView.getMaxExpandedHeight();
367 } else {
Mady Mellor7af771a2019-03-07 15:04:54 -0800368 boolean useRes = data.getDesiredHeightResId() != 0;
369 float desiredPx;
370 if (useRes) {
371 desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
372 mEntry.notification.getPackageName(),
373 mEntry.notification.getUser().getIdentifier());
374 } else {
375 desiredPx = data.getDesiredHeight()
376 * getContext().getResources().getDisplayMetrics().density;
377 }
378 desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800379 }
Lyn Han69149122019-04-30 12:03:12 -0700380 int max = mStackView.getMaxExpandedHeight() - mSettingsIconHeight
381 - mPointerView.getHeight() - mPointerMargin;
Mady Mellor7af771a2019-03-07 15:04:54 -0800382 float height = Math.min(desiredHeight, max);
Mady Mellorfe7ec032019-01-30 17:32:49 -0800383 height = Math.max(height, mMinHeight);
384 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800385 mNeedsNewHeight = lp.height != height;
386 if (!mKeyboardVisible) {
387 // If the keyboard is visible... don't adjust the height because that will cause
388 // a configuration change and the keyboard will be lost.
Mady Mellor7af771a2019-03-07 15:04:54 -0800389 lp.height = (int) height;
390 mBubbleHeight = (int) height;
Mady Mellor5d8f1402019-02-21 18:23:52 -0800391 mActivityView.setLayoutParams(lp);
392 mNeedsNewHeight = false;
393 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800394 } else {
395 mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800396 }
397 }
398
Mady Mellor9801e852019-01-22 14:50:28 -0800399 @Override
400 public void onClick(View view) {
401 if (mEntry == null) {
402 return;
403 }
404 Notification n = mEntry.notification.getNotification();
405 int id = view.getId();
Lyn Hanc26ff122019-03-29 16:46:07 -0700406 if (id == R.id.settings_button) {
Mady Mellor9801e852019-01-22 14:50:28 -0800407 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
408 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500409 mStackView.collapseStack(() -> {
410 mContext.startActivity(intent);
Steven Wu45e38ae2019-03-25 16:16:59 -0400411 logBubbleClickEvent(mEntry,
Steven Wub00225b2019-02-08 14:27:42 -0500412 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
413 });
Mady Mellor9801e852019-01-22 14:50:28 -0800414 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800415 }
416
Lyn Han02cca812019-04-02 16:27:32 -0700417 private void updateSettingsContentDescription() {
418 mSettingsIcon.setContentDescription(getResources().getString(
419 R.string.bubbles_settings_button_description, mAppName));
420 }
421
Lyn Hanc26ff122019-03-29 16:46:07 -0700422 void showSettingsIcon() {
Lyn Han02cca812019-04-02 16:27:32 -0700423 updateSettingsContentDescription();
Lyn Hanc26ff122019-03-29 16:46:07 -0700424 mSettingsIcon.setVisibility(VISIBLE);
425 }
426
Mady Mellordea7ecf2018-12-10 15:47:40 -0800427 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800428 * Update appearance of the expanded view being displayed.
429 */
430 public void updateView() {
431 if (usingActivityView()
432 && mActivityView.getVisibility() == VISIBLE
433 && mActivityView.isAttachedToWindow()) {
434 mActivityView.onLocationChanged();
435 } else if (mNotifRow != null) {
436 applyRowState(mNotifRow);
437 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800438 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800439 }
440
441 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800442 * Set the x position that the tip of the triangle should point to.
443 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800444 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800445 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800446 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800447 mPointerView.setTranslationX(x);
448 }
449
450 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800451 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800452 */
Mady Mellor94d94a72019-03-05 18:16:59 -0800453 public void cleanUpExpandedState() {
454 removeView(mNotifRow);
455
Mady Mellor3dff9e62019-02-05 18:12:53 -0800456 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500457 return;
458 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500459 if (mActivityViewReady) {
460 mActivityView.release();
Mady Mellordea7ecf2018-12-10 15:47:40 -0800461 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500462 removeView(mActivityView);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800463 mActivityView = null;
464 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800465 }
466
Mady Mellor3dff9e62019-02-05 18:12:53 -0800467 private boolean usingActivityView() {
Mady Mellor323fb0b2019-03-25 12:15:22 -0700468 return mBubbleIntent != null && mActivityView != null;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800469 }
470
Mady Mellor390bff42019-04-05 15:09:01 -0700471 /**
472 * @return the display id of the virtual display.
473 */
474 public int getVirtualDisplayId() {
475 if (usingActivityView()) {
476 return mActivityView.getVirtualDisplayId();
477 }
478 return INVALID_DISPLAY;
479 }
480
Mady Mellor3dff9e62019-02-05 18:12:53 -0800481 private void applyRowState(ExpandableNotificationRow view) {
482 view.reset();
483 view.setHeadsUp(false);
484 view.resetTranslation();
485 view.setOnKeyguard(false);
486 view.setOnAmbient(false);
487 view.setClipBottomAmount(0);
488 view.setClipTopAmount(0);
489 view.setContentTransformationAmount(0, false);
490 view.setIconsVisible(true);
491
492 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
493 // view.setTopRoundness(1, false);
494 // view.setBottomRoundness(1, false);
495
496 ExpandableViewState viewState = view.getViewState();
497 viewState = viewState == null ? new ExpandableViewState() : viewState;
498 viewState.height = view.getIntrinsicHeight();
499 viewState.gone = false;
500 viewState.hidden = false;
501 viewState.dimmed = false;
502 viewState.dark = false;
503 viewState.alpha = 1f;
504 viewState.notGoneIndex = -1;
505 viewState.xTranslation = 0;
506 viewState.yTranslation = 0;
507 viewState.zTranslation = 0;
508 viewState.scaleX = 1;
509 viewState.scaleY = 1;
510 viewState.inShelf = true;
511 viewState.headsUpIsVisible = false;
512 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800513 }
Mady Mellor9801e852019-01-22 14:50:28 -0800514
515 private Intent getSettingsIntent(String packageName, final int appUid) {
Lyn Han754e77b2019-04-30 14:34:49 -0700516 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
Mady Mellor9801e852019-01-22 14:50:28 -0800517 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
518 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
519 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
520 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Lyn Han754e77b2019-04-30 14:34:49 -0700521 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Mady Mellor9801e852019-01-22 14:50:28 -0800522 return intent;
523 }
Mady Mellore8e07712019-01-23 12:45:33 -0800524
Mady Mellor3dff9e62019-02-05 18:12:53 -0800525 @Nullable
526 private PendingIntent getBubbleIntent(NotificationEntry entry) {
527 Notification notif = entry.notification.getNotification();
528 String packageName = entry.notification.getPackageName();
529 Notification.BubbleMetadata data = notif.getBubbleMetadata();
530 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
531 packageName)) {
532 return data.getIntent();
533 } else if (BubbleController.shouldUseContentIntent(mContext)
534 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
535 packageName)) {
536 return notif.contentIntent;
537 }
538 return null;
539 }
540
541 /**
542 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
543 *
544 * @param intent the pending intent of the bubble.
545 * @param enableLogging whether bubble developer error should be logged.
546 * @param packageName the notification package name for this bubble.
547 * @return
548 */
549 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
550 String packageName) {
551 if (intent == null) {
552 return false;
553 }
554 ActivityInfo info =
555 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
556 if (info == null) {
557 if (enableLogging) {
558 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
559 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
560 }
561 return false;
562 }
563 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
564 if (enableLogging) {
565 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
566 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
567 }
568 return false;
569 }
570 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
571 if (enableLogging) {
572 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
573 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
574 }
575 return false;
576 }
577 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
578 }
579
Mady Mellore8e07712019-01-23 12:45:33 -0800580 /**
581 * Listener that is notified when a bubble is blocked.
582 */
583 public interface OnBubbleBlockedListener {
584 /**
585 * Called when a bubble is blocked for the provided entry.
586 */
587 void onBubbleBlocked(NotificationEntry entry);
588 }
Steven Wub00225b2019-02-08 14:27:42 -0500589
590 /**
591 * Logs bubble UI click event.
592 *
Steven Wu45e38ae2019-03-25 16:16:59 -0400593 * @param entry the bubble notification entry that user is interacting with.
Steven Wub00225b2019-02-08 14:27:42 -0500594 * @param action the user interaction enum.
595 */
Steven Wu45e38ae2019-03-25 16:16:59 -0400596 private void logBubbleClickEvent(NotificationEntry entry, int action) {
597 StatusBarNotification notification = entry.notification;
Steven Wub00225b2019-02-08 14:27:42 -0500598 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
599 notification.getPackageName(),
600 notification.getNotification().getChannelId(),
601 notification.getId(),
602 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
603 mStackView.getBubbleCount(),
604 action,
605 mStackView.getNormalizedXPosition(),
Steven Wu45e38ae2019-03-25 16:16:59 -0400606 mStackView.getNormalizedYPosition(),
Steven Wu8ba8ca92019-04-11 10:47:42 -0400607 entry.showInShadeWhenBubble(),
608 entry.isForegroundService(),
609 BubbleController.isForegroundApp(mContext, notification.getPackageName()));
Steven Wub00225b2019-02-08 14:27:42 -0500610 }
Mady Mellor7af771a2019-03-07 15:04:54 -0800611
612 private int getDimenForPackageUser(int resId, String pkg, int userId) {
613 Resources r;
614 if (pkg != null) {
615 try {
616 if (userId == UserHandle.USER_ALL) {
617 userId = UserHandle.USER_SYSTEM;
618 }
619 r = mPm.getResourcesForApplicationAsUser(pkg, userId);
620 return r.getDimensionPixelSize(resId);
621 } catch (PackageManager.NameNotFoundException ex) {
622 // Uninstalled, don't care
623 } catch (Resources.NotFoundException e) {
624 // Invalid res id, return 0 and user our default
625 Log.e(TAG, "Couldn't find desired height res id", e);
626 }
627 }
628 return 0;
629 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800630}