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