summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp5
-rw-r--r--aconfig/FeatureFlags.aconfig12
-rw-r--r--java/res/color/resolver_profile_tab_text.xml2
-rw-r--r--java/res/drawable/resolver_profile_tab_bg.xml2
-rw-r--r--java/res/layout/chooser_row.xml1
-rw-r--r--java/res/layout/chooser_row_direct_share.xml1
-rw-r--r--java/res/layout/resolver_profile_tab_button.xml1
-rw-r--r--java/res/values-iw/strings.xml2
-rw-r--r--java/res/values/strings.xml10
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java9
-rw-r--r--java/src/com/android/intentresolver/ChooserGridLayoutManager.java131
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java7
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java3
-rw-r--r--java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java9
-rw-r--r--java/src/com/android/intentresolver/data/model/ChooserRequest.kt4
-rw-r--r--java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt4
-rw-r--r--java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt17
-rw-r--r--tests/unit/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt77
-rw-r--r--tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt8
-rw-r--r--tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt53
20 files changed, 329 insertions, 29 deletions
diff --git a/Android.bp b/Android.bp
index 3102f8fa..9dccb9f1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -53,6 +53,7 @@ android_library {
"androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel-ktx",
"dagger2",
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"hilt_android",
"IntentResolverFlagsLib",
"iconloader",
@@ -78,8 +79,8 @@ android_library {
"-Adagger.strictMultibindingValidation=enabled",
],
aidl: {
- local_include_dirs: [ "java/aidl" ],
- }
+ local_include_dirs: ["java/aidl"],
+ },
}
java_defaults {
diff --git a/aconfig/FeatureFlags.aconfig b/aconfig/FeatureFlags.aconfig
index bf6109a8..788a22e2 100644
--- a/aconfig/FeatureFlags.aconfig
+++ b/aconfig/FeatureFlags.aconfig
@@ -6,6 +6,16 @@ container: "system"
# bug: "Feature_Bug_#" or "<none>"
flag {
+ name: "announce_shortcuts_and_suggested_apps"
+ namespace: "intentresolver"
+ description: "Enable talkback announcement for the app shortcuts and the suggested apps target groups."
+ bug: "379208685"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "individual_metadata_title_read"
namespace: "intentresolver"
description: "Enables separate title URI metadata calls"
@@ -26,7 +36,7 @@ flag {
}
flag {
- name: "fix_shortcuts_flashing"
+ name: "fix_shortcuts_flashing_fixed"
namespace: "intentresolver"
description: "Do not flash shortcuts on payload selection change"
bug: "343300158"
diff --git a/java/res/color/resolver_profile_tab_text.xml b/java/res/color/resolver_profile_tab_text.xml
index ffeba854..f6a4eadf 100644
--- a/java/res/color/resolver_profile_tab_text.xml
+++ b/java/res/color/resolver_profile_tab_text.xml
@@ -16,5 +16,5 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:color="@androidprv:color/materialColorOnPrimary" android:state_selected="true"/>
- <item android:color="@androidprv:color/materialColorOnSurfaceVariant"/>
+ <item android:color="@androidprv:color/materialColorOnSurface"/>
</selector>
diff --git a/java/res/drawable/resolver_profile_tab_bg.xml b/java/res/drawable/resolver_profile_tab_bg.xml
index 20f0be92..392f7e30 100644
--- a/java/res/drawable/resolver_profile_tab_bg.xml
+++ b/java/res/drawable/resolver_profile_tab_bg.xml
@@ -29,7 +29,7 @@
<item android:state_selected="false">
<shape android:shape="rectangle">
<corners android:radius="12dp" />
- <solid android:color="@androidprv:color/materialColorSurfaceContainerHighest" />
+ <solid android:color="@androidprv:color/materialColorSurfaceBright" />
</shape>
</item>
diff --git a/java/res/layout/chooser_row.xml b/java/res/layout/chooser_row.xml
index bbe65a85..3fe1ee7d 100644
--- a/java/res/layout/chooser_row.xml
+++ b/java/res/layout/chooser_row.xml
@@ -18,6 +18,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/suggested_apps_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="100dp"
diff --git a/java/res/layout/chooser_row_direct_share.xml b/java/res/layout/chooser_row_direct_share.xml
index d7e36eed..53e666a6 100644
--- a/java/res/layout/chooser_row_direct_share.xml
+++ b/java/res/layout/chooser_row_direct_share.xml
@@ -17,6 +17,7 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/shortcuts_container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="200dp">
diff --git a/java/res/layout/resolver_profile_tab_button.xml b/java/res/layout/resolver_profile_tab_button.xml
index 52a1aacf..7404dc33 100644
--- a/java/res/layout/resolver_profile_tab_button.xml
+++ b/java/res/layout/resolver_profile_tab_button.xml
@@ -17,7 +17,6 @@
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 3c1be527..43921c78 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -106,5 +106,5 @@
<string name="selectable_image" msgid="3157858923437182271">"תמונה שניתן לבחור"</string>
<string name="selectable_video" msgid="1271768647699300826">"סרטון שניתן לבחור"</string>
<string name="selectable_item" msgid="7557320816744205280">"פריט שניתן לבחור"</string>
- <string name="role_description_button" msgid="4537198530568333649">"לחצן"</string>
+ <string name="role_description_button" msgid="4537198530568333649">"כפתור"</string>
</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 2261a4a8..6504462f 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -340,4 +340,14 @@
<string name="selectable_item">Selectable item</string>
<!-- Accessibility role description for a11y on button. [CHAR LIMIT=NONE] -->
<string name="role_description_button">Button</string>
+
+ <!-- Accessibility announcement for the shortcut group (https://developer.android.com/training/sharing/direct-share-targets)
+ in the list of targets. [CHAR LIMIT=NONE]-->
+ <string name="shortcut_group_a11y_title">Direct share targets</string>
+ <!-- Accessibility announcement for the suggested application group in the list of targets.
+ [CHAR LIMIT=NONE] -->
+ <string name="suggested_apps_group_a11y_title">App suggestions</string>
+ <!-- Accessibility announcement for the all-applications group in the list of targets.
+ [CHAR LIMIT=NONE] -->
+ <string name="all_apps_group_a11y_title">App list</string>
</resources>
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index d81adfba..d4cf82ff 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -23,7 +23,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE
import static androidx.lifecycle.LifecycleKt.getCoroutineScope;
import static com.android.intentresolver.ChooserActionFactory.EDIT_SOURCE;
-import static com.android.intentresolver.Flags.fixShortcutsFlashing;
+import static com.android.intentresolver.Flags.fixShortcutsFlashingFixed;
import static com.android.intentresolver.Flags.interactiveSession;
import static com.android.intentresolver.Flags.keyboardNavigationFix;
import static com.android.intentresolver.Flags.rebuildAdaptersOnTargetPinning;
@@ -871,7 +871,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
postRebuildList(
mChooserMultiProfilePagerAdapter.rebuildTabs(
mProfiles.getWorkProfilePresent() || mProfiles.getPrivateProfilePresent()));
- if (fixShortcutsFlashing() && oldPagerAdapter != null) {
+ if (fixShortcutsFlashingFixed() && oldPagerAdapter != null) {
for (int i = 0, count = mChooserMultiProfilePagerAdapter.getCount(); i < count; i++) {
ChooserListAdapter listAdapter =
mChooserMultiProfilePagerAdapter.getPageAdapterForIndex(i)
@@ -2497,7 +2497,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
if (duration >= 0) {
Log.d(TAG, "app target loading time " + duration + " ms");
}
- if (!fixShortcutsFlashing()) {
+ if (!fixShortcutsFlashingFixed()) {
addCallerChooserTargets(chooserListAdapter);
}
getEventLog().logSharesheetAppLoadComplete();
@@ -2529,8 +2529,9 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
ChooserListAdapter adapter =
mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle);
if (adapter != null) {
- if (fixShortcutsFlashing()) {
+ if (fixShortcutsFlashingFixed()) {
adapter.setDirectTargetsEnabled(true);
+ adapter.resetDirectTargets();
addCallerChooserTargets(adapter);
}
for (ShortcutLoader.ShortcutResultInfo resultInfo : result.getShortcutsByApp()) {
diff --git a/java/src/com/android/intentresolver/ChooserGridLayoutManager.java b/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
index aaa7554c..5bbb6c24 100644
--- a/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
+++ b/java/src/com/android/intentresolver/ChooserGridLayoutManager.java
@@ -16,18 +16,35 @@
package com.android.intentresolver;
+import static com.android.intentresolver.Flags.announceShortcutsAndSuggestedApps;
+
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.intentresolver.grid.ChooserGridAdapter;
+
/**
* For a11y and per {@link RecyclerView#onInitializeAccessibilityNodeInfo}, override
* methods to ensure proper row counts.
*/
public class ChooserGridLayoutManager extends GridLayoutManager {
+ private CharSequence mShortcutGroupTitle = "";
+ private CharSequence mSuggestedAppsGroupTitle = "";
+ private CharSequence mAllAppListGroupTitle = "";
+ @Nullable
+ private RecyclerView mRecyclerView;
private boolean mVerticalScrollEnabled = true;
/**
@@ -39,6 +56,9 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
public ChooserGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ if (announceShortcutsAndSuggestedApps()) {
+ readGroupTitles(context);
+ }
}
/**
@@ -49,6 +69,9 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
*/
public ChooserGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
+ if (announceShortcutsAndSuggestedApps()) {
+ readGroupTitles(context);
+ }
}
/**
@@ -61,6 +84,27 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
public ChooserGridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
+ if (announceShortcutsAndSuggestedApps()) {
+ readGroupTitles(context);
+ }
+ }
+
+ private void readGroupTitles(Context context) {
+ mShortcutGroupTitle = context.getString(R.string.shortcut_group_a11y_title);
+ mSuggestedAppsGroupTitle = context.getString(R.string.suggested_apps_group_a11y_title);
+ mAllAppListGroupTitle = context.getString(R.string.all_apps_group_a11y_title);
+ }
+
+ @Override
+ public void onAttachedToWindow(RecyclerView view) {
+ super.onAttachedToWindow(view);
+ mRecyclerView = view;
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+ super.onDetachedFromWindow(view, recycler);
+ mRecyclerView = null;
}
@Override
@@ -78,4 +122,91 @@ public class ChooserGridLayoutManager extends GridLayoutManager {
public boolean canScrollVertically() {
return mVerticalScrollEnabled && super.canScrollVertically();
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(
+ RecyclerView.Recycler recycler,
+ RecyclerView.State state,
+ View host,
+ AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
+ if (announceShortcutsAndSuggestedApps() && host instanceof ViewGroup) {
+ if (host.getId() == R.id.shortcuts_container) {
+ info.setClassName(GridView.class.getName());
+ info.setContainerTitle(mShortcutGroupTitle);
+ info.setCollectionInfo(createShortcutsA11yCollectionInfo((ViewGroup) host));
+ } else if (host.getId() == R.id.suggested_apps_container) {
+ RecyclerView.Adapter adapter =
+ mRecyclerView == null ? null : mRecyclerView.getAdapter();
+ ChooserListAdapter gridAdapter = adapter instanceof ChooserGridAdapter
+ ? ((ChooserGridAdapter) adapter).getListAdapter()
+ : null;
+ info.setClassName(GridView.class.getName());
+ info.setCollectionInfo(createSuggestedAppsA11yCollectionInfo((ViewGroup) host));
+ if (gridAdapter == null || gridAdapter.getAlphaTargetCount() > 0) {
+ info.setContainerTitle(mSuggestedAppsGroupTitle);
+ } else {
+ // if all applications fit into one row, they will be put into the suggested
+ // applications group.
+ info.setContainerTitle(mAllAppListGroupTitle);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(@NonNull RecyclerView.Recycler recycler,
+ @NonNull RecyclerView.State state, @NonNull AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(recycler, state, info);
+ if (announceShortcutsAndSuggestedApps()) {
+ info.setContainerTitle(mAllAppListGroupTitle);
+ }
+ }
+
+ @Override
+ public boolean isLayoutHierarchical(
+ @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
+ return announceShortcutsAndSuggestedApps() || super.isLayoutHierarchical(recycler, state);
+ }
+
+ private CollectionInfoCompat createShortcutsA11yCollectionInfo(ViewGroup container) {
+ // TODO: create a custom view for the shortcuts row and move this logic there.
+ int rowCount = 0;
+ int columnCount = 0;
+ for (int i = 0; i < container.getChildCount(); i++) {
+ View row = container.getChildAt(i);
+ int rowColumnCount = 0;
+ if (row instanceof ViewGroup rowGroup && row.getVisibility() == View.VISIBLE) {
+ for (int j = 0; j < rowGroup.getChildCount(); j++) {
+ View v = rowGroup.getChildAt(j);
+ if (v != null && v.getVisibility() == View.VISIBLE) {
+ rowColumnCount++;
+ if (v instanceof TextView) {
+ // A special case of the no-targets message that also contains an
+ // off-screen item (which looks like a bug).
+ rowColumnCount = 1;
+ break;
+ }
+ }
+ }
+ }
+ if (rowColumnCount > 0) {
+ rowCount++;
+ columnCount = Math.max(columnCount, rowColumnCount);
+ }
+ }
+ return CollectionInfoCompat.obtain(rowCount, columnCount, false);
+ }
+
+ private CollectionInfoCompat createSuggestedAppsA11yCollectionInfo(ViewGroup container) {
+ // TODO: create a custom view for the suggested apps row and move this logic there.
+ int columnCount = 0;
+ for (int i = 0; i < container.getChildCount(); i++) {
+ View v = container.getChildAt(i);
+ if (v.getVisibility() == View.VISIBLE) {
+ columnCount++;
+ }
+ }
+ return CollectionInfoCompat.obtain(1, columnCount, false);
+ }
}
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 563d7d1a..d743f859 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -811,6 +811,13 @@ public class ChooserListAdapter extends ResolverListAdapter {
mServiceTargets.addAll(adapter.mServiceTargets);
}
+ /**
+ * Reset direct targets
+ */
+ public void resetDirectTargets() {
+ createPlaceHolders();
+ }
+
private boolean isDirectTargetRowEmptyState() {
return (mServiceTargets.size() == 1) && mServiceTargets.get(0).isEmptyTargetInfo();
}
diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
index 4166e5ae..2af5881f 100644
--- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
@@ -184,7 +184,8 @@ public final class ChooserContentPreviewUi {
imageLoader,
typeClassifier,
headlineGenerator,
- metadata
+ metadata,
+ chooserRequest.getCallerAllowsTextToggle()
);
if (previewData.getUriCount() > 0) {
JavaFlowHelper.collectToList(
diff --git a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java
index 30161cfb..da701ec4 100644
--- a/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUi.java
@@ -62,6 +62,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
private final CharSequence mMetadata;
private final boolean mIsSingleImage;
private final int mFileCount;
+ private final boolean mAllowTextToggle;
private ViewGroup mContentPreviewView;
private View mHeadliveView;
private boolean mIsMetadataUpdated = false;
@@ -70,8 +71,6 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
private boolean mAllImages;
private boolean mAllVideos;
private int mPreviewSize;
- // TODO(b/285309527): make this a flag
- private static final boolean SHOW_TOGGLE_CHECKMARK = false;
FilesPlusTextContentPreviewUi(
CoroutineScope scope,
@@ -83,7 +82,8 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
ImageLoader imageLoader,
MimeTypeClassifier typeClassifier,
HeadlineGenerator headlineGenerator,
- @Nullable CharSequence metadata) {
+ @Nullable CharSequence metadata,
+ boolean allowTextToggle) {
if (isSingleImage && fileCount != 1) {
throw new IllegalArgumentException(
"fileCount = " + fileCount + " and isSingleImage = true");
@@ -98,6 +98,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
mTypeClassifier = typeClassifier;
mHeadlineGenerator = headlineGenerator;
mMetadata = metadata;
+ mAllowTextToggle = allowTextToggle;
}
@Override
@@ -234,7 +235,7 @@ class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
shareTextAction.accept(!isChecked);
updateHeadline(headlineView, mFileCount, mAllImages, mAllVideos);
});
- if (SHOW_TOGGLE_CHECKMARK) {
+ if (mAllowTextToggle) {
includeText.setVisibility(View.VISIBLE);
}
}
diff --git a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
index 60cc9e05..ad338103 100644
--- a/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
+++ b/java/src/com/android/intentresolver/data/model/ChooserRequest.kt
@@ -30,6 +30,7 @@ import androidx.annotation.StringRes
import com.android.intentresolver.ContentTypeHint
import com.android.intentresolver.IChooserInteractiveSessionCallback
import com.android.intentresolver.ext.hasAction
+import com.android.systemui.shared.Flags.screenshotContextUrl
const val ANDROID_APP_SCHEME = "android-app"
@@ -196,4 +197,7 @@ data class ChooserRequest(
}
val payloadIntents = listOf(targetIntent) + additionalTargets
+
+ val callerAllowsTextToggle =
+ screenshotContextUrl() && "com.android.systemui".equals(referrerPackage)
}
diff --git a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt
index 41f838ee..aa1f385f 100644
--- a/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt
+++ b/java/src/com/android/intentresolver/shortcuts/ShortcutLoader.kt
@@ -35,7 +35,7 @@ import androidx.annotation.MainThread
import androidx.annotation.OpenForTesting
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
-import com.android.intentresolver.Flags.fixShortcutsFlashing
+import com.android.intentresolver.Flags.fixShortcutsFlashingFixed
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.measurements.Tracer
import com.android.intentresolver.measurements.runTracing
@@ -189,7 +189,7 @@ constructor(
Log.d(TAG, "[$id] query AppPredictor for user $userHandle")
val watchdogJob =
- if (fixShortcutsFlashing()) {
+ if (fixShortcutsFlashingFixed()) {
scope
.launch(start = CoroutineStart.LAZY) {
delay(APP_PREDICTOR_RESPONSE_TIMEOUT_MS)
diff --git a/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
index b5a4d617..816a2b1d 100644
--- a/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
+++ b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
@@ -22,7 +22,10 @@ import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.util.TypedValue
+import android.view.InputDevice.SOURCE_MOUSE
import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_HOVER_ENTER
+import android.view.MotionEvent.ACTION_HOVER_MOVE
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
@@ -93,7 +96,7 @@ class ChooserTargetItemView(
val iconView = iconView ?: return false
if (!isEnabled) return true
when (event.action) {
- MotionEvent.ACTION_HOVER_ENTER -> {
+ ACTION_HOVER_ENTER -> {
iconView.isHovered = true
}
MotionEvent.ACTION_HOVER_EXIT -> {
@@ -103,7 +106,17 @@ class ChooserTargetItemView(
return true
}
- override fun onInterceptHoverEvent(event: MotionEvent?) = true
+ override fun onInterceptHoverEvent(event: MotionEvent) =
+ if (event.isFromSource(SOURCE_MOUSE)) {
+ // This is the same logic as in super.onInterceptHoverEvent (ViewGroup) minus the check
+ // that the pointer fall on the scroll bar as we need to control the hover state of the
+ // icon.
+ // We also want to intercept only MOUSE hover events as the TalkBack's Explore by Touch
+ // (including single taps) reported as a hover event.
+ event.action == ACTION_HOVER_MOVE || event.action == ACTION_HOVER_ENTER
+ } else {
+ super.onInterceptHoverEvent(event)
+ }
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
diff --git a/tests/unit/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt b/tests/unit/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt
index 1d85c61b..a944beee 100644
--- a/tests/unit/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt
+++ b/tests/unit/src/com/android/intentresolver/contentpreview/FilesPlusTextContentPreviewUiTest.kt
@@ -20,6 +20,7 @@ import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.CheckBox
import android.widget.TextView
import androidx.annotation.IdRes
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -192,6 +193,7 @@ class FilesPlusTextContentPreviewUiTest {
DefaultMimeTypeClassifier,
headlineGenerator,
testMetadataText,
+ /* allowTextToggle=*/ false,
)
val layoutInflater = LayoutInflater.from(context)
val gridLayout =
@@ -203,7 +205,7 @@ class FilesPlusTextContentPreviewUiTest {
context.resources,
LayoutInflater.from(context),
gridLayout,
- headlineRow
+ headlineRow,
)
verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
@@ -234,6 +236,7 @@ class FilesPlusTextContentPreviewUiTest {
DefaultMimeTypeClassifier,
headlineGenerator,
testMetadataText,
+ /* allowTextToggle=*/ false,
)
val layoutInflater = LayoutInflater.from(context)
val gridLayout =
@@ -253,7 +256,7 @@ class FilesPlusTextContentPreviewUiTest {
context.resources,
LayoutInflater.from(context),
gridLayout,
- headlineRow
+ headlineRow,
)
verify(headlineGenerator, times(1)).getFilesHeadline(sharedFileCount)
@@ -270,6 +273,73 @@ class FilesPlusTextContentPreviewUiTest {
verifyPreviewMetadata(headlineRow, testMetadataText)
}
+ @Test
+ fun test_allowToggle() {
+ val testSubject =
+ FilesPlusTextContentPreviewUi(
+ testScope,
+ /*isSingleImage=*/ false,
+ /* fileCount=*/ 1,
+ SHARED_TEXT,
+ /*intentMimeType=*/ "*/*",
+ actionFactory,
+ imageLoader,
+ DefaultMimeTypeClassifier,
+ headlineGenerator,
+ testMetadataText,
+ /* allowTextToggle=*/ true,
+ )
+ val layoutInflater = LayoutInflater.from(context)
+ val gridLayout =
+ layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
+ as ViewGroup
+ val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
+
+ testSubject.display(
+ context.resources,
+ LayoutInflater.from(context),
+ gridLayout,
+ headlineRow,
+ )
+
+ val checkbox = headlineRow.requireViewById<CheckBox>(R.id.include_text_action)
+ assertThat(checkbox.visibility).isEqualTo(View.VISIBLE)
+ assertThat(checkbox.isChecked).isTrue()
+ }
+
+ @Test
+ fun test_hideTextToggle() {
+ val testSubject =
+ FilesPlusTextContentPreviewUi(
+ testScope,
+ /*isSingleImage=*/ false,
+ /* fileCount=*/ 1,
+ SHARED_TEXT,
+ /*intentMimeType=*/ "*/*",
+ actionFactory,
+ imageLoader,
+ DefaultMimeTypeClassifier,
+ headlineGenerator,
+ testMetadataText,
+ /* allowTextToggle=*/ false,
+ )
+ val layoutInflater = LayoutInflater.from(context)
+ val gridLayout =
+ layoutInflater.inflate(R.layout.chooser_grid_scrollable_preview, null, false)
+ as ViewGroup
+ val headlineRow = gridLayout.requireViewById<View>(R.id.chooser_headline_row_container)
+
+ testSubject.display(
+ context.resources,
+ LayoutInflater.from(context),
+ gridLayout,
+ headlineRow,
+ )
+
+ val checkbox = headlineRow.requireViewById<CheckBox>(R.id.include_text_action)
+ assertThat(checkbox.visibility).isNotEqualTo(View.VISIBLE)
+ }
+
private fun testLoadingHeadline(
intentMimeType: String,
sharedFileCount: Int,
@@ -287,6 +357,7 @@ class FilesPlusTextContentPreviewUiTest {
DefaultMimeTypeClassifier,
headlineGenerator,
testMetadataText,
+ /* allowTextToggle=*/ false,
)
val layoutInflater = LayoutInflater.from(context)
val gridLayout =
@@ -307,7 +378,7 @@ class FilesPlusTextContentPreviewUiTest {
context.resources,
LayoutInflater.from(context),
gridLayout,
- headlineRow
+ headlineRow,
) to headlineRow
}
diff --git a/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt b/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
index 8167f610..eb5297b4 100644
--- a/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
+++ b/tests/unit/src/com/android/intentresolver/shortcuts/ShortcutLoaderTest.kt
@@ -30,7 +30,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.filters.SmallTest
-import com.android.intentresolver.Flags.FLAG_FIX_SHORTCUTS_FLASHING
+import com.android.intentresolver.Flags.FLAG_FIX_SHORTCUTS_FLASHING_FIXED
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.createAppTarget
import com.android.intentresolver.createShareShortcutInfo
@@ -324,7 +324,7 @@ class ShortcutLoaderTest {
}
@Test
- @DisableFlags(FLAG_FIX_SHORTCUTS_FLASHING)
+ @DisableFlags(FLAG_FIX_SHORTCUTS_FLASHING_FIXED)
fun test_appPredictorNotResponding_noCallbackFromShortcutLoader() {
scope.runTest {
val shortcutManagerResult =
@@ -360,7 +360,7 @@ class ShortcutLoaderTest {
}
@Test
- @EnableFlags(FLAG_FIX_SHORTCUTS_FLASHING)
+ @EnableFlags(FLAG_FIX_SHORTCUTS_FLASHING_FIXED)
fun test_appPredictorNotResponding_timeoutAndFallbackToShortcutManager() {
scope.runTest {
val testSubject =
@@ -398,7 +398,7 @@ class ShortcutLoaderTest {
}
@Test
- @EnableFlags(FLAG_FIX_SHORTCUTS_FLASHING)
+ @EnableFlags(FLAG_FIX_SHORTCUTS_FLASHING_FIXED)
fun test_appPredictorResponding_appPredictorTimeoutJobIsCancelled() {
scope.runTest {
val shortcutManagerResult =
diff --git a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
index facfe151..7bc1e785 100644
--- a/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
+++ b/tests/unit/src/com/android/intentresolver/ui/viewmodel/ChooserRequestTest.kt
@@ -28,6 +28,9 @@ import android.content.Intent.EXTRA_REFERRER
import android.content.Intent.EXTRA_TEXT
import android.content.Intent.EXTRA_TITLE
import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import com.android.intentresolver.ContentTypeHint
@@ -37,13 +40,16 @@ import com.android.intentresolver.validation.Importance
import com.android.intentresolver.validation.Invalid
import com.android.intentresolver.validation.NoValue
import com.android.intentresolver.validation.Valid
+import com.android.systemui.shared.Flags
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
private fun createActivityModel(
targetIntent: Intent?,
referrer: Uri? = null,
additionalIntents: List<Intent>? = null,
+ launchedFromPackage: String = "com.android.example",
) =
ActivityModel(
Intent(ACTION_CHOOSER).apply {
@@ -51,12 +57,13 @@ private fun createActivityModel(
additionalIntents?.also { putExtra(EXTRA_ALTERNATE_INTENTS, it.toTypedArray()) }
},
launchedFromUid = 10000,
- launchedFromPackage = "com.android.example",
- referrer = referrer ?: "android-app://com.android.example".toUri(),
+ launchedFromPackage = launchedFromPackage,
+ referrer = referrer ?: "android-app://$launchedFromPackage".toUri(),
false,
)
class ChooserRequestTest {
+ @get:Rule val flagsRule = SetFlagsRule()
@Test
fun missingIntent() {
@@ -265,4 +272,46 @@ class ChooserRequestTest {
assertThat(request.sharedTextTitle).isEqualTo(title)
}
}
+
+ @Test
+ @DisableFlags(Flags.FLAG_SCREENSHOT_CONTEXT_URL)
+ fun testCallerAllowsTextToggle_flagOff() {
+ val intent = Intent().putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
+ val model =
+ createActivityModel(targetIntent = intent, launchedFromPackage = "com.android.systemui")
+ val result = readChooserRequest(model)
+
+ assertThat(result).isInstanceOf(Valid::class.java)
+ result as Valid<ChooserRequest>
+
+ assertThat(result.value.callerAllowsTextToggle).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_CONTEXT_URL)
+ fun testCallerAllowsTextToggle_sysuiPackage() {
+ val intent = Intent().putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
+ val model =
+ createActivityModel(targetIntent = intent, launchedFromPackage = "com.android.systemui")
+ val result = readChooserRequest(model)
+
+ assertThat(result).isInstanceOf(Valid::class.java)
+ result as Valid<ChooserRequest>
+
+ assertThat(result.value.callerAllowsTextToggle).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SCREENSHOT_CONTEXT_URL)
+ fun testCallerAllowsTextToggle_otherPackage() {
+ val intent = Intent().putExtras(bundleOf(EXTRA_INTENT to Intent(ACTION_SEND)))
+ val model =
+ createActivityModel(targetIntent = intent, launchedFromPackage = "com.hello.world")
+ val result = readChooserRequest(model)
+
+ assertThat(result).isInstanceOf(Valid::class.java)
+ result as Valid<ChooserRequest>
+
+ assertThat(result.value.callerAllowsTextToggle).isFalse()
+ }
}