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

Commit 807dfbb0 authored by Fan Zhang's avatar Fan Zhang
Browse files

Misc tweaks to create shortcut handler

- Add restriction to only allow system apps provide shortcuts
- Load icon from target package when building shortcut icons
- Codestyle clean up

Change-Id: If9e5c5e60601efda216ce2cc2d4fd7beb44279e1
Fixes: 110393916
Test: visual, robotests
parent 3df8494f
Loading
Loading
Loading
Loading
+40 −18
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -33,6 +34,7 @@ import android.graphics.drawable.Icon;
import android.graphics.drawable.LayerDrawable;
import android.net.ConnectivityManager;
import android.os.AsyncTask;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
@@ -52,6 +54,7 @@ import androidx.annotation.VisibleForTesting;

public class CreateShortcut extends LauncherActivity {

    private static final String TAG = "CreateShortcut";
    @VisibleForTesting
    static final String SHORTCUT_ID_PREFIX = "component-shortcut-";

@@ -76,11 +79,12 @@ public class CreateShortcut extends LauncherActivity {
        ShortcutManager sm = getSystemService(ShortcutManager.class);
        ActivityInfo activityInfo = resolveInfo.activityInfo;

        Icon maskableIcon = activityInfo.icon != 0 ? Icon.createWithAdaptiveBitmap(
                createIcon(activityInfo.icon,
        Icon maskableIcon = activityInfo.icon != 0 && activityInfo.applicationInfo != null
                ? Icon.createWithAdaptiveBitmap(
                createIcon(activityInfo.applicationInfo, activityInfo.icon,
                        R.layout.shortcut_badge_maskable,
                        getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable))) :
                Icon.createWithResource(this, R.drawable.ic_launcher_settings);
                        getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)))
                : Icon.createWithResource(this, R.drawable.ic_launcher_settings);
        String shortcutId = SHORTCUT_ID_PREFIX +
                shortcutIntent.getComponent().flattenToShortString();
        ShortcutInfo info = new ShortcutInfo.Builder(this, shortcutId)
@@ -98,7 +102,9 @@ public class CreateShortcut extends LauncherActivity {
        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);

        if (activityInfo.icon != 0) {
            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(activityInfo.icon,
            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(
                    activityInfo.applicationInfo,
                    activityInfo.icon,
                    R.layout.shortcut_badge,
                    getResources().getDimensionPixelSize(R.dimen.shortcut_size)));
        }
@@ -114,20 +120,29 @@ public class CreateShortcut extends LauncherActivity {
                info.activityInfo.name);
    }

    private Bitmap createIcon(int resource, int layoutRes, int size) {
        Context context = new ContextThemeWrapper(this, android.R.style.Theme_Material);
        View view = LayoutInflater.from(context).inflate(layoutRes, null);
        Drawable iconDrawable = getDrawable(resource);
    private Bitmap createIcon(ApplicationInfo app, int resource, int layoutRes, int size) {
        final Context context = new ContextThemeWrapper(this, android.R.style.Theme_Material);
        final View view = LayoutInflater.from(context).inflate(layoutRes, null);
        final int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        view.measure(spec, spec);
        final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
                Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);

        Drawable iconDrawable = null;
        try {
            iconDrawable =
                    getPackageManager().getResourcesForApplication(app).getDrawable(resource);
            if (iconDrawable instanceof LayerDrawable) {
                iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
            }
            ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Cannot load icon from app " + app + ", returning a default icon");
            Icon icon = Icon.createWithResource(this, R.drawable.ic_launcher_settings);
            ((ImageView) view.findViewById(android.R.id.icon)).setImageIcon(icon);
        }

        int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        view.measure(spec, spec);
        Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
                Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        view.draw(canvas);
        return bitmap;
@@ -147,12 +162,15 @@ public class CreateShortcut extends LauncherActivity {
     * Perform query on package manager for list items.  The default
     * implementation queries for activities.
     */
    @Override
    protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) {
        List<ResolveInfo> activities = getPackageManager().queryIntentActivities(queryIntent,
                PackageManager.GET_META_DATA);
        final ConnectivityManager cm =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        if (activities == null) return null;
        if (activities == null) {
            return null;
        }
        for (int i = activities.size() - 1; i >= 0; i--) {
            ResolveInfo info = activities.get(i);
            if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) {
@@ -160,6 +178,10 @@ public class CreateShortcut extends LauncherActivity {
                    activities.remove(i);
                }
            }
            if (!info.activityInfo.applicationInfo.isSystemApp()) {
                Log.d(TAG, "Skipping non-system app: " + info.activityInfo);
                activities.remove(i);
            }
        }
        return activities;
    }
+179 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
@@ -16,33 +16,29 @@

package com.android.settings.shortcut;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.shadow.ShadowConnectivityManager;

import org.junit.Before;
import org.junit.Test;
@@ -51,6 +47,11 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowPackageManager;

import java.util.Arrays;
import java.util.List;
@@ -58,80 +59,110 @@ import java.util.List;
/**
 * Tests for {@link CreateShortcutTest}
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
@RunWith(SettingsRobolectricTestRunner.class)
@Config(shadows = ShadowConnectivityManager.class)
public class CreateShortcutTest {

    private static final String SHORTCUT_ID_PREFIX = CreateShortcut.SHORTCUT_ID_PREFIX;

    private Instrumentation mInstrumentation;
    private Context mContext;
    private ShadowConnectivityManager mShadowConnectivityManager;
    private ShadowPackageManager mPackageManager;

    @Mock
    ShortcutManager mShortcutManager;
    private ShortcutManager mShortcutManager;
    @Captor
    ArgumentCaptor<List<ShortcutInfo>> mListCaptor;
    private ArgumentCaptor<List<ShortcutInfo>> mListCaptor;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mInstrumentation = InstrumentationRegistry.getInstrumentation();
        mContext = mInstrumentation.getTargetContext();
    }

    @Test
    public void test_layoutDoesNotHaveCancelButton() {
        mInstrumentation.startActivitySync(new Intent(Intent.ACTION_CREATE_SHORTCUT)
                .setClassName(mContext, CreateShortcut.class.getName())
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
        onView(ViewMatchers.withText(R.string.cancel)).check(doesNotExist());
        mContext = RuntimeEnvironment.application;
        mPackageManager = Shadow.extract(mContext.getPackageManager());
        mShadowConnectivityManager = ShadowConnectivityManager.getShadow();
        mShadowConnectivityManager.setTetheringSupported(true);
    }

    @Test
    public void createResultIntent() {
        CreateShortcut orgActivity = (CreateShortcut) mInstrumentation.startActivitySync(
                new Intent(Intent.ACTION_CREATE_SHORTCUT)
                        .setClassName(mContext, CreateShortcut.class.getName())
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
        CreateShortcut orgActivity = Robolectric.setupActivity(CreateShortcut.class);
        CreateShortcut activity = spy(orgActivity);
        doReturn(mShortcutManager).when(activity).getSystemService(eq(Context.SHORTCUT_SERVICE));

        when(mShortcutManager.createShortcutResultIntent(any(ShortcutInfo.class)))
                .thenReturn(new Intent().putExtra("d1", "d2"));

        Intent intent = CreateShortcut.getBaseIntent()
        final Intent intent = CreateShortcut.getBaseIntent()
                .setClass(activity, Settings.ManageApplicationsActivity.class);
        ResolveInfo ri = activity.getPackageManager().resolveActivity(intent, 0);
        Intent result = activity.createResultIntent(intent, ri, "dummy");
        assertEquals("d2", result.getStringExtra("d1"));
        assertNotNull(result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT));
        final ResolveInfo ri = activity.getPackageManager().resolveActivity(intent, 0);
        final Intent result = activity.createResultIntent(intent, ri, "dummy");

        assertThat(result.getStringExtra("d1")).isEqualTo("d2");
        assertThat((Object) result.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT)).isNotNull();

        ArgumentCaptor<ShortcutInfo> infoCaptor = ArgumentCaptor.forClass(ShortcutInfo.class);
        verify(mShortcutManager, times(1))
                .createShortcutResultIntent(infoCaptor.capture());
        String expectedId = SHORTCUT_ID_PREFIX + intent.getComponent().flattenToShortString();
        assertEquals(expectedId, infoCaptor.getValue().getId());
        assertThat(infoCaptor.getValue().getId())
                .isEqualTo(SHORTCUT_ID_PREFIX + intent.getComponent().flattenToShortString());
    }

    @Test
    public void shortcutsUpdateTask() {
        mContext = spy(new ContextWrapper(mInstrumentation.getTargetContext()));
        mContext = spy(RuntimeEnvironment.application);
        doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));

        List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
                makeShortcut("d1"), makeShortcut("d2"),
        final Intent shortcut1 = CreateShortcut.getBaseIntent().setComponent(
                new ComponentName(mContext, Settings.ManageApplicationsActivity.class));
        final ResolveInfo ri1 = mock(ResolveInfo.class);
        final Intent shortcut2 = CreateShortcut.getBaseIntent().setComponent(
                new ComponentName(mContext, Settings.SoundSettingsActivity.class));
        final ResolveInfo ri2 = mock(ResolveInfo.class);
        when(ri1.loadLabel(any(PackageManager.class))).thenReturn("label1");
        when(ri2.loadLabel(any(PackageManager.class))).thenReturn("label2");
        mPackageManager.addResolveInfoForIntent(shortcut1, ri1);
        mPackageManager.addResolveInfoForIntent(shortcut2, ri2);

        final List<ShortcutInfo> pinnedShortcuts = Arrays.asList(
                makeShortcut("d1"),
                makeShortcut("d2"),
                makeShortcut(Settings.ManageApplicationsActivity.class),
                makeShortcut("d3"),
                makeShortcut(Settings.SoundSettingsActivity.class));
        when(mShortcutManager.getPinnedShortcuts()).thenReturn(pinnedShortcuts);

        new CreateShortcut.ShortcutsUpdateTask(mContext).doInBackground();

        verify(mShortcutManager, times(1)).updateShortcuts(mListCaptor.capture());

        List<ShortcutInfo> updates = mListCaptor.getValue();
        assertEquals(2, updates.size());
        assertEquals(pinnedShortcuts.get(2).getId(), updates.get(0).getId());
        assertEquals(pinnedShortcuts.get(4).getId(), updates.get(1).getId());
        final List<ShortcutInfo> updates = mListCaptor.getValue();

        assertThat(updates).hasSize(2);
        assertThat(pinnedShortcuts.get(2).getId()).isEqualTo(updates.get(0).getId());
        assertThat(pinnedShortcuts.get(4).getId()).isEqualTo(updates.get(1).getId());
    }

    @Test
    public void queryActivities_shouldOnlyIncludeSystemApp() {
        final ResolveInfo ri1 = new ResolveInfo();
        ri1.activityInfo = new ActivityInfo();
        ri1.activityInfo.name = "activity1";
        ri1.activityInfo.applicationInfo = new ApplicationInfo();
        ri1.activityInfo.applicationInfo.flags = 0;
        final ResolveInfo ri2 = new ResolveInfo();
        ri2.activityInfo = new ActivityInfo();
        ri2.activityInfo.name = "activity2";
        ri2.activityInfo.applicationInfo = new ApplicationInfo();
        ri2.activityInfo.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;

        mPackageManager.addResolveInfoForIntent(CreateShortcut.getBaseIntent(),
                Arrays.asList(ri1, ri2));

        TestClass orgActivity = Robolectric.setupActivity(TestClass.class);
        TestClass activity = spy(orgActivity);

        List<ResolveInfo> info = activity.onQueryPackageManager(CreateShortcut.getBaseIntent());
        assertThat(info).hasSize(1);
        assertThat(info.get(0)).isEqualTo(ri2);
    }

    private ShortcutInfo makeShortcut(Class<?> className) {
@@ -142,4 +173,7 @@ public class CreateShortcutTest {
    private ShortcutInfo makeShortcut(String id) {
        return new ShortcutInfo.Builder(mContext, id).build();
    }

    private static class TestClass extends CreateShortcut {
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -19,8 +19,10 @@ package com.android.settings.testutils.shadow;
import android.net.ConnectivityManager;
import android.util.SparseBooleanArray;

import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;

@Implements(value = ConnectivityManager.class, inheritImplementationMethods = true)
public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowConnectivityManager {
@@ -45,4 +47,9 @@ public class ShadowConnectivityManager extends org.robolectric.shadows.ShadowCon
    public boolean isTetheringSupported() {
        return mTetheringSupported;
    }

    public static ShadowConnectivityManager getShadow() {
        return Shadow.extract(
                RuntimeEnvironment.application.getSystemService(ConnectivityManager.class));
    }
}