blob: 08ec789bba6aa8eb14e1862583ed097a6ad6e497 [file] [log] [blame]
Lyn Hanb58c7562020-01-07 14:29:20 -08001/*
2 * Copyright (C) 2020 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
Lyn Hancd6465e2019-12-09 15:14:17 -080017package com.android.systemui.bubbles;
18
Lyn Hanb58c7562020-01-07 14:29:20 -080019import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
20import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
21import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
22
Lyn Hancd6465e2019-12-09 15:14:17 -080023import android.app.Activity;
Lyn Hane5706962020-05-05 13:45:13 -070024import android.app.Notification;
Lyn Han2cf5d902020-04-27 14:40:51 -070025import android.content.Context;
Lyn Hane1cf3b22020-04-01 13:39:43 -070026import android.content.res.Configuration;
Lyn Han4dccbc62020-03-16 23:31:24 -070027import android.content.res.Resources;
Lyn Hancd6465e2019-12-09 15:14:17 -080028import android.content.res.TypedArray;
29import android.graphics.Color;
30import android.os.Bundle;
Lyn Han4dccbc62020-03-16 23:31:24 -070031import android.util.DisplayMetrics;
Lyn Hanb58c7562020-01-07 14:29:20 -080032import android.util.Log;
33import android.view.LayoutInflater;
Lyn Han05ea3f82020-02-05 17:53:08 -080034import android.view.View;
Lyn Hanb58c7562020-01-07 14:29:20 -080035import android.view.ViewGroup;
Lyn Hane5706962020-05-05 13:45:13 -070036import android.view.accessibility.AccessibilityNodeInfo;
Lyn Hane1cf3b22020-04-01 13:39:43 -070037import android.widget.ImageView;
Lyn Han05ea3f82020-02-05 17:53:08 -080038import android.widget.LinearLayout;
Lyn Han4dccbc62020-03-16 23:31:24 -070039import android.widget.TextView;
Lyn Hancd6465e2019-12-09 15:14:17 -080040
41import androidx.recyclerview.widget.GridLayoutManager;
42import androidx.recyclerview.widget.RecyclerView;
43
Lyn Han6e6a52d2020-05-06 13:37:31 -070044import com.android.internal.util.ContrastColorUtil;
Lyn Hancd6465e2019-12-09 15:14:17 -080045import com.android.systemui.R;
46
Lyn Hanb58c7562020-01-07 14:29:20 -080047import java.util.ArrayList;
48import java.util.List;
49import java.util.function.Consumer;
50
Lyn Han6cdc7062020-01-15 13:32:17 -080051import javax.inject.Inject;
52
Lyn Hancd6465e2019-12-09 15:14:17 -080053/**
54 * Activity for showing aged out bubbles.
55 * Must be public to be accessible to androidx...AppComponentFactory
56 */
57public class BubbleOverflowActivity extends Activity {
Lyn Hanb58c7562020-01-07 14:29:20 -080058 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
59
Lyn Han05ea3f82020-02-05 17:53:08 -080060 private LinearLayout mEmptyState;
Lyn Hane1cf3b22020-04-01 13:39:43 -070061 private ImageView mEmptyStateImage;
Lyn Han6cdc7062020-01-15 13:32:17 -080062 private BubbleController mBubbleController;
Lyn Hanb58c7562020-01-07 14:29:20 -080063 private BubbleOverflowAdapter mAdapter;
64 private RecyclerView mRecyclerView;
65 private List<Bubble> mOverflowBubbles = new ArrayList<>();
Lyn Han6cdc7062020-01-15 13:32:17 -080066
Lyn Han2cf5d902020-04-27 14:40:51 -070067 private class NoScrollGridLayoutManager extends GridLayoutManager {
68 NoScrollGridLayoutManager(Context context, int columns) {
69 super(context, columns);
70 }
71 @Override
72 public boolean canScrollVertically() {
73 return false;
74 }
75 }
76
Lyn Han6cdc7062020-01-15 13:32:17 -080077 @Inject
78 public BubbleOverflowActivity(BubbleController controller) {
79 mBubbleController = controller;
80 }
Lyn Hancd6465e2019-12-09 15:14:17 -080081
82 @Override
83 public void onCreate(Bundle savedInstanceState) {
84 super.onCreate(savedInstanceState);
85 setContentView(R.layout.bubble_overflow_activity);
86 setBackgroundColor();
87
Lyn Han05ea3f82020-02-05 17:53:08 -080088 mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
Lyn Hancd6465e2019-12-09 15:14:17 -080089 mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
Lyn Hane1cf3b22020-04-01 13:39:43 -070090 mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
Lyn Hanb58c7562020-01-07 14:29:20 -080091
Lyn Han4dccbc62020-03-16 23:31:24 -070092 Resources res = getResources();
93 final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
94 mRecyclerView.setLayoutManager(
Lyn Han2cf5d902020-04-27 14:40:51 -070095 new NoScrollGridLayoutManager(getApplicationContext(), columns));
Lyn Han4dccbc62020-03-16 23:31:24 -070096
97 DisplayMetrics displayMetrics = new DisplayMetrics();
98 getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
Lyn Han0b6ca732020-03-31 00:11:04 -070099 final int recyclerViewWidth = (displayMetrics.widthPixels
100 - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding));
101 final int viewWidth = recyclerViewWidth / columns;
Lyn Han4dccbc62020-03-16 23:31:24 -0700102
103 final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
104 final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns);
Lyn Han0b6ca732020-03-31 00:11:04 -0700105 final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height)
106 - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
107 final int viewHeight = recyclerViewHeight / rows;
Lyn Han4dccbc62020-03-16 23:31:24 -0700108
Lyn Hane5706962020-05-05 13:45:13 -0700109 mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
Lyn Han4dccbc62020-03-16 23:31:24 -0700110 mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
Lyn Hanb58c7562020-01-07 14:29:20 -0800111 mRecyclerView.setAdapter(mAdapter);
Mady Mellor1d082022020-05-12 16:35:39 +0000112 onDataChanged(mBubbleController.getOverflowBubbles());
113 mBubbleController.setOverflowCallback(() -> {
114 onDataChanged(mBubbleController.getOverflowBubbles());
115 });
116 onThemeChanged();
Lyn Hane1cf3b22020-04-01 13:39:43 -0700117 }
118
119 /**
120 * Handle theme changes.
121 */
122 void onThemeChanged() {
123 final int mode =
124 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
125 switch (mode) {
126 case Configuration.UI_MODE_NIGHT_NO:
127 if (DEBUG_OVERFLOW) {
128 Log.d(TAG, "Set overflow UI to light mode");
129 }
130 mEmptyStateImage.setImageDrawable(
131 getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light));
132 break;
133 case Configuration.UI_MODE_NIGHT_YES:
134 if (DEBUG_OVERFLOW) {
135 Log.d(TAG, "Set overflow UI to dark mode");
136 }
137 mEmptyStateImage.setImageDrawable(
138 getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
139 break;
140 }
Lyn Hancd6465e2019-12-09 15:14:17 -0800141 }
142
143 void setBackgroundColor() {
144 final TypedArray ta = getApplicationContext().obtainStyledAttributes(
Lyn Hanb58c7562020-01-07 14:29:20 -0800145 new int[]{android.R.attr.colorBackgroundFloating});
Lyn Hancd6465e2019-12-09 15:14:17 -0800146 int bgColor = ta.getColor(0, Color.WHITE);
147 ta.recycle();
148 findViewById(android.R.id.content).setBackgroundColor(bgColor);
149 }
150
Mady Mellor1d082022020-05-12 16:35:39 +0000151 void onDataChanged(List<Bubble> bubbles) {
152 mOverflowBubbles.clear();
153 mOverflowBubbles.addAll(bubbles);
154 mAdapter.notifyDataSetChanged();
Lyn Hanb58c7562020-01-07 14:29:20 -0800155
Mady Mellor1d082022020-05-12 16:35:39 +0000156 if (mOverflowBubbles.isEmpty()) {
157 mEmptyState.setVisibility(View.VISIBLE);
158 } else {
159 mEmptyState.setVisibility(View.GONE);
Lyn Hanb58c7562020-01-07 14:29:20 -0800160 }
Mady Mellor1d082022020-05-12 16:35:39 +0000161
162 if (DEBUG_OVERFLOW) {
163 Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
164 mOverflowBubbles, /*selected*/ null));
165 }
166 }
Lyn Hanb58c7562020-01-07 14:29:20 -0800167
Lyn Hancd6465e2019-12-09 15:14:17 -0800168 @Override
169 public void onStart() {
170 super.onStart();
171 }
172
173 @Override
174 public void onRestart() {
175 super.onRestart();
176 }
177
178 @Override
179 public void onResume() {
180 super.onResume();
Lyn Hane1cf3b22020-04-01 13:39:43 -0700181 onThemeChanged();
Lyn Hancd6465e2019-12-09 15:14:17 -0800182 }
183
184 @Override
185 public void onPause() {
186 super.onPause();
187 }
188
189 @Override
190 public void onStop() {
191 super.onStop();
192 }
193
194 public void onDestroy() {
Lyn Han6cdc7062020-01-15 13:32:17 -0800195 super.onDestroy();
Lyn Hancd6465e2019-12-09 15:14:17 -0800196 }
197}
Lyn Hanb58c7562020-01-07 14:29:20 -0800198
199class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
Lyn Hane5706962020-05-05 13:45:13 -0700200 private Context mContext;
Lyn Hanb58c7562020-01-07 14:29:20 -0800201 private Consumer<Bubble> mPromoteBubbleFromOverflow;
202 private List<Bubble> mBubbles;
Lyn Han4dccbc62020-03-16 23:31:24 -0700203 private int mWidth;
204 private int mHeight;
Lyn Hanb58c7562020-01-07 14:29:20 -0800205
Lyn Hane5706962020-05-05 13:45:13 -0700206 public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
207 int width, int height) {
208 mContext = context;
Lyn Hanb58c7562020-01-07 14:29:20 -0800209 mBubbles = list;
210 mPromoteBubbleFromOverflow = promoteBubble;
Lyn Han4dccbc62020-03-16 23:31:24 -0700211 mWidth = width;
212 mHeight = height;
Lyn Hanb58c7562020-01-07 14:29:20 -0800213 }
214
215 @Override
216 public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
217 int viewType) {
Lyn Han6e6a52d2020-05-06 13:37:31 -0700218
219 // Set layout for overflow bubble view.
Lyn Han4dccbc62020-03-16 23:31:24 -0700220 LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
221 .inflate(R.layout.bubble_overflow_view, parent, false);
Lyn Hancd4f87e2020-02-19 20:33:45 -0800222 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
223 LinearLayout.LayoutParams.WRAP_CONTENT,
Lyn Han4dccbc62020-03-16 23:31:24 -0700224 LinearLayout.LayoutParams.WRAP_CONTENT);
225 params.width = mWidth;
226 params.height = mHeight;
227 overflowView.setLayoutParams(params);
Lyn Han6e6a52d2020-05-06 13:37:31 -0700228
229 // Ensure name has enough contrast.
230 final TypedArray ta = mContext.obtainStyledAttributes(
231 new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.textColorPrimary});
232 final int bgColor = ta.getColor(0, Color.WHITE);
233 int textColor = ta.getColor(1, Color.BLACK);
234 textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
235 ta.recycle();
236
237 TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
238 viewName.setTextColor(textColor);
239
Lyn Han4dccbc62020-03-16 23:31:24 -0700240 return new ViewHolder(overflowView);
Lyn Hanb58c7562020-01-07 14:29:20 -0800241 }
242
243 @Override
244 public void onBindViewHolder(ViewHolder vh, int index) {
Lyn Han4dccbc62020-03-16 23:31:24 -0700245 Bubble b = mBubbles.get(index);
Lyn Hanb58c7562020-01-07 14:29:20 -0800246
Joshua Tsuji2ed260e2020-03-26 14:26:01 -0400247 vh.iconView.setRenderedBubble(b);
Lyn Han2480d9c2020-04-26 07:19:00 -0700248 vh.iconView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
Lyn Han4dccbc62020-03-16 23:31:24 -0700249 vh.iconView.setOnClickListener(view -> {
250 mBubbles.remove(b);
Lyn Hanb58c7562020-01-07 14:29:20 -0800251 notifyDataSetChanged();
Lyn Han4dccbc62020-03-16 23:31:24 -0700252 mPromoteBubbleFromOverflow.accept(b);
Lyn Hanb58c7562020-01-07 14:29:20 -0800253 });
Lyn Han4dccbc62020-03-16 23:31:24 -0700254
Lyn Hane5706962020-05-05 13:45:13 -0700255 final CharSequence titleCharSeq =
256 b.getEntry().getSbn().getNotification().extras.getCharSequence(
257 Notification.EXTRA_TITLE);
258 String titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
259 if (titleCharSeq != null) {
260 titleStr = titleCharSeq.toString();
261 }
262 vh.iconView.setContentDescription(mContext.getResources().getString(
263 R.string.bubble_content_description_single, titleStr, b.getAppName()));
264
265 vh.iconView.setAccessibilityDelegate(
266 new View.AccessibilityDelegate() {
267 @Override
268 public void onInitializeAccessibilityNodeInfo(View host,
269 AccessibilityNodeInfo info) {
270 super.onInitializeAccessibilityNodeInfo(host, info);
271 // Talkback prompts "Double tap to add back to stack"
272 // instead of the default "Double tap to activate"
273 info.addAction(
274 new AccessibilityNodeInfo.AccessibilityAction(
275 AccessibilityNodeInfo.ACTION_CLICK,
276 mContext.getResources().getString(
277 R.string.bubble_accessibility_action_add_back)));
278 }
279 });
280
Lyn Han4dccbc62020-03-16 23:31:24 -0700281 Bubble.FlyoutMessage message = b.getFlyoutMessage();
282 if (message != null && message.senderName != null) {
283 vh.textView.setText(message.senderName);
284 } else {
285 vh.textView.setText(b.getAppName());
286 }
Lyn Hanb58c7562020-01-07 14:29:20 -0800287 }
288
289 @Override
290 public int getItemCount() {
291 return mBubbles.size();
292 }
293
294 public static class ViewHolder extends RecyclerView.ViewHolder {
Lyn Han4dccbc62020-03-16 23:31:24 -0700295 public BadgedImageView iconView;
296 public TextView textView;
Lyn Hanb58c7562020-01-07 14:29:20 -0800297
Lyn Han4dccbc62020-03-16 23:31:24 -0700298 public ViewHolder(LinearLayout v) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800299 super(v);
Lyn Han4dccbc62020-03-16 23:31:24 -0700300 iconView = v.findViewById(R.id.bubble_view);
301 textView = v.findViewById(R.id.bubble_view_name);
Lyn Hanb58c7562020-01-07 14:29:20 -0800302 }
303 }
304}