Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.systemui.bubbles; |
| 18 | |
| 19 | import android.annotation.Nullable; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 20 | import android.app.Notification; |
| 21 | import android.app.PendingIntent; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 22 | import android.content.Context; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 23 | import android.content.Intent; |
| 24 | import android.content.pm.ApplicationInfo; |
| 25 | import android.content.pm.PackageManager; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 26 | import android.content.res.Resources; |
Mady Mellor | dd49705 | 2019-01-30 17:23:48 -0800 | [diff] [blame] | 27 | import android.content.res.TypedArray; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 28 | import android.graphics.Color; |
| 29 | import android.graphics.drawable.ShapeDrawable; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 30 | import android.provider.Settings; |
Mady Mellor | 3f2efdb | 2018-11-21 11:30:45 -0800 | [diff] [blame] | 31 | import android.text.TextUtils; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 32 | import android.util.AttributeSet; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 33 | import android.util.Log; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 34 | import android.view.View; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 35 | import android.widget.ImageButton; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 36 | import android.widget.LinearLayout; |
Mark Renouf | 89b1a4a | 2018-12-04 14:59:45 -0500 | [diff] [blame] | 37 | import android.widget.TextView; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 38 | |
| 39 | import com.android.systemui.R; |
| 40 | import com.android.systemui.recents.TriangleShape; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 41 | import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 42 | |
| 43 | /** |
| 44 | * Container for the expanded bubble view, handles rendering the caret and header of the view. |
| 45 | */ |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 46 | public class BubbleExpandedViewContainer extends LinearLayout implements View.OnClickListener { |
| 47 | private static final String TAG = "BubbleExpandedView"; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 48 | |
| 49 | // The triangle pointing to the expanded view |
| 50 | private View mPointerView; |
Mark Renouf | 89b1a4a | 2018-12-04 14:59:45 -0500 | [diff] [blame] | 51 | // The view displayed between the pointer and the expanded view |
| 52 | private TextView mHeaderView; |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 53 | // Tappable header icon deeplinking into the app |
| 54 | private ImageButton mDeepLinkIcon; |
| 55 | // Tappable header icon deeplinking into notification settings |
| 56 | private ImageButton mSettingsIcon; |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 57 | // The view that is being displayed for the expanded state |
| 58 | private View mExpandedView; |
| 59 | |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 60 | private NotificationEntry mEntry; |
| 61 | private PackageManager mPm; |
| 62 | private String mAppName; |
| 63 | |
| 64 | // Need reference to let it know to collapse when new task is launched |
| 65 | private BubbleStackView mStackView; |
| 66 | |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 67 | public BubbleExpandedViewContainer(Context context) { |
| 68 | this(context, null); |
| 69 | } |
| 70 | |
| 71 | public BubbleExpandedViewContainer(Context context, AttributeSet attrs) { |
| 72 | this(context, attrs, 0); |
| 73 | } |
| 74 | |
| 75 | public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr) { |
| 76 | this(context, attrs, defStyleAttr, 0); |
| 77 | } |
| 78 | |
| 79 | public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr, |
| 80 | int defStyleRes) { |
| 81 | super(context, attrs, defStyleAttr, defStyleRes); |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 82 | mPm = context.getPackageManager(); |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 83 | } |
| 84 | |
| 85 | @Override |
| 86 | protected void onFinishInflate() { |
| 87 | super.onFinishInflate(); |
| 88 | |
| 89 | Resources res = getResources(); |
| 90 | mPointerView = findViewById(R.id.pointer_view); |
| 91 | int width = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); |
| 92 | int height = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); |
Mady Mellor | dd49705 | 2019-01-30 17:23:48 -0800 | [diff] [blame] | 93 | |
| 94 | TypedArray ta = getContext().obtainStyledAttributes( |
| 95 | new int[] {android.R.attr.colorBackgroundFloating}); |
| 96 | int bgColor = ta.getColor(0, Color.WHITE); |
| 97 | ta.recycle(); |
| 98 | |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 99 | ShapeDrawable triangleDrawable = new ShapeDrawable( |
| 100 | TriangleShape.create(width, height, true /* pointUp */)); |
Mady Mellor | dd49705 | 2019-01-30 17:23:48 -0800 | [diff] [blame] | 101 | triangleDrawable.setTint(bgColor); |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 102 | mPointerView.setBackground(triangleDrawable); |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 103 | |
| 104 | mHeaderView = findViewById(R.id.header_text); |
| 105 | mDeepLinkIcon = findViewById(R.id.deep_link_button); |
| 106 | mSettingsIcon = findViewById(R.id.settings_button); |
| 107 | mDeepLinkIcon.setOnClickListener(this); |
| 108 | mSettingsIcon.setOnClickListener(this); |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Sets the notification entry used to populate this view. |
| 113 | */ |
| 114 | public void setEntry(NotificationEntry entry, BubbleStackView stackView) { |
| 115 | mStackView = stackView; |
| 116 | mEntry = entry; |
| 117 | |
| 118 | ApplicationInfo info; |
| 119 | try { |
| 120 | info = mPm.getApplicationInfo( |
| 121 | entry.notification.getPackageName(), |
| 122 | PackageManager.MATCH_UNINSTALLED_PACKAGES |
| 123 | | PackageManager.MATCH_DISABLED_COMPONENTS |
| 124 | | PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
| 125 | | PackageManager.MATCH_DIRECT_BOOT_AWARE); |
| 126 | if (info != null) { |
| 127 | mAppName = String.valueOf(mPm.getApplicationLabel(info)); |
| 128 | } |
| 129 | } catch (PackageManager.NameNotFoundException e) { |
| 130 | // Ahh... just use package name |
| 131 | mAppName = entry.notification.getPackageName(); |
| 132 | } |
| 133 | |
| 134 | updateHeaderView(); |
| 135 | } |
| 136 | |
| 137 | private void updateHeaderView() { |
| 138 | mSettingsIcon.setContentDescription(getResources().getString( |
| 139 | R.string.bubbles_settings_button_description, mAppName)); |
| 140 | mDeepLinkIcon.setContentDescription(getResources().getString( |
| 141 | R.string.bubbles_deep_link_button_description, mAppName)); |
| 142 | if (mEntry != null && mEntry.getBubbleMetadata() != null) { |
| 143 | setHeaderText(mEntry.getBubbleMetadata().getTitle()); |
| 144 | } else { |
| 145 | // This should only happen if we're auto-bubbling notification content that isn't |
| 146 | // explicitly a bubble |
| 147 | setHeaderText(mAppName); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | @Override |
| 152 | public void onClick(View view) { |
| 153 | if (mEntry == null) { |
| 154 | return; |
| 155 | } |
| 156 | Notification n = mEntry.notification.getNotification(); |
| 157 | int id = view.getId(); |
| 158 | if (id == R.id.deep_link_button) { |
| 159 | mStackView.collapseStack(() -> { |
| 160 | try { |
| 161 | n.contentIntent.send(); |
| 162 | } catch (PendingIntent.CanceledException e) { |
| 163 | Log.w(TAG, "Failed to send intent for bubble with key: " |
| 164 | + (mEntry != null ? mEntry.key : " null entry")); |
| 165 | } |
| 166 | }); |
| 167 | } else if (id == R.id.settings_button) { |
| 168 | Intent intent = getSettingsIntent(mEntry.notification.getPackageName(), |
| 169 | mEntry.notification.getUid()); |
| 170 | mStackView.collapseStack(() -> mContext.startActivity(intent)); |
| 171 | } |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Set the x position that the tip of the triangle should point to. |
| 176 | */ |
| 177 | public void setPointerPosition(int x) { |
| 178 | // Adjust for the pointer size |
| 179 | x -= (mPointerView.getWidth() / 2); |
| 180 | mPointerView.setTranslationX(x); |
| 181 | } |
| 182 | |
| 183 | /** |
Mark Renouf | 89b1a4a | 2018-12-04 14:59:45 -0500 | [diff] [blame] | 184 | * Set the text displayed within the header. |
| 185 | */ |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 186 | private void setHeaderText(CharSequence text) { |
Mark Renouf | 89b1a4a | 2018-12-04 14:59:45 -0500 | [diff] [blame] | 187 | mHeaderView.setText(text); |
Mady Mellor | 3f2efdb | 2018-11-21 11:30:45 -0800 | [diff] [blame] | 188 | mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE); |
Mark Renouf | 89b1a4a | 2018-12-04 14:59:45 -0500 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | /** |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 192 | * Set the view to display for the expanded state. Passing null will clear the view. |
| 193 | */ |
| 194 | public void setExpandedView(View view) { |
Mark Renouf | 89b1a4a | 2018-12-04 14:59:45 -0500 | [diff] [blame] | 195 | if (mExpandedView == view) { |
| 196 | return; |
| 197 | } |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 198 | if (mExpandedView != null) { |
| 199 | removeView(mExpandedView); |
| 200 | } |
| 201 | mExpandedView = view; |
| 202 | if (mExpandedView != null) { |
| 203 | addView(mExpandedView); |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * @return the view containing the expanded content, can be null. |
| 209 | */ |
| 210 | @Nullable |
| 211 | public View getExpandedView() { |
| 212 | return mExpandedView; |
| 213 | } |
Mady Mellor | 9801e85 | 2019-01-22 14:50:28 -0800 | [diff] [blame] | 214 | |
| 215 | private Intent getSettingsIntent(String packageName, final int appUid) { |
| 216 | final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); |
| 217 | intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); |
| 218 | intent.putExtra(Settings.EXTRA_APP_UID, appUid); |
| 219 | intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); |
| 220 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 221 | return intent; |
| 222 | } |
Mady Mellor | dea7ecf | 2018-12-10 15:47:40 -0800 | [diff] [blame] | 223 | } |