Merge "Talkback for focus on collapsed bubble stack" into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4360543283
@@ -18,6 +18,9 @@ package com.android.systemui.bubbles;
|
|||||||
|
|
||||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
|
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ class Bubble {
|
|||||||
|
|
||||||
private final String mKey;
|
private final String mKey;
|
||||||
private final String mGroupId;
|
private final String mGroupId;
|
||||||
|
private String mAppName;
|
||||||
private final BubbleExpandedView.OnBubbleBlockedListener mListener;
|
private final BubbleExpandedView.OnBubbleBlockedListener mListener;
|
||||||
|
|
||||||
private boolean mInflated;
|
private boolean mInflated;
|
||||||
@@ -45,6 +49,7 @@ class Bubble {
|
|||||||
BubbleExpandedView expandedView;
|
BubbleExpandedView expandedView;
|
||||||
private long mLastUpdated;
|
private long mLastUpdated;
|
||||||
private long mLastAccessed;
|
private long mLastAccessed;
|
||||||
|
private PackageManager mPm;
|
||||||
|
|
||||||
public static String groupId(NotificationEntry entry) {
|
public static String groupId(NotificationEntry entry) {
|
||||||
UserHandle user = entry.notification.getUser();
|
UserHandle user = entry.notification.getUser();
|
||||||
@@ -53,16 +58,33 @@ class Bubble {
|
|||||||
|
|
||||||
/** Used in tests when no UI is required. */
|
/** Used in tests when no UI is required. */
|
||||||
@VisibleForTesting(visibility = PRIVATE)
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
Bubble(NotificationEntry e) {
|
Bubble(Context context, NotificationEntry e) {
|
||||||
this (e, null);
|
this (context, e, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) {
|
Bubble(Context context, NotificationEntry e,
|
||||||
|
BubbleExpandedView.OnBubbleBlockedListener listener) {
|
||||||
entry = e;
|
entry = e;
|
||||||
mKey = e.key;
|
mKey = e.key;
|
||||||
mLastUpdated = e.notification.getPostTime();
|
mLastUpdated = e.notification.getPostTime();
|
||||||
mGroupId = groupId(e);
|
mGroupId = groupId(e);
|
||||||
mListener = listener;
|
mListener = listener;
|
||||||
|
|
||||||
|
mPm = context.getPackageManager();
|
||||||
|
ApplicationInfo info;
|
||||||
|
try {
|
||||||
|
info = mPm.getApplicationInfo(
|
||||||
|
entry.notification.getPackageName(),
|
||||||
|
PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||||
|
| PackageManager.MATCH_DISABLED_COMPONENTS
|
||||||
|
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||||
|
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
|
||||||
|
if (info != null) {
|
||||||
|
mAppName = String.valueOf(mPm.getApplicationLabel(info));
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException unused) {
|
||||||
|
mAppName = entry.notification.getPackageName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
@@ -77,6 +99,10 @@ class Bubble {
|
|||||||
return entry.notification.getPackageName();
|
return entry.notification.getPackageName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAppName() {
|
||||||
|
return mAppName;
|
||||||
|
}
|
||||||
|
|
||||||
boolean isInflated() {
|
boolean isInflated() {
|
||||||
return mInflated;
|
return mInflated;
|
||||||
}
|
}
|
||||||
@@ -97,9 +123,9 @@ class Bubble {
|
|||||||
|
|
||||||
expandedView = (BubbleExpandedView) inflater.inflate(
|
expandedView = (BubbleExpandedView) inflater.inflate(
|
||||||
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
|
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
|
||||||
expandedView.setEntry(entry, stackView);
|
expandedView.setEntry(entry, stackView, mAppName);
|
||||||
|
|
||||||
expandedView.setOnBlockedListener(mListener);
|
expandedView.setOnBlockedListener(mListener);
|
||||||
|
|
||||||
mInflated = true;
|
mInflated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
|||||||
if (shouldCollapse) {
|
if (shouldCollapse) {
|
||||||
collapseStack();
|
collapseStack();
|
||||||
}
|
}
|
||||||
updateVisibility();
|
updateStack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,10 +534,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs on state change.
|
||||||
@Override
|
@Override
|
||||||
public void apply() {
|
public void apply() {
|
||||||
mNotificationEntryManager.updateNotifications();
|
mNotificationEntryManager.updateNotifications();
|
||||||
updateVisibility();
|
updateStack();
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "[BubbleData]");
|
Log.d(TAG, "[BubbleData]");
|
||||||
@@ -554,34 +555,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lets any listeners know if bubble state has changed.
|
* Updates the visibility of the bubbles based on current state.
|
||||||
|
* Does not un-bubble, just hides or un-hides. Notifies any
|
||||||
|
* {@link BubbleStateChangeListener}s of visibility changes.
|
||||||
|
* Updates stack description for TalkBack focus.
|
||||||
*/
|
*/
|
||||||
private void updateBubblesShowing() {
|
public void updateStack() {
|
||||||
if (mStackView == null) {
|
if (mStackView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
|
|
||||||
boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
|
|
||||||
mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
|
|
||||||
if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
|
|
||||||
mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the visibility of the bubbles based on current state.
|
|
||||||
* Does not un-bubble, just hides or un-hides. Will notify any
|
|
||||||
* {@link BubbleStateChangeListener}s if visibility changes.
|
|
||||||
*/
|
|
||||||
public void updateVisibility() {
|
|
||||||
if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
|
if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
|
||||||
// Bubbles only appear in unlocked shade
|
// Bubbles only appear in unlocked shade
|
||||||
mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
|
mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
|
||||||
} else if (mStackView != null) {
|
} else if (mStackView != null) {
|
||||||
mStackView.setVisibility(INVISIBLE);
|
mStackView.setVisibility(INVISIBLE);
|
||||||
}
|
}
|
||||||
updateBubblesShowing();
|
|
||||||
|
// Let listeners know if bubble state changed.
|
||||||
|
boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
|
||||||
|
boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
|
||||||
|
mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
|
||||||
|
if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
|
||||||
|
mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
|
||||||
|
}
|
||||||
|
|
||||||
|
mStackView.updateContentDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ public class BubbleData {
|
|||||||
Bubble bubble = getBubbleWithKey(entry.key);
|
Bubble bubble = getBubbleWithKey(entry.key);
|
||||||
if (bubble == null) {
|
if (bubble == null) {
|
||||||
// Create a new bubble
|
// Create a new bubble
|
||||||
bubble = new Bubble(entry, this::onBubbleBlocked);
|
bubble = new Bubble(mContext, entry, this::onBubbleBlocked);
|
||||||
doAdd(bubble);
|
doAdd(bubble);
|
||||||
trim();
|
trim();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -244,9 +244,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
|||||||
/**
|
/**
|
||||||
* Sets the notification entry used to populate this view.
|
* Sets the notification entry used to populate this view.
|
||||||
*/
|
*/
|
||||||
public void setEntry(NotificationEntry entry, BubbleStackView stackView) {
|
public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
|
||||||
mStackView = stackView;
|
mStackView = stackView;
|
||||||
mEntry = entry;
|
mEntry = entry;
|
||||||
|
mAppName = appName;
|
||||||
|
|
||||||
ApplicationInfo info;
|
ApplicationInfo info;
|
||||||
try {
|
try {
|
||||||
@@ -257,12 +258,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
|||||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||||
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
|
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
mAppName = String.valueOf(mPm.getApplicationLabel(info));
|
|
||||||
mAppIcon = mPm.getApplicationIcon(info);
|
mAppIcon = mPm.getApplicationIcon(info);
|
||||||
}
|
}
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
// Ahh... just use package name
|
// Do nothing.
|
||||||
mAppName = entry.notification.getPackageName();
|
|
||||||
}
|
}
|
||||||
if (mAppIcon == null) {
|
if (mAppIcon == null) {
|
||||||
mAppIcon = mPm.getDefaultActivityIcon();
|
mAppIcon = mPm.getDefaultActivityIcon();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.animation.Animator;
|
|||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.ValueAnimator;
|
import android.animation.ValueAnimator;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.ColorMatrix;
|
import android.graphics.ColorMatrix;
|
||||||
@@ -553,6 +554,43 @@ public class BubbleStackView extends FrameLayout {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update content description for a11y TalkBack.
|
||||||
|
*/
|
||||||
|
public void updateContentDescription() {
|
||||||
|
if (mBubbleData.getBubbles().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Bubble topBubble = mBubbleData.getBubbles().get(0);
|
||||||
|
String appName = topBubble.getAppName();
|
||||||
|
Notification notification = topBubble.entry.notification.getNotification();
|
||||||
|
CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
|
||||||
|
String titleStr = getResources().getString(R.string.stream_notification);
|
||||||
|
if (titleCharSeq != null) {
|
||||||
|
titleStr = titleCharSeq.toString();
|
||||||
|
}
|
||||||
|
int moreCount = mBubbleContainer.getChildCount() - 1;
|
||||||
|
|
||||||
|
// Example: Title from app name.
|
||||||
|
String singleDescription = getResources().getString(
|
||||||
|
R.string.bubble_content_description_single, titleStr, appName);
|
||||||
|
|
||||||
|
// Example: Title from app name and 4 more.
|
||||||
|
String stackDescription = getResources().getString(
|
||||||
|
R.string.bubble_content_description_stack, titleStr, appName, moreCount);
|
||||||
|
|
||||||
|
if (mIsExpanded) {
|
||||||
|
// TODO(b/129522932) - update content description for each bubble in expanded view.
|
||||||
|
} else {
|
||||||
|
// Collapsed stack.
|
||||||
|
if (moreCount > 0) {
|
||||||
|
mBubbleContainer.setContentDescription(stackDescription);
|
||||||
|
} else {
|
||||||
|
mBubbleContainer.setContentDescription(singleDescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSystemGestureExcludeRects() {
|
private void updateSystemGestureExcludeRects() {
|
||||||
// Exclude the region occupied by the first BubbleView in the stack
|
// Exclude the region occupied by the first BubbleView in the stack
|
||||||
Rect excludeZone = mSystemGestureExclusionRects.get(0);
|
Rect excludeZone = mSystemGestureExclusionRects.get(0);
|
||||||
|
|||||||
@@ -103,13 +103,13 @@ public class BubbleDataTest extends SysuiTestCase {
|
|||||||
mEntryB3 = createBubbleEntry(1, "b3", "package.b");
|
mEntryB3 = createBubbleEntry(1, "b3", "package.b");
|
||||||
mEntryC1 = createBubbleEntry(1, "c1", "package.c");
|
mEntryC1 = createBubbleEntry(1, "c1", "package.c");
|
||||||
|
|
||||||
mBubbleA1 = new Bubble(mEntryA1);
|
mBubbleA1 = new Bubble(mContext, mEntryA1);
|
||||||
mBubbleA2 = new Bubble(mEntryA2);
|
mBubbleA2 = new Bubble(mContext, mEntryA2);
|
||||||
mBubbleA3 = new Bubble(mEntryA3);
|
mBubbleA3 = new Bubble(mContext, mEntryA3);
|
||||||
mBubbleB1 = new Bubble(mEntryB1);
|
mBubbleB1 = new Bubble(mContext, mEntryB1);
|
||||||
mBubbleB2 = new Bubble(mEntryB2);
|
mBubbleB2 = new Bubble(mContext, mEntryB2);
|
||||||
mBubbleB3 = new Bubble(mEntryB3);
|
mBubbleB3 = new Bubble(mContext, mEntryB3);
|
||||||
mBubbleC1 = new Bubble(mEntryC1);
|
mBubbleC1 = new Bubble(mContext, mEntryC1);
|
||||||
|
|
||||||
mBubbleData = new BubbleData(getContext());
|
mBubbleData = new BubbleData(getContext());
|
||||||
|
|
||||||
@@ -803,4 +803,4 @@ public class BubbleDataTest extends SysuiTestCase {
|
|||||||
private static <T> List<T> listOf(T... a) {
|
private static <T> List<T> listOf(T... a) {
|
||||||
return ImmutableList.copyOf(a);
|
return ImmutableList.copyOf(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user