Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 package com.android.launcher3.ui.widget;
     17 
     18 import android.appwidget.AppWidgetHost;
     19 import android.content.ComponentName;
     20 import android.content.ContentResolver;
     21 import android.content.ContentValues;
     22 import android.content.pm.PackageInstaller;
     23 import android.content.pm.PackageInstaller.SessionParams;
     24 import android.content.pm.PackageManager;
     25 import android.database.Cursor;
     26 import android.os.Bundle;
     27 import android.support.test.filters.LargeTest;
     28 import android.support.test.runner.AndroidJUnit4;
     29 import android.support.test.uiautomator.UiSelector;
     30 
     31 import com.android.launcher3.LauncherAppWidgetHost;
     32 import com.android.launcher3.LauncherAppWidgetHostView;
     33 import com.android.launcher3.LauncherAppWidgetInfo;
     34 import com.android.launcher3.LauncherAppWidgetProviderInfo;
     35 import com.android.launcher3.LauncherModel;
     36 import com.android.launcher3.LauncherSettings;
     37 import com.android.launcher3.PendingAppWidgetHostView;
     38 import com.android.launcher3.Workspace;
     39 import com.android.launcher3.compat.AppWidgetManagerCompat;
     40 import com.android.launcher3.compat.PackageInstallerCompat;
     41 import com.android.launcher3.ui.AbstractLauncherUiTest;
     42 import com.android.launcher3.util.ContentWriter;
     43 import com.android.launcher3.util.LooperExecutor;
     44 import com.android.launcher3.util.rule.LauncherActivityRule;
     45 import com.android.launcher3.util.rule.ShellCommandRule;
     46 import com.android.launcher3.widget.PendingAddWidgetInfo;
     47 import com.android.launcher3.widget.WidgetHostViewLoader;
     48 
     49 import org.junit.After;
     50 import org.junit.Before;
     51 import org.junit.Rule;
     52 import org.junit.Test;
     53 import org.junit.runner.RunWith;
     54 
     55 import java.util.Set;
     56 import java.util.concurrent.Callable;
     57 import java.util.concurrent.TimeUnit;
     58 
     59 import static org.junit.Assert.assertEquals;
     60 import static org.junit.Assert.assertFalse;
     61 import static org.junit.Assert.assertNotNull;
     62 import static org.junit.Assert.assertTrue;
     63 
     64 /**
     65  * Tests for bind widget flow.
     66  *
     67  * Note running these tests will clear the workspace on the device.
     68  */
     69 @LargeTest
     70 @RunWith(AndroidJUnit4.class)
     71 public class BindWidgetTest extends AbstractLauncherUiTest {
     72 
     73     @Rule public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
     74     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
     75 
     76     private ContentResolver mResolver;
     77     private AppWidgetManagerCompat mWidgetManager;
     78 
     79     // Objects created during test, which should be cleaned up in the end.
     80     private Cursor mCursor;
     81     // App install session id.
     82     private int mSessionId = -1;
     83 
     84     @Override
     85     @Before
     86     public void setUp() throws Exception {
     87         super.setUp();
     88 
     89         mResolver = mTargetContext.getContentResolver();
     90         mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext);
     91 
     92         // Clear all existing data
     93         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
     94         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
     95     }
     96 
     97     @After
     98     public void tearDown() throws Exception {
     99         if (mCursor != null) {
    100             mCursor.close();
    101         }
    102 
    103         if (mSessionId > -1) {
    104             mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
    105         }
    106     }
    107 
    108     @Test
    109     public void testBindNormalWidget_withConfig() {
    110         LauncherAppWidgetProviderInfo info = findWidgetProvider(true);
    111         LauncherAppWidgetInfo item = createWidgetInfo(info, true);
    112 
    113         setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
    114     }
    115 
    116     @Test
    117     public void testBindNormalWidget_withoutConfig() {
    118         LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
    119         LauncherAppWidgetInfo item = createWidgetInfo(info, true);
    120 
    121         setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
    122     }
    123 
    124     @Test
    125     public void testUnboundWidget_removed() throws Exception {
    126         LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
    127         LauncherAppWidgetInfo item = createWidgetInfo(info, false);
    128         item.appWidgetId = -33;
    129 
    130         // Since there is no widget to verify, just wait until the workspace is ready.
    131         setupAndVerifyContents(item, Workspace.class, null);
    132 
    133         waitUntilLoaderIdle();
    134         // Item deleted from db
    135         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
    136                 null, null, null, null, null);
    137         assertEquals(0, mCursor.getCount());
    138 
    139         // The view does not exist
    140         assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists());
    141     }
    142 
    143     @Test
    144     public void testPendingWidget_autoRestored() {
    145         // A non-restored widget with no config screen gets restored automatically.
    146         LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
    147 
    148         // Do not bind the widget
    149         LauncherAppWidgetInfo item = createWidgetInfo(info, false);
    150         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
    151 
    152         setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label);
    153     }
    154 
    155     @Test
    156     public void testPendingWidget_withConfigScreen() throws Exception {
    157         // A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
    158         LauncherAppWidgetProviderInfo info = findWidgetProvider(true);
    159 
    160         // Do not bind the widget
    161         LauncherAppWidgetInfo item = createWidgetInfo(info, false);
    162         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
    163 
    164         setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
    165         waitUntilLoaderIdle();
    166         // Item deleted from db
    167         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
    168                 null, null, null, null, null);
    169         mCursor.moveToNext();
    170 
    171         // Widget has a valid Id now.
    172         assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
    173                 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
    174         assertNotNull(mWidgetManager.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
    175                 LauncherSettings.Favorites.APPWIDGET_ID))));
    176     }
    177 
    178     @Test
    179     public void testPendingWidget_notRestored_removed() throws Exception {
    180         LauncherAppWidgetInfo item = getInvalidWidgetInfo();
    181         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
    182                 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
    183 
    184         setupAndVerifyContents(item, Workspace.class, null);
    185         // The view does not exist
    186         assertFalse(mDevice.findObject(
    187                 new UiSelector().className(PendingAppWidgetHostView.class)).exists());
    188         waitUntilLoaderIdle();
    189         // Item deleted from db
    190         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
    191                 null, null, null, null, null);
    192         assertEquals(0, mCursor.getCount());
    193     }
    194 
    195     @Test
    196     public void testPendingWidget_notRestored_brokenInstall() throws Exception {
    197         // A widget which is was being installed once, even if its not being
    198         // installed at the moment is not removed.
    199         LauncherAppWidgetInfo item = getInvalidWidgetInfo();
    200         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
    201                 | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
    202                 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
    203 
    204         setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
    205         // Verify item still exists in db
    206         waitUntilLoaderIdle();
    207         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
    208                 null, null, null, null, null);
    209         assertEquals(1, mCursor.getCount());
    210 
    211         // Widget still has an invalid id.
    212         mCursor.moveToNext();
    213         assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
    214                 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
    215                         & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
    216     }
    217 
    218     @Test
    219     public void testPendingWidget_notRestored_activeInstall() throws Exception {
    220         // A widget which is being installed is not removed
    221         LauncherAppWidgetInfo item = getInvalidWidgetInfo();
    222         item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
    223                 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
    224 
    225         // Create an active installer session
    226         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
    227         params.setAppPackageName(item.providerName.getPackageName());
    228         PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
    229         mSessionId = installer.createSession(params);
    230 
    231         setupAndVerifyContents(item, PendingAppWidgetHostView.class, null);
    232         // Verify item still exists in db
    233         waitUntilLoaderIdle();
    234         mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
    235                 null, null, null, null, null);
    236         assertEquals(1, mCursor.getCount());
    237 
    238         // Widget still has an invalid id.
    239         mCursor.moveToNext();
    240         assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
    241                 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
    242                         & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
    243     }
    244 
    245     /**
    246      * Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the
    247      * widget class is displayed on the homescreen.
    248      * @param widgetClass the View class which is displayed on the homescreen
    249      * @param desc the content description of the view or null.
    250      */
    251     private void setupAndVerifyContents(
    252             LauncherAppWidgetInfo item, Class<?> widgetClass, String desc) {
    253         long screenId = Workspace.FIRST_SCREEN_ID;
    254         // Update the screen id counter for the provider.
    255         LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
    256 
    257         if (screenId > Workspace.FIRST_SCREEN_ID) {
    258             screenId = Workspace.FIRST_SCREEN_ID;
    259         }
    260         ContentValues v = new ContentValues();
    261         v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
    262         v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0);
    263         mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
    264 
    265         // Insert the item
    266         ContentWriter writer = new ContentWriter(mTargetContext);
    267         item.id = LauncherSettings.Settings.call(
    268                 mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
    269                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
    270         item.screenId = screenId;
    271         item.onAddToDatabase(writer);
    272         writer.put(LauncherSettings.Favorites._ID, item.id);
    273         mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
    274         resetLoaderState();
    275 
    276         // Launch the home activity
    277         mActivityMonitor.startLauncher();
    278         // Verify UI
    279         UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName())
    280                 .className(widgetClass);
    281         if (desc != null) {
    282             selector = selector.description(desc);
    283         }
    284         assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT));
    285     }
    286 
    287     /**
    288      * Creates a LauncherAppWidgetInfo corresponding to {@param info}
    289      * @param bindWidget if true the info is bound and a valid widgetId is assigned to
    290      *                   the LauncherAppWidgetInfo
    291      */
    292     private LauncherAppWidgetInfo createWidgetInfo(
    293             LauncherAppWidgetProviderInfo info, boolean bindWidget) {
    294         LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
    295                 LauncherAppWidgetInfo.NO_ID, info.provider);
    296         item.spanX = info.minSpanX;
    297         item.spanY = info.minSpanY;
    298         item.minSpanX = info.minSpanX;
    299         item.minSpanY = info.minSpanY;
    300         item.user = info.getUser();
    301         item.cellX = 0;
    302         item.cellY = 1;
    303         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
    304 
    305         if (bindWidget) {
    306             PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(info);
    307             pendingInfo.spanX = item.spanX;
    308             pendingInfo.spanY = item.spanY;
    309             pendingInfo.minSpanX = item.minSpanX;
    310             pendingInfo.minSpanY = item.minSpanY;
    311             Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo);
    312 
    313             AppWidgetHost host = new LauncherAppWidgetHost(mTargetContext);
    314             int widgetId = host.allocateAppWidgetId();
    315             if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
    316                 host.deleteAppWidgetId(widgetId);
    317                 throw new IllegalArgumentException("Unable to bind widget id");
    318             }
    319             item.appWidgetId = widgetId;
    320         }
    321         return item;
    322     }
    323 
    324     /**
    325      * Returns a LauncherAppWidgetInfo with package name which is not present on the device
    326      */
    327     private LauncherAppWidgetInfo getInvalidWidgetInfo() {
    328         String invalidPackage = "com.invalidpackage";
    329         int count = 0;
    330         String pkg = invalidPackage;
    331 
    332         Set<String> activePackage = getOnUiThread(new Callable<Set<String>>() {
    333             @Override
    334             public Set<String> call() throws Exception {
    335                 return PackageInstallerCompat.getInstance(mTargetContext)
    336                         .updateAndGetActiveSessionCache().keySet();
    337             }
    338         });
    339         while(true) {
    340             try {
    341                 mTargetContext.getPackageManager().getPackageInfo(
    342                         pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
    343             } catch (Exception e) {
    344                 if (!activePackage.contains(pkg)) {
    345                     break;
    346                 }
    347             }
    348             pkg = invalidPackage + count;
    349             count ++;
    350         }
    351         LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10,
    352                 new ComponentName(pkg, "com.test.widgetprovider"));
    353         item.spanX = 2;
    354         item.spanY = 2;
    355         item.minSpanX = 2;
    356         item.minSpanY = 2;
    357         item.cellX = 0;
    358         item.cellY = 1;
    359         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
    360         return item;
    361     }
    362 
    363     /**
    364      * Blocks the current thread until all the jobs in the main worker thread are complete.
    365      */
    366     private void waitUntilLoaderIdle() throws Exception {
    367         new LooperExecutor(LauncherModel.getWorkerLooper())
    368                 .submit(new Runnable() {
    369                     @Override
    370                     public void run() { }
    371                 }).get(DEFAULT_WORKER_TIMEOUT_SECS, TimeUnit.SECONDS);
    372     }
    373 }
    374