blob: 2109a7be9ca8cb686982798323298eed3969a34f [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 Han2cf5d902020-04-27 14:40:51 -070024import android.content.Context;
Lyn Hane1cf3b22020-04-01 13:39:43 -070025import android.content.res.Configuration;
Lyn Han4dccbc62020-03-16 23:31:24 -070026import android.content.res.Resources;
Lyn Hancd6465e2019-12-09 15:14:17 -080027import android.content.res.TypedArray;
28import android.graphics.Color;
29import android.os.Bundle;
Lyn Han4dccbc62020-03-16 23:31:24 -070030import android.util.DisplayMetrics;
Lyn Hanb58c7562020-01-07 14:29:20 -080031import android.util.Log;
32import android.view.LayoutInflater;
Lyn Han05ea3f82020-02-05 17:53:08 -080033import android.view.View;
Lyn Hanb58c7562020-01-07 14:29:20 -080034import android.view.ViewGroup;
Lyn Hane5706962020-05-05 13:45:13 -070035import android.view.accessibility.AccessibilityNodeInfo;
Lyn Hane1cf3b22020-04-01 13:39:43 -070036import android.widget.ImageView;
Lyn Han05ea3f82020-02-05 17:53:08 -080037import android.widget.LinearLayout;
Lyn Han4dccbc62020-03-16 23:31:24 -070038import android.widget.TextView;
Lyn Hancd6465e2019-12-09 15:14:17 -080039
40import androidx.recyclerview.widget.GridLayoutManager;
41import androidx.recyclerview.widget.RecyclerView;
42
Lyn Han6e6a52d2020-05-06 13:37:31 -070043import com.android.internal.util.ContrastColorUtil;
Lyn Hancd6465e2019-12-09 15:14:17 -080044import com.android.systemui.R;
45
Lyn Hanb58c7562020-01-07 14:29:20 -080046import java.util.ArrayList;
47import java.util.List;
48import java.util.function.Consumer;
49
Lyn Han6cdc7062020-01-15 13:32:17 -080050import javax.inject.Inject;
51
Lyn Hancd6465e2019-12-09 15:14:17 -080052/**
53 * Activity for showing aged out bubbles.
54 * Must be public to be accessible to androidx...AppComponentFactory
55 */
56public class BubbleOverflowActivity extends Activity {
Lyn Hanb58c7562020-01-07 14:29:20 -080057 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
58
Lyn Han05ea3f82020-02-05 17:53:08 -080059 private LinearLayout mEmptyState;
Lyn Hane1cf3b22020-04-01 13:39:43 -070060 private ImageView mEmptyStateImage;
Lyn Han6cdc7062020-01-15 13:32:17 -080061 private BubbleController mBubbleController;
Lyn Hanb58c7562020-01-07 14:29:20 -080062 private BubbleOverflowAdapter mAdapter;
63 private RecyclerView mRecyclerView;
64 private List<Bubble> mOverflowBubbles = new ArrayList<>();
Lyn Han6cdc7062020-01-15 13:32:17 -080065
Lyn Han2cf5d902020-04-27 14:40:51 -070066 private class NoScrollGridLayoutManager extends GridLayoutManager {
67 NoScrollGridLayoutManager(Context context, int columns) {
68 super(context, columns);
69 }
70 @Override
71 public boolean canScrollVertically() {
Lyn Han79c78132020-05-20 20:05:23 -070072 if (mBubbleController.inLandscape()) {
73 return super.canScrollVertically();
74 }
Lyn Han2cf5d902020-04-27 14:40:51 -070075 return false;
76 }
77 }
78
Lyn Han6cdc7062020-01-15 13:32:17 -080079 @Inject
80 public BubbleOverflowActivity(BubbleController controller) {
81 mBubbleController = controller;
82 }
Lyn Hancd6465e2019-12-09 15:14:17 -080083
84 @Override
85 public void onCreate(Bundle savedInstanceState) {
86 super.onCreate(savedInstanceState);
87 setContentView(R.layout.bubble_overflow_activity);
88 setBackgroundColor();
89
Lyn Han05ea3f82020-02-05 17:53:08 -080090 mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
Lyn Hancd6465e2019-12-09 15:14:17 -080091 mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
Lyn Hane1cf3b22020-04-01 13:39:43 -070092 mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
Lyn Hanb58c7562020-01-07 14:29:20 -080093
Lyn Hanbc985c52020-05-18 17:57:58 -070094 updateDimensions();
95 onDataChanged(mBubbleController.getOverflowBubbles());
96 mBubbleController.setOverflowCallback(() -> {
97 onDataChanged(mBubbleController.getOverflowBubbles());
98 });
99 }
100
101 void updateDimensions() {
Lyn Han4dccbc62020-03-16 23:31:24 -0700102 Resources res = getResources();
103 final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
104 mRecyclerView.setLayoutManager(
Lyn Han2cf5d902020-04-27 14:40:51 -0700105 new NoScrollGridLayoutManager(getApplicationContext(), columns));
Lyn Han4dccbc62020-03-16 23:31:24 -0700106
107 DisplayMetrics displayMetrics = new DisplayMetrics();
108 getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
Lyn Hanbc985c52020-05-18 17:57:58 -0700109
110 final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
111 final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
Lyn Han0b6ca732020-03-31 00:11:04 -0700112 final int viewWidth = recyclerViewWidth / columns;
Lyn Han4dccbc62020-03-16 23:31:24 -0700113
114 final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
115 final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns);
Lyn Han0b6ca732020-03-31 00:11:04 -0700116 final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height)
117 - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
118 final int viewHeight = recyclerViewHeight / rows;
Lyn Han4dccbc62020-03-16 23:31:24 -0700119
Lyn Hane5706962020-05-05 13:45:13 -0700120 mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
Lyn Han4dccbc62020-03-16 23:31:24 -0700121 mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
Lyn Hanb58c7562020-01-07 14:29:20 -0800122 mRecyclerView.setAdapter(mAdapter);
Lyn Hane1cf3b22020-04-01 13:39:43 -0700123 }
124
125 /**
126 * Handle theme changes.
127 */
Lyn Hanbc985c52020-05-18 17:57:58 -0700128 void updateTheme() {
Lyn Hane1cf3b22020-04-01 13:39:43 -0700129 final int mode =
130 getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
131 switch (mode) {
132 case Configuration.UI_MODE_NIGHT_NO:
133 if (DEBUG_OVERFLOW) {
134 Log.d(TAG, "Set overflow UI to light mode");
135 }
136 mEmptyStateImage.setImageDrawable(
137 getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_light));
138 break;
139 case Configuration.UI_MODE_NIGHT_YES:
140 if (DEBUG_OVERFLOW) {
141 Log.d(TAG, "Set overflow UI to dark mode");
142 }
143 mEmptyStateImage.setImageDrawable(
144 getResources().getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
145 break;
146 }
Lyn Hancd6465e2019-12-09 15:14:17 -0800147 }
148
149 void setBackgroundColor() {
150 final TypedArray ta = getApplicationContext().obtainStyledAttributes(
Lyn Hanb58c7562020-01-07 14:29:20 -0800151 new int[]{android.R.attr.colorBackgroundFloating});
Lyn Hancd6465e2019-12-09 15:14:17 -0800152 int bgColor = ta.getColor(0, Color.WHITE);
153 ta.recycle();
154 findViewById(android.R.id.content).setBackgroundColor(bgColor);
155 }
156
Mady Mellor1d082022020-05-12 16:35:39 +0000157 void onDataChanged(List<Bubble> bubbles) {
158 mOverflowBubbles.clear();
159 mOverflowBubbles.addAll(bubbles);
160 mAdapter.notifyDataSetChanged();
Lyn Hanb58c7562020-01-07 14:29:20 -0800161
Mady Mellor1d082022020-05-12 16:35:39 +0000162 if (mOverflowBubbles.isEmpty()) {
163 mEmptyState.setVisibility(View.VISIBLE);
164 } else {
165 mEmptyState.setVisibility(View.GONE);
Lyn Hanb58c7562020-01-07 14:29:20 -0800166 }
Mady Mellor1d082022020-05-12 16:35:39 +0000167
168 if (DEBUG_OVERFLOW) {
169 Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
170 mOverflowBubbles, /*selected*/ null));
171 }
172 }
Lyn Hanb58c7562020-01-07 14:29:20 -0800173
Lyn Hancd6465e2019-12-09 15:14:17 -0800174 @Override
175 public void onStart() {
176 super.onStart();
177 }
178
179 @Override
180 public void onRestart() {
181 super.onRestart();
182 }
183
184 @Override
185 public void onResume() {
186 super.onResume();
Lyn Hanbc985c52020-05-18 17:57:58 -0700187 updateDimensions();
188 updateTheme();
Lyn Hancd6465e2019-12-09 15:14:17 -0800189 }
190
191 @Override
192 public void onPause() {
193 super.onPause();
194 }
195
196 @Override
197 public void onStop() {
198 super.onStop();
199 }
200
201 public void onDestroy() {
Lyn Han6cdc7062020-01-15 13:32:17 -0800202 super.onDestroy();
Lyn Hancd6465e2019-12-09 15:14:17 -0800203 }
204}
Lyn Hanb58c7562020-01-07 14:29:20 -0800205
206class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
Lyn Hane5706962020-05-05 13:45:13 -0700207 private Context mContext;
Lyn Hanb58c7562020-01-07 14:29:20 -0800208 private Consumer<Bubble> mPromoteBubbleFromOverflow;
209 private List<Bubble> mBubbles;
Lyn Han4dccbc62020-03-16 23:31:24 -0700210 private int mWidth;
211 private int mHeight;
Lyn Hanb58c7562020-01-07 14:29:20 -0800212
Lyn Hane5706962020-05-05 13:45:13 -0700213 public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
214 int width, int height) {
215 mContext = context;
Lyn Hanb58c7562020-01-07 14:29:20 -0800216 mBubbles = list;
217 mPromoteBubbleFromOverflow = promoteBubble;
Lyn Han4dccbc62020-03-16 23:31:24 -0700218 mWidth = width;
219 mHeight = height;
Lyn Hanb58c7562020-01-07 14:29:20 -0800220 }
221
222 @Override
223 public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
224 int viewType) {
Lyn Han6e6a52d2020-05-06 13:37:31 -0700225
226 // Set layout for overflow bubble view.
Lyn Han4dccbc62020-03-16 23:31:24 -0700227 LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
228 .inflate(R.layout.bubble_overflow_view, parent, false);
Lyn Hancd4f87e2020-02-19 20:33:45 -0800229 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
230 LinearLayout.LayoutParams.WRAP_CONTENT,
Lyn Han4dccbc62020-03-16 23:31:24 -0700231 LinearLayout.LayoutParams.WRAP_CONTENT);
232 params.width = mWidth;
233 params.height = mHeight;
234 overflowView.setLayoutParams(params);
Lyn Han6e6a52d2020-05-06 13:37:31 -0700235
236 // Ensure name has enough contrast.
237 final TypedArray ta = mContext.obtainStyledAttributes(
238 new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.textColorPrimary});
239 final int bgColor = ta.getColor(0, Color.WHITE);
240 int textColor = ta.getColor(1, Color.BLACK);
241 textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
242 ta.recycle();
243
244 TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
245 viewName.setTextColor(textColor);
246
Lyn Han4dccbc62020-03-16 23:31:24 -0700247 return new ViewHolder(overflowView);
Lyn Hanb58c7562020-01-07 14:29:20 -0800248 }
249
250 @Override
251 public void onBindViewHolder(ViewHolder vh, int index) {
Lyn Han4dccbc62020-03-16 23:31:24 -0700252 Bubble b = mBubbles.get(index);
Lyn Hanb58c7562020-01-07 14:29:20 -0800253
Joshua Tsuji2ed260e2020-03-26 14:26:01 -0400254 vh.iconView.setRenderedBubble(b);
Lyn Han2480d9c2020-04-26 07:19:00 -0700255 vh.iconView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
Lyn Han4dccbc62020-03-16 23:31:24 -0700256 vh.iconView.setOnClickListener(view -> {
257 mBubbles.remove(b);
Lyn Hanb58c7562020-01-07 14:29:20 -0800258 notifyDataSetChanged();
Lyn Han4dccbc62020-03-16 23:31:24 -0700259 mPromoteBubbleFromOverflow.accept(b);
Lyn Hanb58c7562020-01-07 14:29:20 -0800260 });
Lyn Han4dccbc62020-03-16 23:31:24 -0700261
Pinyao Ting3c930612020-05-19 00:26:03 +0000262 String titleStr = b.getTitle();
263 if (titleStr == null) {
264 titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
Lyn Hane5706962020-05-05 13:45:13 -0700265 }
266 vh.iconView.setContentDescription(mContext.getResources().getString(
267 R.string.bubble_content_description_single, titleStr, b.getAppName()));
268
269 vh.iconView.setAccessibilityDelegate(
270 new View.AccessibilityDelegate() {
271 @Override
272 public void onInitializeAccessibilityNodeInfo(View host,
273 AccessibilityNodeInfo info) {
274 super.onInitializeAccessibilityNodeInfo(host, info);
275 // Talkback prompts "Double tap to add back to stack"
276 // instead of the default "Double tap to activate"
277 info.addAction(
278 new AccessibilityNodeInfo.AccessibilityAction(
279 AccessibilityNodeInfo.ACTION_CLICK,
280 mContext.getResources().getString(
281 R.string.bubble_accessibility_action_add_back)));
282 }
283 });
284
Lyn Han4dccbc62020-03-16 23:31:24 -0700285 Bubble.FlyoutMessage message = b.getFlyoutMessage();
286 if (message != null && message.senderName != null) {
287 vh.textView.setText(message.senderName);
288 } else {
289 vh.textView.setText(b.getAppName());
290 }
Lyn Hanb58c7562020-01-07 14:29:20 -0800291 }
292
293 @Override
294 public int getItemCount() {
295 return mBubbles.size();
296 }
297
298 public static class ViewHolder extends RecyclerView.ViewHolder {
Lyn Han4dccbc62020-03-16 23:31:24 -0700299 public BadgedImageView iconView;
300 public TextView textView;
Lyn Hanb58c7562020-01-07 14:29:20 -0800301
Lyn Han4dccbc62020-03-16 23:31:24 -0700302 public ViewHolder(LinearLayout v) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800303 super(v);
Lyn Han4dccbc62020-03-16 23:31:24 -0700304 iconView = v.findViewById(R.id.bubble_view);
305 textView = v.findViewById(R.id.bubble_view_name);
Lyn Hanb58c7562020-01-07 14:29:20 -0800306 }
307 }
308}