blob: 67f327490a6edbfa1b5986de9cce8e0f06b406eb [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 Mellore8e07712019-01-23 12:45:33 -080025import android.animation.LayoutTransition;
26import android.animation.ObjectAnimator;
Mady Mellordea7ecf2018-12-10 15:47:40 -080027import android.annotation.Nullable;
Mady Mellor60101c92019-04-11 19:04:00 -070028import android.app.ActivityOptions;
Mady Mellor3dff9e62019-02-05 18:12:53 -080029import android.app.ActivityView;
Mady Mellore8e07712019-01-23 12:45:33 -080030import android.app.INotificationManager;
Mady Mellor9801e852019-01-22 14:50:28 -080031import android.app.Notification;
32import android.app.PendingIntent;
Mady Mellordea7ecf2018-12-10 15:47:40 -080033import android.content.Context;
Mady Mellor9801e852019-01-22 14:50:28 -080034import android.content.Intent;
Mady Mellor3dff9e62019-02-05 18:12:53 -080035import android.content.pm.ActivityInfo;
Mady Mellor9801e852019-01-22 14:50:28 -080036import android.content.pm.ApplicationInfo;
37import android.content.pm.PackageManager;
Mady Mellordea7ecf2018-12-10 15:47:40 -080038import android.content.res.Resources;
Mady Mellordd497052019-01-30 17:23:48 -080039import android.content.res.TypedArray;
Mady Mellordea7ecf2018-12-10 15:47:40 -080040import android.graphics.Color;
Mady Mellor3dff9e62019-02-05 18:12:53 -080041import android.graphics.Insets;
42import android.graphics.Point;
Mady Mellore8e07712019-01-23 12:45:33 -080043import android.graphics.drawable.Drawable;
Mady Mellor310c7c62019-02-21 17:03:08 -080044import android.graphics.drawable.GradientDrawable;
Mady Mellordea7ecf2018-12-10 15:47:40 -080045import android.graphics.drawable.ShapeDrawable;
Mady Mellore8e07712019-01-23 12:45:33 -080046import android.os.RemoteException;
47import android.os.ServiceManager;
Mady Mellor7af771a2019-03-07 15:04:54 -080048import android.os.UserHandle;
Mady Mellor9801e852019-01-22 14:50:28 -080049import android.provider.Settings;
Steven Wub00225b2019-02-08 14:27:42 -050050import android.service.notification.StatusBarNotification;
Mady Mellordea7ecf2018-12-10 15:47:40 -080051import android.util.AttributeSet;
Mady Mellor9801e852019-01-22 14:50:28 -080052import android.util.Log;
Steven Wub00225b2019-02-08 14:27:42 -050053import android.util.StatsLog;
Mady Mellordea7ecf2018-12-10 15:47:40 -080054import android.view.View;
Mady Mellor5029fa62019-03-05 12:16:21 -080055import android.view.ViewGroup;
Mady Mellor3dff9e62019-02-05 18:12:53 -080056import android.view.WindowInsets;
Mady Mellore8e07712019-01-23 12:45:33 -080057import android.widget.FrameLayout;
Mady Mellore8e07712019-01-23 12:45:33 -080058import android.widget.ImageView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080059import android.widget.LinearLayout;
Mark Renouf89b1a4a2018-12-04 14:59:45 -050060import android.widget.TextView;
Mady Mellordea7ecf2018-12-10 15:47:40 -080061
Mady Mellor3dff9e62019-02-05 18:12:53 -080062import com.android.systemui.Dependency;
Mady Mellore8e07712019-01-23 12:45:33 -080063import com.android.systemui.Interpolators;
Mady Mellordea7ecf2018-12-10 15:47:40 -080064import com.android.systemui.R;
65import com.android.systemui.recents.TriangleShape;
Lyn Han754e77b2019-04-30 14:34:49 -070066import com.android.systemui.statusbar.AlphaOptimizedButton;
Mady Mellor9801e852019-01-22 14:50:28 -080067import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080068import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
69import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080070
71/**
Lyn Han02cca812019-04-02 16:27:32 -070072 * Container for the expanded bubble view, handles rendering the caret and settings icon.
Mady Mellordea7ecf2018-12-10 15:47:40 -080073 */
Mady Mellor3d82e682019-02-05 13:34:48 -080074public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080075 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080076
77 // 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
Lyn Han754e77b2019-04-30 14:34:49 -070081 private AlphaOptimizedButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080082
83 // Permission view
84 private View mPermissionView;
Lyn Han02cca812019-04-02 16:27:32 -070085 private TextView mPermissionPrompt;
Mady Mellore8e07712019-01-23 12:45:33 -080086
Mady Mellor3dff9e62019-02-05 18:12:53 -080087 // Views for expanded state
88 private ExpandableNotificationRow mNotifRow;
89 private ActivityView mActivityView;
90
91 private boolean mActivityViewReady = false;
92 private PendingIntent mBubbleIntent;
93
Mady Mellor5d8f1402019-02-21 18:23:52 -080094 private boolean mKeyboardVisible;
95 private boolean mNeedsNewHeight;
96
Mady Mellorfe7ec032019-01-30 17:32:49 -080097 private int mMinHeight;
Lyn Han02cca812019-04-02 16:27:32 -070098 private int mSettingsIconHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080099 private int mBubbleHeight;
100 private int mPermissionHeight;
Lyn Han02cca812019-04-02 16:27:32 -0700101 private int mPointerWidth;
102 private int mPointerHeight;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800103
Mady Mellor9801e852019-01-22 14:50:28 -0800104 private NotificationEntry mEntry;
105 private PackageManager mPm;
106 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -0800107 private Drawable mAppIcon;
108
109 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800110 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -0800111
Mady Mellor9801e852019-01-22 14:50:28 -0800112 private BubbleStackView mStackView;
113
Mady Mellor3dff9e62019-02-05 18:12:53 -0800114 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
115
116 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
117 @Override
118 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800119 if (!mActivityViewReady) {
120 mActivityViewReady = true;
Mady Mellor60101c92019-04-11 19:04:00 -0700121 // Custom options so there is no activity transition animation
122 ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
123 0 /* enterResId */, 0 /* exitResId */);
124 // Post to keep the lifecycle normal
125 post(() -> mActivityView.startActivity(mBubbleIntent, options));
Mady Mellor6d002032019-02-13 13:45:17 -0800126 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800127 }
128
129 @Override
130 public void onActivityViewDestroyed(ActivityView view) {
131 mActivityViewReady = false;
132 }
133
134 /**
135 * This is only called for tasks on this ActivityView, which is also set to
136 * single-task mode -- meaning never more than one task on this display. If a task
137 * is being removed, it's the top Activity finishing and this bubble should
138 * be removed or collapsed.
139 */
140 @Override
141 public void onTaskRemovalStarted(int taskId) {
142 if (mEntry != null) {
143 // Must post because this is called from a binder thread.
Mark Renouf08bc42a2019-03-07 13:01:59 -0500144 post(() -> mBubbleController.removeBubble(mEntry.key,
145 BubbleController.DISMISS_TASK_FINISHED));
Mady Mellor3dff9e62019-02-05 18:12:53 -0800146 }
147 }
148 };
Mady Mellore8e07712019-01-23 12:45:33 -0800149
Mady Mellor3d82e682019-02-05 13:34:48 -0800150 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800151 this(context, null);
152 }
153
Mady Mellor3d82e682019-02-05 13:34:48 -0800154 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800155 this(context, attrs, 0);
156 }
157
Mady Mellor3d82e682019-02-05 13:34:48 -0800158 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800159 this(context, attrs, defStyleAttr, 0);
160 }
161
Mady Mellor3d82e682019-02-05 13:34:48 -0800162 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800163 int defStyleRes) {
164 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800165 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800166 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800167 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800168 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800169 try {
170 mNotificationManagerService = INotificationManager.Stub.asInterface(
171 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
172 } catch (ServiceManager.ServiceNotFoundException e) {
173 Log.w(TAG, e);
174 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800175 }
176
177 @Override
178 protected void onFinishInflate() {
179 super.onFinishInflate();
180
181 Resources res = getResources();
182 mPointerView = findViewById(R.id.pointer_view);
Lyn Han02cca812019-04-02 16:27:32 -0700183 mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
184 mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800185
186 TypedArray ta = getContext().obtainStyledAttributes(
187 new int[] {android.R.attr.colorBackgroundFloating});
188 int bgColor = ta.getColor(0, Color.WHITE);
189 ta.recycle();
190
Lyn Han02cca812019-04-02 16:27:32 -0700191 ShapeDrawable triangleDrawable = new ShapeDrawable(TriangleShape.create(
192 mPointerWidth, mPointerHeight, false /* pointUp */));
193
Mady Mellordd497052019-01-30 17:23:48 -0800194 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800195 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800196
Lyn Han02cca812019-04-02 16:27:32 -0700197 FrameLayout permissionOrSettings = findViewById(R.id.permission_or_settings);
Mady Mellor310c7c62019-02-21 17:03:08 -0800198
Mady Mellore8e07712019-01-23 12:45:33 -0800199 LayoutTransition transition = new LayoutTransition();
200 transition.setDuration(200);
201
202 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f);
203 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
204 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
205
206 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f);
207 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
208 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
209
210 transition.setAnimateParentHierarchy(false);
Lyn Han02cca812019-04-02 16:27:32 -0700211 permissionOrSettings.setLayoutTransition(transition);
212 permissionOrSettings.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
Mady Mellore8e07712019-01-23 12:45:33 -0800213
Lyn Han02cca812019-04-02 16:27:32 -0700214 mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
Mady Mellorfe7ec032019-01-30 17:32:49 -0800215 R.dimen.bubble_expanded_header_height);
Lyn Han02cca812019-04-02 16:27:32 -0700216 mSettingsIcon = findViewById(R.id.settings_button);
Lyn Han02cca812019-04-02 16:27:32 -0700217 mSettingsIcon.setOnClickListener(this);
Lyn Han02cca812019-04-02 16:27:32 -0700218
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800219 mPermissionHeight = getContext().getResources().getDimensionPixelSize(
220 R.dimen.bubble_permission_height);
Mady Mellore8e07712019-01-23 12:45:33 -0800221 mPermissionView = findViewById(R.id.permission_layout);
Lyn Han02cca812019-04-02 16:27:32 -0700222 mPermissionPrompt = mPermissionView.findViewById(R.id.prompt);
Lyn Hanc26ff122019-03-29 16:46:07 -0700223
Mady Mellore8e07712019-01-23 12:45:33 -0800224 findViewById(R.id.no_bubbles_button).setOnClickListener(this);
225 findViewById(R.id.yes_bubbles_button).setOnClickListener(this);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800226
227 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
228 true /* singleTaskInstance */);
229 addView(mActivityView);
230
Mady Mellor52b1ac62019-04-10 16:59:03 -0700231 // Make sure pointer is below activity view
232 bringChildToFront(mPointerView);
233
Mady Mellor5d8f1402019-02-21 18:23:52 -0800234 setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
235 // Keep track of IME displaying because we should not make any adjustments that might
236 // cause a config change while the IME is displayed otherwise it'll loose focus.
Mady Mellor3dff9e62019-02-05 18:12:53 -0800237 final int keyboardHeight = insets.getSystemWindowInsetBottom()
238 - insets.getStableInsetBottom();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800239 mKeyboardVisible = keyboardHeight != 0;
240 if (!mKeyboardVisible && mNeedsNewHeight) {
241 updateHeight();
242 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800243 return view.onApplyWindowInsets(insets);
244 });
Mady Mellore8e07712019-01-23 12:45:33 -0800245 }
246
Mady Mellor5d8f1402019-02-21 18:23:52 -0800247 @Override
248 protected void onDetachedFromWindow() {
249 super.onDetachedFromWindow();
250 mKeyboardVisible = false;
251 mNeedsNewHeight = false;
252 if (mActivityView != null) {
253 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
254 }
255 }
256
257 /**
258 * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
259 * This should be done post-move and post-animation.
260 */
261 void updateInsets(WindowInsets insets) {
262 if (usingActivityView()) {
263 Point displaySize = new Point();
264 mActivityView.getContext().getDisplay().getSize(displaySize);
265 int[] windowLocation = mActivityView.getLocationOnScreen();
266 final int windowBottom = windowLocation[1] + mActivityView.getHeight();
267 final int keyboardHeight = insets.getSystemWindowInsetBottom()
268 - insets.getStableInsetBottom();
269 final int insetsBottom = Math.max(0,
270 windowBottom + keyboardHeight - displaySize.y);
271 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
272 }
273 }
274
Mady Mellore8e07712019-01-23 12:45:33 -0800275 /**
Mady Mellor310c7c62019-02-21 17:03:08 -0800276 * Creates a background with corners rounded based on how the view is configured to display
277 */
Lyn Han02cca812019-04-02 16:27:32 -0700278 private Drawable createPermissionBackground(int bgColor) {
Mady Mellor310c7c62019-02-21 17:03:08 -0800279 TypedArray ta2 = getContext().obtainStyledAttributes(
280 new int[] {android.R.attr.dialogCornerRadius});
281 final float cr = ta2.getDimension(0, 0f);
282 ta2.recycle();
283
Lyn Han3f5c3a42019-04-01 15:59:56 -0700284 float[] radii = new float[] {cr, cr, cr, cr, 0, 0, 0, 0};
Mady Mellor310c7c62019-02-21 17:03:08 -0800285 GradientDrawable chromeBackground = new GradientDrawable();
286 chromeBackground.setShape(GradientDrawable.RECTANGLE);
287 chromeBackground.setCornerRadii(radii);
288 chromeBackground.setColor(bgColor);
289 return chromeBackground;
290 }
291
292 /**
Mady Mellore8e07712019-01-23 12:45:33 -0800293 * Sets the listener to notify when a bubble has been blocked.
294 */
295 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
296 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800297 }
298
299 /**
300 * Sets the notification entry used to populate this view.
301 */
302 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
303 mStackView = stackView;
304 mEntry = entry;
305
306 ApplicationInfo info;
307 try {
308 info = mPm.getApplicationInfo(
309 entry.notification.getPackageName(),
310 PackageManager.MATCH_UNINSTALLED_PACKAGES
311 | PackageManager.MATCH_DISABLED_COMPONENTS
312 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
313 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
314 if (info != null) {
315 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800316 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800317 }
318 } catch (PackageManager.NameNotFoundException e) {
319 // Ahh... just use package name
320 mAppName = entry.notification.getPackageName();
321 }
Mady Mellore8e07712019-01-23 12:45:33 -0800322 if (mAppIcon == null) {
323 mAppIcon = mPm.getDefaultActivityIcon();
324 }
Lyn Han02cca812019-04-02 16:27:32 -0700325 updateTheme();
326 togglePermissionOrSettings();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800327 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800328 }
329
330 /**
331 * Lets activity view know it should be shown / populated.
332 */
Mady Mellor5029fa62019-03-05 12:16:21 -0800333 public void populateExpandedView() {
334 if (usingActivityView()) {
335 mActivityView.setCallback(mStateCallback);
336 } else {
337 // We're using notification template
338 ViewGroup parent = (ViewGroup) mNotifRow.getParent();
339 if (parent == this) {
340 // Already added
341 return;
342 } else if (parent != null) {
343 // Still in the shade... remove it
344 parent.removeView(mNotifRow);
345 }
Lyn Han3f5c3a42019-04-01 15:59:56 -0700346 addView(mNotifRow, 1 /* index */);
Mady Mellor5029fa62019-03-05 12:16:21 -0800347 }
Mady Mellor9801e852019-01-22 14:50:28 -0800348 }
349
Mady Mellorfe7ec032019-01-30 17:32:49 -0800350 /**
351 * Updates the entry backing this view. This will not re-populate ActivityView, it will
Lyn Han02cca812019-04-02 16:27:32 -0700352 * only update the deep-links in the title, and the height of the view.
Mady Mellorfe7ec032019-01-30 17:32:49 -0800353 */
354 public void update(NotificationEntry entry) {
355 if (entry.key.equals(mEntry.key)) {
356 mEntry = entry;
Lyn Han02cca812019-04-02 16:27:32 -0700357 updateSettingsContentDescription();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800358 updateHeight();
359 } else {
360 Log.w(TAG, "Trying to update entry with different key, new entry: "
361 + entry.key + " old entry: " + mEntry.key);
362 }
363 }
364
Lyn Han02cca812019-04-02 16:27:32 -0700365 void updateTheme() {
366 // Get new colors.
Lyn Han76e803d2019-03-26 17:31:33 -0700367 TypedArray ta = mContext.obtainStyledAttributes(
Lyn Han02cca812019-04-02 16:27:32 -0700368 new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.colorForeground});
Lyn Hanc26ff122019-03-29 16:46:07 -0700369 int backgroundColor = ta.getColor(0, Color.WHITE /* default */);
370 int foregroundColor = ta.getColor(1, Color.BLACK /* default */);
Lyn Han76e803d2019-03-26 17:31:33 -0700371 ta.recycle();
372
Lyn Han02cca812019-04-02 16:27:32 -0700373 // Update permission prompt color.
374 mPermissionView.setBackground(createPermissionBackground(backgroundColor));
375 mPermissionPrompt.setTextColor(foregroundColor);
376
377 // Update triangle color.
378 ShapeDrawable triangleDrawable = new ShapeDrawable(
379 TriangleShape.create(mPointerWidth, mPointerHeight, false /* pointUp */));
380 triangleDrawable.setTint(backgroundColor);
381 mPointerView.setBackground(triangleDrawable);
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700382 }
383
Lyn Han02cca812019-04-02 16:27:32 -0700384 void togglePermissionOrSettings() {
Mady Mellore8e07712019-01-23 12:45:33 -0800385 boolean hasUserApprovedBubblesForPackage = false;
386 try {
387 hasUserApprovedBubblesForPackage =
388 mNotificationManagerService.hasUserApprovedBubblesForPackage(
389 mEntry.notification.getPackageName(), mEntry.notification.getUid());
390 } catch (RemoteException e) {
391 Log.w(TAG, e);
392 }
393 if (hasUserApprovedBubblesForPackage) {
Lyn Hanc26ff122019-03-29 16:46:07 -0700394 showSettingsIcon();
Mady Mellore8e07712019-01-23 12:45:33 -0800395 } else {
Lyn Hanc26ff122019-03-29 16:46:07 -0700396 showPermissionView();
Mady Mellor9801e852019-01-22 14:50:28 -0800397 }
398 }
399
Mady Mellor3dff9e62019-02-05 18:12:53 -0800400 private void updateExpandedView() {
401 mBubbleIntent = getBubbleIntent(mEntry);
402 if (mBubbleIntent != null) {
403 if (mNotifRow != null) {
404 // Clear out the row if we had it previously
405 removeView(mNotifRow);
406 mNotifRow = null;
407 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800408 mActivityView.setVisibility(VISIBLE);
409 } else {
410 // Hide activity view if we had it previously
411 mActivityView.setVisibility(GONE);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800412 mNotifRow = mEntry.getRow();
Mady Mellor5029fa62019-03-05 12:16:21 -0800413
Mady Mellor3dff9e62019-02-05 18:12:53 -0800414 }
415 updateView();
416 }
417
Mark Renouf041d7262019-02-06 12:09:41 -0500418 boolean performBackPressIfNeeded() {
Mady Mellor323fb0b2019-03-25 12:15:22 -0700419 if (!usingActivityView()) {
Mark Renouf041d7262019-02-06 12:09:41 -0500420 return false;
421 }
422 mActivityView.performBackPress();
423 return true;
424 }
425
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800426 /**
427 * @return total height that the expanded view occupies.
428 */
429 int getExpandedSize() {
430 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
Lyn Han02cca812019-04-02 16:27:32 -0700431 ? mSettingsIconHeight
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800432 : mPermissionHeight;
433 return mBubbleHeight + mPointerView.getHeight() + mPointerMargin
434 + chromeHeight;
435 }
436
Mady Mellorfe7ec032019-01-30 17:32:49 -0800437 void updateHeight() {
438 if (usingActivityView()) {
439 Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
Mady Mellor7af771a2019-03-07 15:04:54 -0800440 float desiredHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800441 if (data == null) {
442 // This is a contentIntent based bubble, lets allow it to be the max height
443 // as it was forced into this mode and not prepared to be small
444 desiredHeight = mStackView.getMaxExpandedHeight();
445 } else {
Mady Mellor7af771a2019-03-07 15:04:54 -0800446 boolean useRes = data.getDesiredHeightResId() != 0;
447 float desiredPx;
448 if (useRes) {
449 desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
450 mEntry.notification.getPackageName(),
451 mEntry.notification.getUser().getIdentifier());
452 } else {
453 desiredPx = data.getDesiredHeight()
454 * getContext().getResources().getDisplayMetrics().density;
455 }
456 desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800457 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800458 int chromeHeight = mPermissionView.getVisibility() != View.VISIBLE
Lyn Han02cca812019-04-02 16:27:32 -0700459 ? mSettingsIconHeight
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800460 : mPermissionHeight;
461 int max = mStackView.getMaxExpandedHeight() - chromeHeight - mPointerView.getHeight()
462 - mPointerMargin;
Mady Mellor7af771a2019-03-07 15:04:54 -0800463 float height = Math.min(desiredHeight, max);
Mady Mellorfe7ec032019-01-30 17:32:49 -0800464 height = Math.max(height, mMinHeight);
465 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800466 mNeedsNewHeight = lp.height != height;
467 if (!mKeyboardVisible) {
468 // If the keyboard is visible... don't adjust the height because that will cause
469 // a configuration change and the keyboard will be lost.
Mady Mellor7af771a2019-03-07 15:04:54 -0800470 lp.height = (int) height;
471 mBubbleHeight = (int) height;
Mady Mellor5d8f1402019-02-21 18:23:52 -0800472 mActivityView.setLayoutParams(lp);
473 mNeedsNewHeight = false;
474 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800475 } else {
476 mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
Mady Mellorfe7ec032019-01-30 17:32:49 -0800477 }
478 }
479
Mady Mellor9801e852019-01-22 14:50:28 -0800480 @Override
481 public void onClick(View view) {
482 if (mEntry == null) {
483 return;
484 }
485 Notification n = mEntry.notification.getNotification();
486 int id = view.getId();
Lyn Hanc26ff122019-03-29 16:46:07 -0700487 if (id == R.id.settings_button) {
Mady Mellor9801e852019-01-22 14:50:28 -0800488 Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
489 mEntry.notification.getUid());
Steven Wub00225b2019-02-08 14:27:42 -0500490 mStackView.collapseStack(() -> {
491 mContext.startActivity(intent);
Steven Wu45e38ae2019-03-25 16:16:59 -0400492 logBubbleClickEvent(mEntry,
Steven Wub00225b2019-02-08 14:27:42 -0500493 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
494 });
Mady Mellore8e07712019-01-23 12:45:33 -0800495 } else if (id == R.id.no_bubbles_button) {
496 setBubblesAllowed(false);
497 } else if (id == R.id.yes_bubbles_button) {
498 setBubblesAllowed(true);
499 }
500 }
501
502 private void setBubblesAllowed(boolean allowed) {
503 try {
504 mNotificationManagerService.setBubblesAllowed(
505 mEntry.notification.getPackageName(),
506 mEntry.notification.getUid(),
507 allowed);
508 if (allowed) {
Lyn Hanc26ff122019-03-29 16:46:07 -0700509 showSettingsIcon();
Mady Mellore8e07712019-01-23 12:45:33 -0800510 } else if (mOnBubbleBlockedListener != null) {
511 mOnBubbleBlockedListener.onBubbleBlocked(mEntry);
512 }
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800513 mStackView.onExpandedHeightChanged();
Steven Wu45e38ae2019-03-25 16:16:59 -0400514 logBubbleClickEvent(mEntry,
Steven Wub00225b2019-02-08 14:27:42 -0500515 allowed ? StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_IN :
516 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_OPT_OUT);
Mady Mellore8e07712019-01-23 12:45:33 -0800517 } catch (RemoteException e) {
518 Log.w(TAG, e);
Mady Mellor9801e852019-01-22 14:50:28 -0800519 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800520 }
521
Lyn Han02cca812019-04-02 16:27:32 -0700522 private void updateSettingsContentDescription() {
523 mSettingsIcon.setContentDescription(getResources().getString(
524 R.string.bubbles_settings_button_description, mAppName));
525 }
526
Lyn Hanc26ff122019-03-29 16:46:07 -0700527 void showSettingsIcon() {
Lyn Han02cca812019-04-02 16:27:32 -0700528 updateSettingsContentDescription();
529
Lyn Hanc26ff122019-03-29 16:46:07 -0700530 mPermissionView.setVisibility(GONE);
531 mSettingsIcon.setVisibility(VISIBLE);
532 }
533
534 void showPermissionView() {
Lyn Han02cca812019-04-02 16:27:32 -0700535 ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon);
536 ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName);
537 mPermissionPrompt.setText(
538 getResources().getString(R.string.bubbles_prompt, mAppName));
539 logBubbleClickEvent(mEntry,
540 StatsLog.BUBBLE_UICHANGED__ACTION__PERMISSION_DIALOG_SHOWN);
541
Lyn Hanc26ff122019-03-29 16:46:07 -0700542 mSettingsIcon.setVisibility(GONE);
543 mPermissionView.setVisibility(VISIBLE);
Lyn Hanc26ff122019-03-29 16:46:07 -0700544 }
545
Mady Mellordea7ecf2018-12-10 15:47:40 -0800546 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800547 * Update appearance of the expanded view being displayed.
548 */
549 public void updateView() {
550 if (usingActivityView()
551 && mActivityView.getVisibility() == VISIBLE
552 && mActivityView.isAttachedToWindow()) {
553 mActivityView.onLocationChanged();
554 } else if (mNotifRow != null) {
555 applyRowState(mNotifRow);
556 }
Mady Mellorfe7ec032019-01-30 17:32:49 -0800557 updateHeight();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800558 }
559
560 /**
Mady Mellordea7ecf2018-12-10 15:47:40 -0800561 * Set the x position that the tip of the triangle should point to.
562 */
Mady Mellor3dff9e62019-02-05 18:12:53 -0800563 public void setPointerPosition(float x) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800564 // Adjust for the pointer size
Mady Mellor3dff9e62019-02-05 18:12:53 -0800565 x -= (mPointerView.getWidth() / 2f);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800566 mPointerView.setTranslationX(x);
567 }
568
569 /**
Mady Mellor3dff9e62019-02-05 18:12:53 -0800570 * Removes and releases an ActivityView if one was previously created for this bubble.
Mady Mellordea7ecf2018-12-10 15:47:40 -0800571 */
Mady Mellor94d94a72019-03-05 18:16:59 -0800572 public void cleanUpExpandedState() {
573 removeView(mNotifRow);
574
Mady Mellor3dff9e62019-02-05 18:12:53 -0800575 if (mActivityView == null) {
Mark Renouf89b1a4a2018-12-04 14:59:45 -0500576 return;
577 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500578 if (mActivityViewReady) {
579 mActivityView.release();
Mady Mellordea7ecf2018-12-10 15:47:40 -0800580 }
Mark Renouf28c250d2019-02-25 16:47:34 -0500581 removeView(mActivityView);
Mady Mellor3dff9e62019-02-05 18:12:53 -0800582 mActivityView = null;
583 mActivityViewReady = false;
Mady Mellordea7ecf2018-12-10 15:47:40 -0800584 }
585
Mady Mellor3dff9e62019-02-05 18:12:53 -0800586 private boolean usingActivityView() {
Mady Mellor323fb0b2019-03-25 12:15:22 -0700587 return mBubbleIntent != null && mActivityView != null;
Mady Mellor3dff9e62019-02-05 18:12:53 -0800588 }
589
Mady Mellor390bff42019-04-05 15:09:01 -0700590 /**
591 * @return the display id of the virtual display.
592 */
593 public int getVirtualDisplayId() {
594 if (usingActivityView()) {
595 return mActivityView.getVirtualDisplayId();
596 }
597 return INVALID_DISPLAY;
598 }
599
Mady Mellor3dff9e62019-02-05 18:12:53 -0800600 private void applyRowState(ExpandableNotificationRow view) {
601 view.reset();
602 view.setHeadsUp(false);
603 view.resetTranslation();
604 view.setOnKeyguard(false);
605 view.setOnAmbient(false);
606 view.setClipBottomAmount(0);
607 view.setClipTopAmount(0);
608 view.setContentTransformationAmount(0, false);
609 view.setIconsVisible(true);
610
611 // TODO - Need to reset this (and others) when view goes back in shade, leave for now
612 // view.setTopRoundness(1, false);
613 // view.setBottomRoundness(1, false);
614
615 ExpandableViewState viewState = view.getViewState();
616 viewState = viewState == null ? new ExpandableViewState() : viewState;
617 viewState.height = view.getIntrinsicHeight();
618 viewState.gone = false;
619 viewState.hidden = false;
620 viewState.dimmed = false;
621 viewState.dark = false;
622 viewState.alpha = 1f;
623 viewState.notGoneIndex = -1;
624 viewState.xTranslation = 0;
625 viewState.yTranslation = 0;
626 viewState.zTranslation = 0;
627 viewState.scaleX = 1;
628 viewState.scaleY = 1;
629 viewState.inShelf = true;
630 viewState.headsUpIsVisible = false;
631 viewState.applyToView(view);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800632 }
Mady Mellor9801e852019-01-22 14:50:28 -0800633
634 private Intent getSettingsIntent(String packageName, final int appUid) {
Lyn Han754e77b2019-04-30 14:34:49 -0700635 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
Mady Mellor9801e852019-01-22 14:50:28 -0800636 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
637 intent.putExtra(Settings.EXTRA_APP_UID, appUid);
638 intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
639 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Lyn Han754e77b2019-04-30 14:34:49 -0700640 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Mady Mellor9801e852019-01-22 14:50:28 -0800641 return intent;
642 }
Mady Mellore8e07712019-01-23 12:45:33 -0800643
Mady Mellor3dff9e62019-02-05 18:12:53 -0800644 @Nullable
645 private PendingIntent getBubbleIntent(NotificationEntry entry) {
646 Notification notif = entry.notification.getNotification();
647 String packageName = entry.notification.getPackageName();
648 Notification.BubbleMetadata data = notif.getBubbleMetadata();
649 if (data != null && canLaunchInActivityView(data.getIntent(), true /* enableLogging */,
650 packageName)) {
651 return data.getIntent();
652 } else if (BubbleController.shouldUseContentIntent(mContext)
653 && canLaunchInActivityView(notif.contentIntent, false /* enableLogging */,
654 packageName)) {
655 return notif.contentIntent;
656 }
657 return null;
658 }
659
660 /**
661 * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
662 *
663 * @param intent the pending intent of the bubble.
664 * @param enableLogging whether bubble developer error should be logged.
665 * @param packageName the notification package name for this bubble.
666 * @return
667 */
668 private boolean canLaunchInActivityView(PendingIntent intent, boolean enableLogging,
669 String packageName) {
670 if (intent == null) {
671 return false;
672 }
673 ActivityInfo info =
674 intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
675 if (info == null) {
676 if (enableLogging) {
677 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
678 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING);
679 }
680 return false;
681 }
682 if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
683 if (enableLogging) {
684 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
685 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE);
686 }
687 return false;
688 }
689 if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
690 if (enableLogging) {
691 StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
692 BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
693 }
694 return false;
695 }
696 return (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
697 }
698
Mady Mellore8e07712019-01-23 12:45:33 -0800699 /**
700 * Listener that is notified when a bubble is blocked.
701 */
702 public interface OnBubbleBlockedListener {
703 /**
704 * Called when a bubble is blocked for the provided entry.
705 */
706 void onBubbleBlocked(NotificationEntry entry);
707 }
Steven Wub00225b2019-02-08 14:27:42 -0500708
709 /**
710 * Logs bubble UI click event.
711 *
Steven Wu45e38ae2019-03-25 16:16:59 -0400712 * @param entry the bubble notification entry that user is interacting with.
Steven Wub00225b2019-02-08 14:27:42 -0500713 * @param action the user interaction enum.
714 */
Steven Wu45e38ae2019-03-25 16:16:59 -0400715 private void logBubbleClickEvent(NotificationEntry entry, int action) {
716 StatusBarNotification notification = entry.notification;
Steven Wub00225b2019-02-08 14:27:42 -0500717 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
718 notification.getPackageName(),
719 notification.getNotification().getChannelId(),
720 notification.getId(),
721 mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
722 mStackView.getBubbleCount(),
723 action,
724 mStackView.getNormalizedXPosition(),
Steven Wu45e38ae2019-03-25 16:16:59 -0400725 mStackView.getNormalizedYPosition(),
Steven Wu8ba8ca92019-04-11 10:47:42 -0400726 entry.showInShadeWhenBubble(),
727 entry.isForegroundService(),
728 BubbleController.isForegroundApp(mContext, notification.getPackageName()));
Steven Wub00225b2019-02-08 14:27:42 -0500729 }
Mady Mellor7af771a2019-03-07 15:04:54 -0800730
731 private int getDimenForPackageUser(int resId, String pkg, int userId) {
732 Resources r;
733 if (pkg != null) {
734 try {
735 if (userId == UserHandle.USER_ALL) {
736 userId = UserHandle.USER_SYSTEM;
737 }
738 r = mPm.getResourcesForApplicationAsUser(pkg, userId);
739 return r.getDimensionPixelSize(resId);
740 } catch (PackageManager.NameNotFoundException ex) {
741 // Uninstalled, don't care
742 } catch (Resources.NotFoundException e) {
743 // Invalid res id, return 0 and user our default
744 Log.e(TAG, "Couldn't find desired height res id", e);
745 }
746 }
747 return 0;
748 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800749}