blob: 7408fb3de0195e81d4d19e0621471ae034bf15d2 [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 }
Lyn Hanfc19ceb2020-06-07 14:26:08 -070077
78 @Override
79 public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
80 RecyclerView.State state) {
81 int bubbleCount = state.getItemCount();
82 int columnCount = super.getColumnCountForAccessibility(recycler, state);
83 if (bubbleCount < columnCount) {
84 // If there are 4 columns and bubbles <= 3,
85 // TalkBack says "AppName 1 of 4 in list 4 items"
86 // This is a workaround until TalkBack bug is fixed for GridLayoutManager
87 return bubbleCount;
88 }
89 return columnCount;
90 }
Lyn Han2cf5d902020-04-27 14:40:51 -070091 }
92
Lyn Han6cdc7062020-01-15 13:32:17 -080093 @Inject
94 public BubbleOverflowActivity(BubbleController controller) {
95 mBubbleController = controller;
96 }
Lyn Hancd6465e2019-12-09 15:14:17 -080097
98 @Override
99 public void onCreate(Bundle savedInstanceState) {
100 super.onCreate(savedInstanceState);
101 setContentView(R.layout.bubble_overflow_activity);
Lyn Hancd6465e2019-12-09 15:14:17 -0800102
Lyn Han05ea3f82020-02-05 17:53:08 -0800103 mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
Lyn Hancd6465e2019-12-09 15:14:17 -0800104 mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
Lyn Hane1cf3b22020-04-01 13:39:43 -0700105 mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
Lyn Hanb58c7562020-01-07 14:29:20 -0800106
Lyn Hanbc985c52020-05-18 17:57:58 -0700107 updateDimensions();
108 onDataChanged(mBubbleController.getOverflowBubbles());
109 mBubbleController.setOverflowCallback(() -> {
110 onDataChanged(mBubbleController.getOverflowBubbles());
111 });
112 }
113
114 void updateDimensions() {
Lyn Han4dccbc62020-03-16 23:31:24 -0700115 Resources res = getResources();
116 final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
117 mRecyclerView.setLayoutManager(
Lyn Han2cf5d902020-04-27 14:40:51 -0700118 new NoScrollGridLayoutManager(getApplicationContext(), columns));
Lyn Han4dccbc62020-03-16 23:31:24 -0700119
120 DisplayMetrics displayMetrics = new DisplayMetrics();
121 getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
Lyn Hanbc985c52020-05-18 17:57:58 -0700122
123 final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
124 final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
Lyn Han0b6ca732020-03-31 00:11:04 -0700125 final int viewWidth = recyclerViewWidth / columns;
Lyn Han4dccbc62020-03-16 23:31:24 -0700126
127 final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
128 final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns);
Lyn Han0b6ca732020-03-31 00:11:04 -0700129 final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height)
130 - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
131 final int viewHeight = recyclerViewHeight / rows;
Lyn Han4dccbc62020-03-16 23:31:24 -0700132
Lyn Hane5706962020-05-05 13:45:13 -0700133 mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
Lyn Han4dccbc62020-03-16 23:31:24 -0700134 mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
Lyn Hanb58c7562020-01-07 14:29:20 -0800135 mRecyclerView.setAdapter(mAdapter);
Lyn Hane1cf3b22020-04-01 13:39:43 -0700136 }
137
138 /**
139 * Handle theme changes.
140 */
Lyn Hanbc985c52020-05-18 17:57:58 -0700141 void updateTheme() {
Lyn Han79d10bf2020-06-11 14:36:06 -0700142 Resources res = getResources();
143 final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
Lyn Hane1cf3b22020-04-01 13:39:43 -0700144 switch (mode) {
Lyn Hane1cf3b22020-04-01 13:39:43 -0700145 case Configuration.UI_MODE_NIGHT_YES:
Lyn Hane1cf3b22020-04-01 13:39:43 -0700146 mEmptyStateImage.setImageDrawable(
Lyn Han79d10bf2020-06-11 14:36:06 -0700147 res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark));
148 findViewById(android.R.id.content)
149 .setBackgroundColor(res.getColor(R.color.bubbles_dark));
150 break;
151
152 case Configuration.UI_MODE_NIGHT_NO:
153 mEmptyStateImage.setImageDrawable(
154 res.getDrawable(R.drawable.ic_empty_bubble_overflow_light));
155 findViewById(android.R.id.content)
156 .setBackgroundColor(res.getColor(R.color.bubbles_light));
Lyn Hane1cf3b22020-04-01 13:39:43 -0700157 break;
158 }
Lyn Hancd6465e2019-12-09 15:14:17 -0800159 }
160
Mady Mellor1d082022020-05-12 16:35:39 +0000161 void onDataChanged(List<Bubble> bubbles) {
162 mOverflowBubbles.clear();
163 mOverflowBubbles.addAll(bubbles);
164 mAdapter.notifyDataSetChanged();
Lyn Hanb58c7562020-01-07 14:29:20 -0800165
Mady Mellor1d082022020-05-12 16:35:39 +0000166 if (mOverflowBubbles.isEmpty()) {
167 mEmptyState.setVisibility(View.VISIBLE);
168 } else {
169 mEmptyState.setVisibility(View.GONE);
Lyn Hanb58c7562020-01-07 14:29:20 -0800170 }
Mady Mellor1d082022020-05-12 16:35:39 +0000171
172 if (DEBUG_OVERFLOW) {
173 Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
174 mOverflowBubbles, /*selected*/ null));
175 }
176 }
Lyn Hanb58c7562020-01-07 14:29:20 -0800177
Lyn Hancd6465e2019-12-09 15:14:17 -0800178 @Override
179 public void onStart() {
180 super.onStart();
181 }
182
183 @Override
184 public void onRestart() {
185 super.onRestart();
186 }
187
188 @Override
189 public void onResume() {
190 super.onResume();
Lyn Hanbc985c52020-05-18 17:57:58 -0700191 updateDimensions();
192 updateTheme();
Lyn Hancd6465e2019-12-09 15:14:17 -0800193 }
194
195 @Override
196 public void onPause() {
197 super.onPause();
198 }
199
200 @Override
201 public void onStop() {
202 super.onStop();
203 }
204
205 public void onDestroy() {
Lyn Han6cdc7062020-01-15 13:32:17 -0800206 super.onDestroy();
Lyn Hancd6465e2019-12-09 15:14:17 -0800207 }
208}
Lyn Hanb58c7562020-01-07 14:29:20 -0800209
210class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
Lyn Han7d0254c2020-06-01 18:16:38 -0700211 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowAdapter" : TAG_BUBBLES;
212
Lyn Hane5706962020-05-05 13:45:13 -0700213 private Context mContext;
Lyn Hanb58c7562020-01-07 14:29:20 -0800214 private Consumer<Bubble> mPromoteBubbleFromOverflow;
215 private List<Bubble> mBubbles;
Lyn Han4dccbc62020-03-16 23:31:24 -0700216 private int mWidth;
217 private int mHeight;
Lyn Hanb58c7562020-01-07 14:29:20 -0800218
Lyn Hane5706962020-05-05 13:45:13 -0700219 public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
220 int width, int height) {
221 mContext = context;
Lyn Hanb58c7562020-01-07 14:29:20 -0800222 mBubbles = list;
223 mPromoteBubbleFromOverflow = promoteBubble;
Lyn Han4dccbc62020-03-16 23:31:24 -0700224 mWidth = width;
225 mHeight = height;
Lyn Hanb58c7562020-01-07 14:29:20 -0800226 }
227
228 @Override
229 public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
230 int viewType) {
Lyn Han6e6a52d2020-05-06 13:37:31 -0700231
232 // Set layout for overflow bubble view.
Lyn Han4dccbc62020-03-16 23:31:24 -0700233 LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
234 .inflate(R.layout.bubble_overflow_view, parent, false);
Lyn Hancd4f87e2020-02-19 20:33:45 -0800235 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
236 LinearLayout.LayoutParams.WRAP_CONTENT,
Lyn Han4dccbc62020-03-16 23:31:24 -0700237 LinearLayout.LayoutParams.WRAP_CONTENT);
238 params.width = mWidth;
239 params.height = mHeight;
240 overflowView.setLayoutParams(params);
Lyn Han6e6a52d2020-05-06 13:37:31 -0700241
242 // Ensure name has enough contrast.
243 final TypedArray ta = mContext.obtainStyledAttributes(
244 new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.textColorPrimary});
245 final int bgColor = ta.getColor(0, Color.WHITE);
246 int textColor = ta.getColor(1, Color.BLACK);
247 textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
248 ta.recycle();
249
250 TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
251 viewName.setTextColor(textColor);
252
Lyn Han4dccbc62020-03-16 23:31:24 -0700253 return new ViewHolder(overflowView);
Lyn Hanb58c7562020-01-07 14:29:20 -0800254 }
255
256 @Override
257 public void onBindViewHolder(ViewHolder vh, int index) {
Lyn Han4dccbc62020-03-16 23:31:24 -0700258 Bubble b = mBubbles.get(index);
Lyn Hanb58c7562020-01-07 14:29:20 -0800259
Joshua Tsuji2ed260e2020-03-26 14:26:01 -0400260 vh.iconView.setRenderedBubble(b);
Lyn Han2480d9c2020-04-26 07:19:00 -0700261 vh.iconView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
Lyn Han4dccbc62020-03-16 23:31:24 -0700262 vh.iconView.setOnClickListener(view -> {
263 mBubbles.remove(b);
Lyn Hanb58c7562020-01-07 14:29:20 -0800264 notifyDataSetChanged();
Lyn Han4dccbc62020-03-16 23:31:24 -0700265 mPromoteBubbleFromOverflow.accept(b);
Lyn Hanb58c7562020-01-07 14:29:20 -0800266 });
Lyn Han4dccbc62020-03-16 23:31:24 -0700267
Pinyao Ting3c930612020-05-19 00:26:03 +0000268 String titleStr = b.getTitle();
269 if (titleStr == null) {
270 titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
Lyn Hane5706962020-05-05 13:45:13 -0700271 }
272 vh.iconView.setContentDescription(mContext.getResources().getString(
273 R.string.bubble_content_description_single, titleStr, b.getAppName()));
274
275 vh.iconView.setAccessibilityDelegate(
276 new View.AccessibilityDelegate() {
277 @Override
278 public void onInitializeAccessibilityNodeInfo(View host,
279 AccessibilityNodeInfo info) {
280 super.onInitializeAccessibilityNodeInfo(host, info);
281 // Talkback prompts "Double tap to add back to stack"
282 // instead of the default "Double tap to activate"
283 info.addAction(
284 new AccessibilityNodeInfo.AccessibilityAction(
285 AccessibilityNodeInfo.ACTION_CLICK,
286 mContext.getResources().getString(
287 R.string.bubble_accessibility_action_add_back)));
288 }
289 });
290
Mady Mellor1c264ea2020-06-17 16:52:57 -0700291 CharSequence label = b.getShortcutInfo() != null
292 ? b.getShortcutInfo().getLabel()
293 : b.getAppName();
294 vh.textView.setText(label);
Lyn Hanb58c7562020-01-07 14:29:20 -0800295 }
296
297 @Override
298 public int getItemCount() {
299 return mBubbles.size();
300 }
301
302 public static class ViewHolder extends RecyclerView.ViewHolder {
Lyn Han4dccbc62020-03-16 23:31:24 -0700303 public BadgedImageView iconView;
304 public TextView textView;
Lyn Hanb58c7562020-01-07 14:29:20 -0800305
Lyn Han4dccbc62020-03-16 23:31:24 -0700306 public ViewHolder(LinearLayout v) {
Lyn Hanb58c7562020-01-07 14:29:20 -0800307 super(v);
Lyn Han4dccbc62020-03-16 23:31:24 -0700308 iconView = v.findViewById(R.id.bubble_view);
309 textView = v.findViewById(R.id.bubble_view_name);
Lyn Hanb58c7562020-01-07 14:29:20 -0800310 }
311 }
312}