blob: 39867c3a0bdb10ab897af7b2cdbc4b2d700d9f1b [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
Mady Mellor3dff9e62019-02-05 18:12:53 -080055import com.android.systemui.Dependency;
Mady Mellordea7ecf2018-12-10 15:47:40 -080056import com.android.systemui.R;
57import com.android.systemui.recents.TriangleShape;
Lyn Han754e77b2019-04-30 14:34:49 -070058import com.android.systemui.statusbar.AlphaOptimizedButton;
Mady Mellor9801e852019-01-22 14:50:28 -080059import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3dff9e62019-02-05 18:12:53 -080060import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
61import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
Mady Mellordea7ecf2018-12-10 15:47:40 -080062
63/**
Lyn Han02cca812019-04-02 16:27:32 -070064 * Container for the expanded bubble view, handles rendering the caret and settings icon.
Mady Mellordea7ecf2018-12-10 15:47:40 -080065 */
Mady Mellor3d82e682019-02-05 13:34:48 -080066public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
Mady Mellor9801e852019-01-22 14:50:28 -080067 private static final String TAG = "BubbleExpandedView";
Mady Mellordea7ecf2018-12-10 15:47:40 -080068
69 // The triangle pointing to the expanded view
70 private View mPointerView;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080071 private int mPointerMargin;
Mady Mellore8e07712019-01-23 12:45:33 -080072
Lyn Han754e77b2019-04-30 14:34:49 -070073 private AlphaOptimizedButton mSettingsIcon;
Mady Mellore8e07712019-01-23 12:45:33 -080074
Mady Mellor3dff9e62019-02-05 18:12:53 -080075 // Views for expanded state
76 private ExpandableNotificationRow mNotifRow;
77 private ActivityView mActivityView;
78
79 private boolean mActivityViewReady = false;
80 private PendingIntent mBubbleIntent;
81
Mady Mellor5d8f1402019-02-21 18:23:52 -080082 private boolean mKeyboardVisible;
83 private boolean mNeedsNewHeight;
84
Mady Mellorfe7ec032019-01-30 17:32:49 -080085 private int mMinHeight;
Lyn Han02cca812019-04-02 16:27:32 -070086 private int mSettingsIconHeight;
Mady Mellor44ee2fe2019-01-30 17:51:16 -080087 private int mBubbleHeight;
Lyn Han02cca812019-04-02 16:27:32 -070088 private int mPointerWidth;
89 private int mPointerHeight;
Mady Mellordea7ecf2018-12-10 15:47:40 -080090
Mady Mellor9801e852019-01-22 14:50:28 -080091 private NotificationEntry mEntry;
92 private PackageManager mPm;
93 private String mAppName;
Mady Mellore8e07712019-01-23 12:45:33 -080094 private Drawable mAppIcon;
95
96 private INotificationManager mNotificationManagerService;
Mady Mellor3dff9e62019-02-05 18:12:53 -080097 private BubbleController mBubbleController = Dependency.get(BubbleController.class);
Mady Mellor9801e852019-01-22 14:50:28 -080098
Mady Mellor9801e852019-01-22 14:50:28 -080099 private BubbleStackView mStackView;
100
Mady Mellor3dff9e62019-02-05 18:12:53 -0800101 private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
102
103 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
104 @Override
105 public void onActivityViewReady(ActivityView view) {
Mady Mellor6d002032019-02-13 13:45:17 -0800106 if (!mActivityViewReady) {
107 mActivityViewReady = true;
Mady Mellor60101c92019-04-11 19:04:00 -0700108 // Custom options so there is no activity transition animation
109 ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
110 0 /* enterResId */, 0 /* exitResId */);
111 // Post to keep the lifecycle normal
112 post(() -> mActivityView.startActivity(mBubbleIntent, options));
Mady Mellor6d002032019-02-13 13:45:17 -0800113 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800114 }
115
116 @Override
117 public void onActivityViewDestroyed(ActivityView view) {
118 mActivityViewReady = false;
119 }
120
121 /**
122 * This is only called for tasks on this ActivityView, which is also set to
123 * single-task mode -- meaning never more than one task on this display. If a task
124 * is being removed, it's the top Activity finishing and this bubble should
125 * be removed or collapsed.
126 */
127 @Override
128 public void onTaskRemovalStarted(int taskId) {
129 if (mEntry != null) {
130 // Must post because this is called from a binder thread.
Mark Renouf08bc42a2019-03-07 13:01:59 -0500131 post(() -> mBubbleController.removeBubble(mEntry.key,
132 BubbleController.DISMISS_TASK_FINISHED));
Mady Mellor3dff9e62019-02-05 18:12:53 -0800133 }
134 }
135 };
Mady Mellore8e07712019-01-23 12:45:33 -0800136
Mady Mellor3d82e682019-02-05 13:34:48 -0800137 public BubbleExpandedView(Context context) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800138 this(context, null);
139 }
140
Mady Mellor3d82e682019-02-05 13:34:48 -0800141 public BubbleExpandedView(Context context, AttributeSet attrs) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800142 this(context, attrs, 0);
143 }
144
Mady Mellor3d82e682019-02-05 13:34:48 -0800145 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
Mady Mellordea7ecf2018-12-10 15:47:40 -0800146 this(context, attrs, defStyleAttr, 0);
147 }
148
Mady Mellor3d82e682019-02-05 13:34:48 -0800149 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
Mady Mellordea7ecf2018-12-10 15:47:40 -0800150 int defStyleRes) {
151 super(context, attrs, defStyleAttr, defStyleRes);
Mady Mellor9801e852019-01-22 14:50:28 -0800152 mPm = context.getPackageManager();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800153 mMinHeight = getResources().getDimensionPixelSize(
Mady Mellor3dff9e62019-02-05 18:12:53 -0800154 R.dimen.bubble_expanded_default_height);
Mady Mellor44ee2fe2019-01-30 17:51:16 -0800155 mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
Mady Mellore8e07712019-01-23 12:45:33 -0800156 try {
157 mNotificationManagerService = INotificationManager.Stub.asInterface(
158 ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
159 } catch (ServiceManager.ServiceNotFoundException e) {
160 Log.w(TAG, e);
161 }
Mady Mellordea7ecf2018-12-10 15:47:40 -0800162 }
163
164 @Override
165 protected void onFinishInflate() {
166 super.onFinishInflate();
167
168 Resources res = getResources();
169 mPointerView = findViewById(R.id.pointer_view);
Lyn Han02cca812019-04-02 16:27:32 -0700170 mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
171 mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
Mady Mellordd497052019-01-30 17:23:48 -0800172
173 TypedArray ta = getContext().obtainStyledAttributes(
174 new int[] {android.R.attr.colorBackgroundFloating});
175 int bgColor = ta.getColor(0, Color.WHITE);
176 ta.recycle();
177
Lyn Han02cca812019-04-02 16:27:32 -0700178 ShapeDrawable triangleDrawable = new ShapeDrawable(TriangleShape.create(
179 mPointerWidth, mPointerHeight, false /* pointUp */));
180
Mady Mellordd497052019-01-30 17:23:48 -0800181 triangleDrawable.setTint(bgColor);
Mady Mellordea7ecf2018-12-10 15:47:40 -0800182 mPointerView.setBackground(triangleDrawable);
Mady Mellor9801e852019-01-22 14:50:28 -0800183
Lyn Han02cca812019-04-02 16:27:32 -0700184 mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
Mady Mellorfe7ec032019-01-30 17:32:49 -0800185 R.dimen.bubble_expanded_header_height);
Lyn Han02cca812019-04-02 16:27:32 -0700186 mSettingsIcon = findViewById(R.id.settings_button);
Lyn Han02cca812019-04-02 16:27:32 -0700187 mSettingsIcon.setOnClickListener(this);
Lyn Han02cca812019-04-02 16:27:32 -0700188
Mady Mellor3dff9e62019-02-05 18:12:53 -0800189 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
190 true /* singleTaskInstance */);
191 addView(mActivityView);
192
Mady Mellor52b1ac62019-04-10 16:59:03 -0700193 // Make sure pointer is below activity view
194 bringChildToFront(mPointerView);
195
Mady Mellor5d8f1402019-02-21 18:23:52 -0800196 setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
197 // Keep track of IME displaying because we should not make any adjustments that might
198 // cause a config change while the IME is displayed otherwise it'll loose focus.
Mady Mellor3dff9e62019-02-05 18:12:53 -0800199 final int keyboardHeight = insets.getSystemWindowInsetBottom()
200 - insets.getStableInsetBottom();
Mady Mellor5d8f1402019-02-21 18:23:52 -0800201 mKeyboardVisible = keyboardHeight != 0;
202 if (!mKeyboardVisible && mNeedsNewHeight) {
203 updateHeight();
204 }
Mady Mellor3dff9e62019-02-05 18:12:53 -0800205 return view.onApplyWindowInsets(insets);
206 });
Mady Mellore8e07712019-01-23 12:45:33 -0800207 }
208
Mady Mellor5d8f1402019-02-21 18:23:52 -0800209 @Override
210 protected void onDetachedFromWindow() {
211 super.onDetachedFromWindow();
212 mKeyboardVisible = false;
213 mNeedsNewHeight = false;
214 if (mActivityView != null) {
215 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
216 }
217 }
218
219 /**
220 * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
221 * This should be done post-move and post-animation.
222 */
223 void updateInsets(WindowInsets insets) {
224 if (usingActivityView()) {
225 Point displaySize = new Point();
226 mActivityView.getContext().getDisplay().getSize(displaySize);
227 int[] windowLocation = mActivityView.getLocationOnScreen();
228 final int windowBottom = windowLocation[1] + mActivityView.getHeight();
229 final int keyboardHeight = insets.getSystemWindowInsetBottom()
230 - insets.getStableInsetBottom();
231 final int insetsBottom = Math.max(0,
232 windowBottom + keyboardHeight - displaySize.y);
233 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
234 }
235 }
236
Mady Mellore8e07712019-01-23 12:45:33 -0800237 /**
238 * Sets the listener to notify when a bubble has been blocked.
239 */
240 public void setOnBlockedListener(OnBubbleBlockedListener listener) {
241 mOnBubbleBlockedListener = listener;
Mady Mellor9801e852019-01-22 14:50:28 -0800242 }
243
244 /**
245 * Sets the notification entry used to populate this view.
246 */
247 public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
248 mStackView = stackView;
249 mEntry = entry;
250
251 ApplicationInfo info;
252 try {
253 info = mPm.getApplicationInfo(
254 entry.notification.getPackageName(),
255 PackageManager.MATCH_UNINSTALLED_PACKAGES
256 | PackageManager.MATCH_DISABLED_COMPONENTS
257 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
258 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
259 if (info != null) {
260 mAppName = String.valueOf(mPm.getApplicationLabel(info));
Mady Mellore8e07712019-01-23 12:45:33 -0800261 mAppIcon = mPm.getApplicationIcon(info);
Mady Mellor9801e852019-01-22 14:50:28 -0800262 }
263 } catch (PackageManager.NameNotFoundException e) {
264 // Ahh... just use package name
265 mAppName = entry.notification.getPackageName();
266 }
Mady Mellore8e07712019-01-23 12:45:33 -0800267 if (mAppIcon == null) {
268 mAppIcon = mPm.getDefaultActivityIcon();
269 }
Lyn Han02cca812019-04-02 16:27:32 -0700270 updateTheme();
Lyn Han69149122019-04-30 12:03:12 -0700271 showSettingsIcon();
Mady Mellor3dff9e62019-02-05 18:12:53 -0800272 updateExpandedView();
Mady Mellor6d002032019-02-13 13:45:17 -0800273 }
274
275 /**
276 * Lets activity view know it should be shown / populated.
277 */
Mady Mellor5029fa62019-03-05 12:16:21 -0800278 public void populateExpandedView() {
279 if (usingActivityView()) {
280 mActivityView.setCallback(mStateCallback);
281 } else {
282 // We're using notification template
283 ViewGroup parent = (ViewGroup) mNotifRow.getParent();
284 if (parent == this) {
285 // Already added
286 return;
287 } else if (parent != null) {
288 // Still in the shade... remove it
289 parent.removeView(mNotifRow);
290 }
Lyn Han3f5c3a42019-04-01 15:59:56 -0700291 addView(mNotifRow, 1 /* index */);
Mady Mellor5029fa62019-03-05 12:16:21 -0800292 }
Mady Mellor9801e852019-01-22 14:50:28 -0800293 }
294
Mady Mellorfe7ec032019-01-30 17:32:49 -0800295 /**
296 * Updates the entry backing this view. This will not re-populate ActivityView, it will
Lyn Han02cca812019-04-02 16:27:32 -0700297 * only update the deep-links in the title, and the height of the view.
Mady Mellorfe7ec032019-01-30 17:32:49 -0800298 */
299 public void update(NotificationEntry entry) {
300 if (entry.key.equals(mEntry.key)) {
301 mEntry = entry;
Lyn Han02cca812019-04-02 16:27:32 -0700302 updateSettingsContentDescription();
Mady Mellorfe7ec032019-01-30 17:32:49 -0800303 updateHeight();
304 } else {
305 Log.w(TAG, "Trying to update entry with different key, new entry: "
306 + entry.key + " old entry: " + mEntry.key);
307 }
308 }
309
Lyn Han02cca812019-04-02 16:27:32 -0700310 void updateTheme() {
311 // Get new colors.
Lyn Han76e803d2019-03-26 17:31:33 -0700312 TypedArray ta = mContext.obtainStyledAttributes(
Lyn Han02cca812019-04-02 16:27:32 -0700313 new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.colorForeground});
Lyn Hanc26ff122019-03-29 16:46:07 -0700314 int backgroundColor = ta.getColor(0, Color.WHITE /* default */);
315 int foregroundColor = ta.getColor(1, Color.BLACK /* default */);
Lyn Han76e803d2019-03-26 17:31:33 -0700316 ta.recycle();
317
Lyn Han02cca812019-04-02 16:27:32 -0700318 // Update triangle color.
319 ShapeDrawable triangleDrawable = new ShapeDrawable(
320 TriangleShape.create(mPointerWidth, mPointerHeight, false /* pointUp */));
321 triangleDrawable.setTint(backgroundColor);
322 mPointerView.setBackground(triangleDrawable);
Lyn Hanf1c9b8b2019-03-14 16:49:48 -0700323 }
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}