OSDN Git Service

Misc tweaks to create shortcut handler
authorFan Zhang <zhfan@google.com>
Tue, 19 Jun 2018 17:21:38 +0000 (10:21 -0700)
committerFan Zhang <zhfan@google.com>
Tue, 19 Jun 2018 19:42:18 +0000 (12:42 -0700)
- 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

src/com/android/settings/shortcut/CreateShortcut.java
tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/testutils/shadow/ShadowConnectivityManager.java
tests/unit/src/com/android/settings/shortcut/CreateShortcutTest.java [deleted file]

index a25e624..953dfc0 100644 (file)
@@ -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);
-        if (iconDrawable instanceof LayerDrawable) {
-            iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1);
-        }
-        ((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(iconDrawable);
-
-        int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+    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);
-        Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
+        final Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
                 Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
+        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);
+        }
+
         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;
     }
diff --git a/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java b/tests/robotests/src/com/android/settings/shortcut/CreateShortcutTest.java
new file mode 100644 (file)
index 0000000..c56819b
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.shortcut;
+
+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.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;
+import android.content.pm.ShortcutManager;
+
+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;
+import org.junit.runner.RunWith;
+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;
+
+/**
+ * Tests for {@link CreateShortcutTest}
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(shadows = ShadowConnectivityManager.class)
+public class CreateShortcutTest {
+
+    private static final String SHORTCUT_ID_PREFIX = CreateShortcut.SHORTCUT_ID_PREFIX;
+
+    private Context mContext;
+    private ShadowConnectivityManager mShadowConnectivityManager;
+    private ShadowPackageManager mPackageManager;
+
+    @Mock
+    private ShortcutManager mShortcutManager;
+    @Captor
+    private ArgumentCaptor<List<ShortcutInfo>> mListCaptor;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mPackageManager = Shadow.extract(mContext.getPackageManager());
+        mShadowConnectivityManager = ShadowConnectivityManager.getShadow();
+        mShadowConnectivityManager.setTetheringSupported(true);
+    }
+
+    @Test
+    public void createResultIntent() {
+        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"));
+
+        final Intent intent = CreateShortcut.getBaseIntent()
+                .setClass(activity, Settings.ManageApplicationsActivity.class);
+        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());
+        assertThat(infoCaptor.getValue().getId())
+                .isEqualTo(SHORTCUT_ID_PREFIX + intent.getComponent().flattenToShortString());
+    }
+
+    @Test
+    public void shortcutsUpdateTask() {
+        mContext = spy(RuntimeEnvironment.application);
+        doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
+        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());
+
+        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) {
+        ComponentName cn = new ComponentName(mContext, className);
+        return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString());
+    }
+
+    private ShortcutInfo makeShortcut(String id) {
+        return new ShortcutInfo.Builder(mContext, id).build();
+    }
+
+    private static class TestClass extends CreateShortcut {
+    }
+}
index 62a4e72..05687f4 100644 (file)
@@ -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));
+    }
 }
diff --git a/tests/unit/src/com/android/settings/shortcut/CreateShortcutTest.java b/tests/unit/src/com/android/settings/shortcut/CreateShortcutTest.java
deleted file mode 100644 (file)
index 5ec008b..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-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 org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
-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.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 org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Tests for {@link CreateShortcutTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class CreateShortcutTest {
-
-    private static final String SHORTCUT_ID_PREFIX = CreateShortcut.SHORTCUT_ID_PREFIX;
-
-    private Instrumentation mInstrumentation;
-    private Context mContext;
-
-    @Mock
-    ShortcutManager mShortcutManager;
-    @Captor
-    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());
-    }
-
-    @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 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()
-                .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));
-
-        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());
-    }
-
-    @Test
-    public void shortcutsUpdateTask() {
-        mContext = spy(new ContextWrapper(mInstrumentation.getTargetContext()));
-        doReturn(mShortcutManager).when(mContext).getSystemService(eq(Context.SHORTCUT_SERVICE));
-
-        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());
-    }
-
-    private ShortcutInfo makeShortcut(Class<?> className) {
-        ComponentName cn = new ComponentName(mContext, className);
-        return makeShortcut(SHORTCUT_ID_PREFIX + cn.flattenToShortString());
-    }
-
-    private ShortcutInfo makeShortcut(String id) {
-        return new ShortcutInfo.Builder(mContext, id).build();
-    }
-}