blob: 603b3b9766d90eaaf91aa8523650ad1d14e2b85a [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;
23
Mady Mellore8e07712019-01-23 12:45:33 -080024import android.animation.LayoutTransition;
25import android.animation.ObjectAnimator;
Mady Mellordea7ecf2018-12-10 15:47:40 -080026import android.annotation.Nullable;
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 Mellor310c7c62019-02-21 17:03:08 -080042import android.graphics.drawable.GradientDrawable;
Mady Mellordea7ecf2018-12-10 15:47:40 -080043import android.graphics.drawable.ShapeDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080044import android.os.RemoteException;
45import android.os.ServiceManager;
Mady Mellor9801e852019-01-22 14:50:28 -080046import android.provider.Settings;
Steven Wub00225b2019-02-08 14:27:42 -050047import android.service.notification.StatusBarNotification;
Mady Mellordea7ecf2018-12-10 15:47:40 -080048import android.util.AttributeSet;
Mady Mellor9801e852019-01-22 14:50:28 -080049import android.util.Log;
Steven Wub00225b2019-02-08 14:27:42 -050050import android.util.StatsLog;
Mady Mellordea7ecf2018-12-10 15:47:40 -080051import android.view.View;
Mady Mellor3dff9e62019-02-05 18:12:53 -080052import android.view.WindowInsets;
Mady Mellore8e07712019-01-23 12:45:33 -080053import android.widget.FrameLayout;
Mady Mellor9801e852019-01-22 14:50:28 -080054import android.widget.ImageButton;
Mady Mellore8e07712019-01-23 12:45:33 -080055import android.widget.ImageView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080056import android.widget.LinearLayout;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050057import android.widget.TextView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080058
Mady Mellor3dff9e62019-02-05 18:12:53 -080059import com.android.systemui.Dependency;
Mady Mellore8e07712019-01-23 12:45:33 -080060import com.android.systemui.Interpolators;
Mady Mellordea7ecf2018-12-10 15:47:40 -080061import com.android.systemui.R;
62import com.android.systemui.recents.TriangleShape;
Mady Mellor9801e852019-01-22 14:50:28 -080063import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080064import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
65import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080066
67/**
68 * Container for the expanded bubble view, handles rendering the caret and header of the view.
69 */
Mady Mellor3d82e682019-02-05 13:34:48 -080070public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080071 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080072
Mady Mellor44ee2fe2019-01-30 17:51:16 -080073 // Configurable via bubble settings; just for testing
74 private boolean mUseFooter;
75 private boolean mShowOnTop;
76
Mady Mellordea7ecf2018-12-10 15:47:40 -080077 // The triangle pointing to the expanded view
78 private View mPointerView;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080079 private int mPointerMargin;
Mady Mellore8e07712019-01-23 12:45:33 -080080
81 // Header
82 private View mHeaderView;
Mady Mellor9801e852019-01-22 14:50:28 -080083 private ImageButton mDeepLinkIcon;
Mady Mellor9801e852019-01-22 14:50:28 -080084 private ImageButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080085
86 // Permission view
87 private View mPermissionView;
88
Mady Mellor3dff9e62019-02-05 18:12:53 -080089 // Views for expanded state
90 private ExpandableNotificationRow mNotifRow;
91 private ActivityView mActivityView;
92
93 private boolean mActivityViewReady = false;
94 private PendingIntent mBubbleIntent;
95
Mady Mellorfe7ec032019-01-30 17:32:49 -080096 private int mMinHeight;
97 private int mHeaderHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080098 private int mBubbleHeight;
99 private int mPermissionHeight;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800100
Mady Mellor9801e852019-01-22 14:50:28 -0800101 private NotificationEntry mEntry;
102 private PackageManager mPm;
103 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -0800104 private Drawable mAppIcon;
105
106 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800107 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -0800108
Mady Mellor9801e852019-01-22 14:50:28 -0800109 private BubbleStackView mStackView;
110
Mady Mellor3dff9e62019-02-05 18:12:53 -0800111 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
112
113 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
114 @Override
115 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800116 if (!mActivityViewReady) {
117 mActivityViewReady = true;
118 mActivityView.startActivity(mBubbleIntent);
119 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800120 }
121
122 @Override
123 public void onActivityViewDestroyed(ActivityView view) {
124 mActivityViewReady = false;
125 }
126
127 /**
128 * This is only called for tasks on this ActivityView, which is also set to
129 * single-task mode -- meaning never more than one task on this display. If a task
130 * is being removed, it's the top Activity finishing and this bubble should
131 * be removed or collapsed.
132 */
133 @Override
134 public void onTaskRemovalStarted(int taskId) {
135 if (mEntry != null) {
136 // Must post because this is called from a binder thread.
137 post(() -> mBubbleController.removeBubble(mEntry.key));
138 }
139 }
140 };
Mady Mellore8e07712019-01-23 12:45:33 -0800141
Mady Mellor3d82e682019-02-05 13:34:48 -0800142 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800143 this(context, null);
144 }
145
Mady Mellor3d82e682019-02-05 13:34:48 -0800146 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800147 this(context, attrs, 0);
148 }
149
Mady Mellor3d82e682019-02-05 13:34:48 -0800150 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800151 this(context, attrs, defStyleAttr, 0);
152 }
153
Mady Mellor3d82e682019-02-05 13:34:48 -0800154 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800155 int defStyleRes) {
156 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800157 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800158 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800159 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800160 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800161 try {
162 mNotificationManagerService = INotificationManager.Stub.asInterface(
163 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
164 } catch (ServiceManager.ServiceNotFoundException e) {
165 Log.w(TAG, e);
166 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800167 }
168
169 @Override
170 protected void onFinishInflate() {
171 super.onFinishInflate();
172
173 Resources res = getResources();
174 mPointerView = findViewById(R.id.pointer_view);
175 int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
176 int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800177
178 TypedArray ta = getContext().obtainStyledAttributes(
179 new int[] {android.R.attr.colorBackgroundFloating});
180 int bgColor = ta.getColor(0, Color.WHITE);
181 ta.recycle();
182
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800183 mShowOnTop = BubbleController.showBubblesAtTop(getContext());
184 mUseFooter = BubbleController.useFooter(getContext());
185
Mady Mellordea7ecf2018-12-10 15:47:40 -0800186 ShapeDrawable triangleDrawable = new ShapeDrawable(
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800187 TriangleShape.create(width, height, mShowOnTop /* pointUp */));
Mady Mellordd497052019-01-30 17:23:48 -0800188 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800189 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800190
Mady Mellore8e07712019-01-23 12:45:33 -0800191 FrameLayout viewWrapper = findViewById(R.id.header_permission_wrapper);
Mady Mellor310c7c62019-02-21 17:03:08 -0800192 viewWrapper.setBackground(createHeaderPermissionBackground(bgColor));
193
Mady Mellore8e07712019-01-23 12:45:33 -0800194 LayoutTransition transition = new LayoutTransition();
195 transition.setDuration(200);
196
197 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f);
198 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
199 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
200
201 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f);
202 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
203 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
204
205 transition.setAnimateParentHierarchy(false);
206 viewWrapper.setLayoutTransition(transition);
207 viewWrapper.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
208
Mady Mellor310c7c62019-02-21 17:03:08 -0800209
Mady Mellorfe7ec032019-01-30 17:32:49 -0800210 mHeaderHeight = getContext().getResources().getDimensionPixelSize(
211 R.dimen.bubble_expanded_header_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800212 mPermissionHeight = getContext().getResources().getDimensionPixelSize(
213 R.dimen.bubble_permission_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800214 mHeaderView = findViewById(R.id.header_layout);
Mady Mellor9801e852019-01-22 14:50:28 -0800215 mDeepLinkIcon = findViewById(R.id.deep_link_button);
216 mSettingsIcon = findViewById(R.id.settings_button);
217 mDeepLinkIcon.setOnClickListener(this);
218 mSettingsIcon.setOnClickListener(this);
Mady Mellore8e07712019-01-23 12:45:33 -0800219
220 mPermissionView = findViewById(R.id.permission_layout);
221 findViewById(R.id.no_bubbles_button).setOnClickListener(this);
222 findViewById(R.id.yes_bubbles_button).setOnClickListener(this);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800223
224 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
225 true /* singleTaskInstance */);
226 addView(mActivityView);
227
228 mActivityView.setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
229 ActivityView activityView = (ActivityView) view;
230 // Here we assume that the position of the ActivityView on the screen
231 // remains regardless of IME status. When we move ActivityView, the
232 // forwardedInsets should be computed not against the current location
233 // and size, but against the post-moved location and size.
234 Point displaySize = new Point();
235 view.getContext().getDisplay().getSize(displaySize);
236 int[] windowLocation = view.getLocationOnScreen();
237 final int windowBottom = windowLocation[1] + view.getHeight();
238 final int keyboardHeight = insets.getSystemWindowInsetBottom()
239 - insets.getStableInsetBottom();
240 final int insetsBottom = Math.max(0,
241 windowBottom + keyboardHeight - displaySize.y);
242 activityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
243 return view.onApplyWindowInsets(insets);
244 });
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800245
246 if (!mShowOnTop) {
247 removeView(mPointerView);
248 if (mUseFooter) {
Mady Mellor310c7c62019-02-21 17:03:08 -0800249 View divider = findViewById(R.id.divider);
250 viewWrapper.removeView(divider);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800251 removeView(viewWrapper);
Mady Mellor310c7c62019-02-21 17:03:08 -0800252 addView(divider);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800253 addView(viewWrapper);
254 }
255 addView(mPointerView);
256 }
Mady Mellore8e07712019-01-23 12:45:33 -0800257 }
258
259 /**
Mady Mellor310c7c62019-02-21 17:03:08 -0800260 * Creates a background with corners rounded based on how the view is configured to display
261 */
262 private Drawable createHeaderPermissionBackground(int bgColor) {
263 TypedArray ta2 = getContext().obtainStyledAttributes(
264 new int[] {android.R.attr.dialogCornerRadius});
265 final float cr = ta2.getDimension(0, 0f);
266 ta2.recycle();
267
268 float[] radii = mUseFooter
269 ? new float[] {0, 0, 0, 0, cr, cr, cr, cr}
270 : new float[] {cr, cr, cr, cr, 0, 0, 0, 0};
271 GradientDrawable chromeBackground = new GradientDrawable();
272 chromeBackground.setShape(GradientDrawable.RECTANGLE);
273 chromeBackground.setCornerRadii(radii);
274 chromeBackground.setColor(bgColor);
275 return chromeBackground;
276 }
277
278 /**
Mady Mellore8e07712019-01-23 12:45:33 -0800279 * Sets the listener to notify when a bubble has been blocked.
280 */
281 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
282 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800283 }
284
285 /**
286 * Sets the notification entry used to populate this view.
287 */
288 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
289 mStackView = stackView;
290 mEntry = entry;
291
292 ApplicationInfo info;
293 try {
294 info = mPm.getApplicationInfo(
295 entry.notification.getPackageName(),
296 PackageManager.MATCH_UNINSTALLED_PACKAGES
297 | PackageManager.MATCH_DISABLED_COMPONENTS
298 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
299 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
300 if (info != null) {
301 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800302 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800303 }
304 } catch (PackageManager.NameNotFoundException e) {
305 // Ahh... just use package name
306 mAppName = entry.notification.getPackageName();
307 }
Mady Mellore8e07712019-01-23 12:45:33 -0800308 if (mAppIcon == null) {
309 mAppIcon = mPm.getDefaultActivityIcon();
310 }
Mady Mellor9801e852019-01-22 14:50:28 -0800311 updateHeaderView();
Mady Mellore8e07712019-01-23 12:45:33 -0800312 updatePermissionView();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800313 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800314 }
315
316 /**
317 * Lets activity view know it should be shown / populated.
318 */
319 public void populateActivityView() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800320 mActivityView.setCallback(mStateCallback);
Mady Mellor9801e852019-01-22 14:50:28 -0800321 }
322
Mady Mellorfe7ec032019-01-30 17:32:49 -0800323 /**
324 * Updates the entry backing this view. This will not re-populate ActivityView, it will
325 * only update the deep-links in the header, the title, and the height of the view.
326 */
327 public void update(NotificationEntry entry) {
328 if (entry.key.equals(mEntry.key)) {
329 mEntry = entry;
330 updateHeaderView();
331 updateHeight();
332 } else {
333 Log.w(TAG, "Trying to update entry with different key, new entry: "
334 + entry.key + " old entry: " + mEntry.key);
335 }
336 }
337
Mady Mellor9801e852019-01-22 14:50:28 -0800338 private void updateHeaderView() {
339 mSettingsIcon.setContentDescription(getResources().getString(
340 R.string.bubbles_settings_button_description, mAppName));
341 mDeepLinkIcon.setContentDescription(getResources().getString(
342 R.string.bubbles_deep_link_button_description, mAppName));
Mady Mellore8e07712019-01-23 12:45:33 -0800343 }
344
345 private void updatePermissionView() {
346 boolean hasUserApprovedBubblesForPackage = false;
347 try {
348 hasUserApprovedBubblesForPackage =
349 mNotificationManagerService.hasUserApprovedBubblesForPackage(
350 mEntry.notification.getPackageName(), mEntry.notification.getUid());
351 } catch (RemoteException e) {
352 Log.w(TAG, e);
353 }
354 if (hasUserApprovedBubblesForPackage) {
355 mHeaderView.setVisibility(VISIBLE);
356 mPermissionView.setVisibility(GONE);
357 } else {
358 mHeaderView.setVisibility(GONE);
359 mPermissionView.setVisibility(VISIBLE);
360 ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon);
361 ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName);
Steven Wua62cb6a2019-02-15 17:12:51 -0500362 logBubbleClickEvent(mEntry.notification,
363 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_DIALOG_SHOWN);
Mady Mellor9801e852019-01-22 14:50:28 -0800364 }
365 }
366
Mady Mellor3dff9e62019-02-05 18:12:53 -0800367 private void updateExpandedView() {
368 mBubbleIntent = getBubbleIntent(mEntry);
369 if (mBubbleIntent != null) {
370 if (mNotifRow != null) {
371 // Clear out the row if we had it previously
372 removeView(mNotifRow);
373 mNotifRow = null;
374 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800375 mActivityView.setVisibility(VISIBLE);
376 } else {
377 // Hide activity view if we had it previously
378 mActivityView.setVisibility(GONE);
379
380 // Use notification view
381 mNotifRow = mEntry.getRow();
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800382 if (mShowOnTop) {
383 addView(mNotifRow);
384 } else {
385 addView(mNotifRow, mUseFooter ? 0 : 1);
386 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800387 }
388 updateView();
389 }
390
Mark Renouf041d7262019-02-06 12:09:41 -0500391 boolean performBackPressIfNeeded() {
392 if (mActivityView == null || !usingActivityView()) {
393 return false;
394 }
395 mActivityView.performBackPress();
396 return true;
397 }
398
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800399 /**
400 * @return total height that the expanded view occupies.
401 */
402 int getExpandedSize() {
403 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
404 ? mHeaderHeight
405 : mPermissionHeight;
406 return mBubbleHeight + mPointerView.getHeight() + mPointerMargin
407 + chromeHeight;
408 }
409
Mady Mellorfe7ec032019-01-30 17:32:49 -0800410 void updateHeight() {
411 if (usingActivityView()) {
412 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
413 int desiredHeight;
414 if (data == null) {
415 // This is a contentIntent based bubble, lets allow it to be the max height
416 // as it was forced into this mode and not prepared to be small
417 desiredHeight = mStackView.getMaxExpandedHeight();
418 } else {
419 desiredHeight = data.getDesiredHeight() > 0
420 ? data.getDesiredHeight()
421 : mMinHeight;
422 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800423 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
424 ? mHeaderHeight
425 : mPermissionHeight;
426 int max = mStackView.getMaxExpandedHeight() - chromeHeight - mPointerView.getHeight()
427 - mPointerMargin;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800428 int height = Math.min(desiredHeight, max);
429 height = Math.max(height, mMinHeight);
430 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
431 lp.height = height;
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800432 mBubbleHeight = height;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800433 mActivityView.setLayoutParams(lp);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800434 } else {
435 mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800436 }
437 }
438
Mady Mellor9801e852019-01-22 14:50:28 -0800439 @Override
440 public void onClick(View view) {
441 if (mEntry == null) {
442 return;
443 }
444 Notification n = mEntry.notification.getNotification();
445 int id = view.getId();
446 if (id == R.id.deep_link_button) {
447 mStackView.collapseStack(() -> {
448 try {
449 n.contentIntent.send();
Steven Wub00225b2019-02-08 14:27:42 -0500450 logBubbleClickEvent(mEntry.notification,
451 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_APP);
Mady Mellor9801e852019-01-22 14:50:28 -0800452 } catch (PendingIntent.CanceledException e) {
453 Log.w(TAG, "Failed to send intent for bubble with key: "
454 + (mEntry != null ? mEntry.key : " null entry"));
455 }
456 });
457 } else if (id == R.id.settings_button) {
458 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
459 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500460 mStackView.collapseStack(() -> {
461 mContext.startActivity(intent);
462 logBubbleClickEvent(mEntry.notification,
463 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
464 });
Mady Mellore8e07712019-01-23 12:45:33 -0800465 } else if (id == R.id.no_bubbles_button) {
466 setBubblesAllowed(false);
467 } else if (id == R.id.yes_bubbles_button) {
468 setBubblesAllowed(true);
469 }
470 }
471
472 private void setBubblesAllowed(boolean allowed) {
473 try {
474 mNotificationManagerService.setBubblesAllowed(
475 mEntry.notification.getPackageName(),
476 mEntry.notification.getUid(),
477 allowed);
478 if (allowed) {
479 mPermissionView.setVisibility(GONE);
480 mHeaderView.setVisibility(VISIBLE);
481 } else if (mOnBubbleBlockedListener != null) {
482 mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
483 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800484 mStackView.onExpandedHeightChanged();
Steven Wub00225b2019-02-08 14:27:42 -0500485 logBubbleClickEvent(mEntry.notification,
486 allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
487 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
Mady Mellore8e07712019-01-23 12:45:33 -0800488 } catch (RemoteException e) {
489 Log.w(TAG, e);
Mady Mellor9801e852019-01-22 14:50:28 -0800490 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800491 }
492
493 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800494 * Update appearance of the expanded view being displayed.
495 */
496 public void updateView() {
497 if (usingActivityView()
498 && mActivityView.getVisibility() == VISIBLE
499 && mActivityView.isAttachedToWindow()) {
500 mActivityView.onLocationChanged();
501 } else if (mNotifRow != null) {
502 applyRowState(mNotifRow);
503 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800504 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800505 }
506
507 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800508 * Set the x position that the tip of the triangle should point to.
509 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800510 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800511 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800512 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800513 mPointerView.setTranslationX(x);
514 }
515
516 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800517 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800518 */
Mark Renouf28c250d2019-02-25 16:47:34 -0500519 public void destroyActivityView() {
Mady Mellor3dff9e62019-02-05 18:12:53 -0800520 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500521 return;
522 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500523 if (mActivityViewReady) {
524 mActivityView.release();
Mady Mellordea7ecf2018-12-10 15:47:40 -0800525 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500526 removeView(mActivityView);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800527 mActivityView = null;
528 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800529 }
530
Mady Mellor3dff9e62019-02-05 18:12:53 -0800531 private boolean usingActivityView() {
532 return mBubbleIntent != null;
533 }
534
535 private void applyRowState(ExpandableNotificationRow view) {
536 view.reset();
537 view.setHeadsUp(false);
538 view.resetTranslation();
539 view.setOnKeyguard(false);
540 view.setOnAmbient(false);
541 view.setClipBottomAmount(0);
542 view.setClipTopAmount(0);
543 view.setContentTransformationAmount(0, false);
544 view.setIconsVisible(true);
545
546 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
547 // view.setTopRoundness(1, false);
548 // view.setBottomRoundness(1, false);
549
550 ExpandableViewState viewState = view.getViewState();
551 viewState = viewState == null ? new ExpandableViewState() : viewState;
552 viewState.height = view.getIntrinsicHeight();
553 viewState.gone = false;
554 viewState.hidden = false;
555 viewState.dimmed = false;
556 viewState.dark = false;
557 viewState.alpha = 1f;
558 viewState.notGoneIndex = -1;
559 viewState.xTranslation = 0;
560 viewState.yTranslation = 0;
561 viewState.zTranslation = 0;
562 viewState.scaleX = 1;
563 viewState.scaleY = 1;
564 viewState.inShelf = true;
565 viewState.headsUpIsVisible = false;
566 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800567 }
Mady Mellor9801e852019-01-22 14:50:28 -0800568
569 private Intent getSettingsIntent(String packageName, final int appUid) {
570 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
571 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
572 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
573 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
574 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
575 return intent;
576 }
Mady Mellore8e07712019-01-23 12:45:33 -0800577
Mady Mellor3dff9e62019-02-05 18:12:53 -0800578 @Nullable
579 private PendingIntent getBubbleIntent(NotificationEntry entry) {
580 Notification notif = entry.notification.getNotification();
581 String packageName = entry.notification.getPackageName();
582 Notification.BubbleMetadata data = notif.getBubbleMetadata();
583 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
584 packageName)) {
585 return data.getIntent();
586 } else if (BubbleController.shouldUseContentIntent(mContext)
587 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
588 packageName)) {
589 return notif.contentIntent;
590 }
591 return null;
592 }
593
594 /**
595 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
596 *
597 * @param intent the pending intent of the bubble.
598 * @param enableLogging whether bubble developer error should be logged.
599 * @param packageName the notification package name for this bubble.
600 * @return
601 */
602 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
603 String packageName) {
604 if (intent == null) {
605 return false;
606 }
607 ActivityInfo info =
608 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
609 if (info == null) {
610 if (enableLogging) {
611 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
612 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
613 }
614 return false;
615 }
616 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
617 if (enableLogging) {
618 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
619 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
620 }
621 return false;
622 }
623 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
624 if (enableLogging) {
625 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
626 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
627 }
628 return false;
629 }
630 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
631 }
632
Mady Mellore8e07712019-01-23 12:45:33 -0800633 /**
634 * Listener that is notified when a bubble is blocked.
635 */
636 public interface OnBubbleBlockedListener {
637 /**
638 * Called when a bubble is blocked for the provided entry.
639 */
640 void onBubbleBlocked(NotificationEntry entry);
641 }
Steven Wub00225b2019-02-08 14:27:42 -0500642
643 /**
644 * Logs bubble UI click event.
645 *
646 * @param notification the bubble notification that user is interacting with.
647 * @param action the user interaction enum.
648 */
649 private void logBubbleClickEvent(StatusBarNotification notification, int action) {
650 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
651 notification.getPackageName(),
652 notification.getNotification().getChannelId(),
653 notification.getId(),
654 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
655 mStackView.getBubbleCount(),
656 action,
657 mStackView.getNormalizedXPosition(),
658 mStackView.getNormalizedYPosition());
659 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800660}