diff options
20 files changed, 329 insertions, 29 deletions
@@ -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() + } } |