Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 61a626cf authored by Ajinkya Chalke's avatar Ajinkya Chalke
Browse files

Handle duplicate backlinks in app clips

- When multiple backlinks with same app name are available, the names
  are suffixed with numerical index for identification.

Bug: 356791827
Flag: com.android.systemui.app_clips_backlinks
Test: atest AppClipsActivityTest AppClipsTest
Change-Id: I24a9c841f78efda5d221ef8798ca63c5b7f9cde5
parent c063bef9
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -270,6 +270,7 @@
    <!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
    <string name="app_clips_save_add_to_note">Add to note</string>
    <string name="backlinks_include_link">Include link</string>
    <string name="backlinks_duplicate_label_format"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> <xliff:g id="frequencyCount" example="(1)">(%2$d)</xliff:g></string>

    <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
    <string name="screenrecord_title">Screen Recorder</string>
+58 −3
Original line number Diff line number Diff line
@@ -71,7 +71,9 @@ import com.android.systemui.res.R;
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
@@ -344,10 +346,63 @@ public class AppClipsActivity extends ComponentActivity {

        // Set up the dropdown when multiple backlinks are available.
        if (backlinksData.size() > 1) {
            setUpListPopupWindow(backlinksData, mBacklinksDataTextView);
            setUpListPopupWindow(updateBacklinkLabelsWithDuplicateNames(backlinksData),
                    mBacklinksDataTextView);
        }
    }

    /**
     * If there are more than 1 backlinks that have the same app name, then this method appends
     * a numerical suffix to such backlinks to help users distinguish.
     */
    private List<InternalBacklinksData> updateBacklinkLabelsWithDuplicateNames(
            List<InternalBacklinksData> backlinksData) {
        // Check if there are multiple backlinks with same name.
        Map<String, Integer> duplicateNamedBacklinksCountMap = new HashMap<>();
        for (InternalBacklinksData data : backlinksData) {
            if (duplicateNamedBacklinksCountMap.containsKey(data.getDisplayLabel())) {
                int duplicateCount = duplicateNamedBacklinksCountMap.get(data.getDisplayLabel());
                if (duplicateCount == 0) {
                    // If this is the first time the loop is coming across a duplicate name, set the
                    // count to 2. This way the count starts from 1 for all duplicate named
                    // backlinks.
                    duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), 2);
                } else {
                    // For all duplicate named backlinks, increase the duplicate count by 1.
                    duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), duplicateCount + 1);
                }
            } else {
                // This is the first time the loop is coming across a backlink with this name. Set
                // its count to 0. The loop will increase its count by 1 when a duplicate is found.
                duplicateNamedBacklinksCountMap.put(data.getDisplayLabel(), 0);
            }
        }

        // Go through the backlinks in reverse order as it is easier to assign the numerical suffix
        // in descending order of frequency using the duplicate map that was built earlier. For
        // example, if "App A" is present 3 times, then we assign display label "App A (3)" first
        // and then "App A (2)", lastly "App A (1)".
        for (InternalBacklinksData data : backlinksData.reversed()) {
            String originalBacklinkLabel = data.getDisplayLabel();
            int duplicateCount = duplicateNamedBacklinksCountMap.get(originalBacklinkLabel);

            // The display label should only be updated if there are multiple backlinks with the
            // same name.
            if (duplicateCount > 0) {
                // Update the display label to: "App name (count)"
                data.setDisplayLabel(
                        getString(R.string.backlinks_duplicate_label_format, originalBacklinkLabel,
                                duplicateCount));

                // Decrease the duplicate count and update the map.
                duplicateCount--;
                duplicateNamedBacklinksCountMap.put(originalBacklinkLabel, duplicateCount);
            }
        }

        return backlinksData;
    }

    private void setUpListPopupWindow(List<InternalBacklinksData> backlinksData, View anchor) {
        ListPopupWindow listPopupWindow = new ListPopupWindow(this);
        listPopupWindow.setAnchorView(anchor);
@@ -365,7 +420,7 @@ public class AppClipsActivity extends ComponentActivity {
            public View getView(int position, @Nullable View convertView, ViewGroup parent) {
                TextView itemView = (TextView) super.getView(position, convertView, parent);
                InternalBacklinksData data = backlinksData.get(position);
                itemView.setText(data.getClipData().getDescription().getLabel());
                itemView.setText(data.getDisplayLabel());

                Drawable icon = data.getAppIcon();
                icon.setBounds(createBacklinksTextViewDrawableBounds());
@@ -387,7 +442,7 @@ public class AppClipsActivity extends ComponentActivity {
     * expected to be already set when this method is called.
     */
    private void updateBacklinksTextView(InternalBacklinksData backlinksData) {
        mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
        mBacklinksDataTextView.setText(backlinksData.getDisplayLabel());
        Drawable appIcon = backlinksData.getAppIcon();
        Rect compoundDrawableBounds = createBacklinksTextViewDrawableBounds();
        appIcon.setBounds(compoundDrawableBounds);
+3 −1
Original line number Diff line number Diff line
@@ -20,4 +20,6 @@ import android.content.ClipData
import android.graphics.drawable.Drawable

/** A class to hold the [ClipData] for backlinks and the corresponding app's [Drawable] icon. */
internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable)
internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable) {
    var displayLabel: String = clipData.description.label.toString()
}
+34 −0
Original line number Diff line number Diff line
@@ -308,6 +308,40 @@ public final class AppClipsActivityTest extends SysuiTestCase {
        assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull();
    }

    @Test
    @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
    public void appClipsLaunched_backlinks_multipleBacklinksAvailable_duplicateName()
            throws RemoteException {
        // Set up mocking for multiple backlinks.
        ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();

        ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
        RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
        int taskId2 = BACKLINKS_TASK_ID + 2;
        runningTaskInfo2.taskId = taskId2;

        when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
                mDisplayIdCaptor.capture()))
                .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS, runningTaskInfo2));
        when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(resolveInfo1,
                resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
        when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);

        // Using same AssistContent data for both tasks.
        mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
        mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, taskId2);

        // Mocking complete, trigger backlinks.
        launchActivity();
        waitForIdleSync();

        // Verify default backlink shown to user has the numerical suffix.
        TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
        assertThat(backlinksData.getText().toString()).isEqualTo(
                mContext.getString(R.string.backlinks_duplicate_label_format,
                        BACKLINKS_TASK_APP_NAME, 1));
    }

    private void setUpMocksForBacklinks() throws RemoteException {
        when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
                mDisplayIdCaptor.capture()))