• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.util;
17 
18 import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
19 
20 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
21 
22 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
23 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
24 import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
25 
26 import static org.mockito.ArgumentMatchers.anyInt;
27 import static org.mockito.ArgumentMatchers.eq;
28 import static org.mockito.Mockito.doReturn;
29 import static org.mockito.Mockito.spy;
30 
31 import android.content.ContentProvider;
32 import android.content.ContentResolver;
33 import android.content.pm.PackageInstaller;
34 import android.content.pm.PackageInstaller.SessionParams;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ProviderInfo;
37 import android.graphics.Bitmap;
38 import android.graphics.Bitmap.Config;
39 import android.graphics.Color;
40 import android.net.Uri;
41 import android.os.ParcelFileDescriptor;
42 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
43 import android.provider.Settings;
44 import android.test.mock.MockContentResolver;
45 import android.util.ArrayMap;
46 
47 import androidx.annotation.NonNull;
48 import androidx.test.core.app.ApplicationProvider;
49 import androidx.test.uiautomator.UiDevice;
50 
51 import com.android.launcher3.InvariantDeviceProfile;
52 import com.android.launcher3.LauncherAppState;
53 import com.android.launcher3.LauncherModel;
54 import com.android.launcher3.LauncherModel.ModelUpdateTask;
55 import com.android.launcher3.LauncherPrefs;
56 import com.android.launcher3.model.AllAppsList;
57 import com.android.launcher3.model.BgDataModel;
58 import com.android.launcher3.model.BgDataModel.Callbacks;
59 import com.android.launcher3.model.ItemInstallQueue;
60 import com.android.launcher3.pm.InstallSessionHelper;
61 import com.android.launcher3.pm.UserCache;
62 import com.android.launcher3.testing.TestInformationProvider;
63 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
64 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
65 import com.android.launcher3.util.window.WindowManagerProxy;
66 import com.android.launcher3.widget.custom.CustomWidgetManager;
67 
68 import java.io.ByteArrayOutputStream;
69 import java.io.File;
70 import java.io.FileNotFoundException;
71 import java.io.IOException;
72 import java.io.OutputStreamWriter;
73 import java.util.UUID;
74 import java.util.concurrent.CountDownLatch;
75 import java.util.concurrent.ExecutionException;
76 import java.util.concurrent.Executor;
77 
78 /**
79  * Utility class to help manage Launcher Model and related objects for test.
80  */
81 public class LauncherModelHelper {
82 
83     public static final String TEST_PACKAGE = getInstrumentation().getContext().getPackageName();
84     public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2";
85     public static final String TEST_ACTIVITY2 = "com.android.launcher3.tests.Activity3";
86     public static final String TEST_ACTIVITY3 = "com.android.launcher3.tests.Activity4";
87 
88     // Authority for providing a test default-workspace-layout data.
89     private static final String TEST_PROVIDER_AUTHORITY =
90             LauncherModelHelper.class.getName().toLowerCase();
91     private static final int DEFAULT_BITMAP_SIZE = 10;
92     private static final int DEFAULT_GRID_SIZE = 4;
93 
94     public final SandboxModelContext sandboxContext;
95 
96     private final RunnableList mDestroyTask = new RunnableList();
97 
98     private BgDataModel mDataModel;
99 
LauncherModelHelper()100     public LauncherModelHelper() {
101         sandboxContext = new SandboxModelContext();
102     }
103 
setupProvider(String authority, ContentProvider provider)104     public void setupProvider(String authority, ContentProvider provider) {
105         sandboxContext.setupProvider(authority, provider);
106     }
107 
getModel()108     public LauncherModel getModel() {
109         return LauncherAppState.getInstance(sandboxContext).getModel();
110     }
111 
getBgDataModel()112     public synchronized BgDataModel getBgDataModel() {
113         if (mDataModel == null) {
114             getModel().enqueueModelUpdateTask(new ModelUpdateTask() {
115                 @Override
116                 public void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
117                         @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
118                         @NonNull Executor uiExecutor) {
119                     mDataModel = dataModel;
120                 }
121 
122                 @Override
123                 public void run() { }
124             });
125         }
126         return mDataModel;
127     }
128 
129     /**
130      * Creates a installer session for the provided package.
131      */
createInstallerSession(String pkg)132     public int createInstallerSession(String pkg) throws IOException {
133         SessionParams sp = new SessionParams(MODE_FULL_INSTALL);
134         sp.setAppPackageName(pkg);
135         Bitmap icon = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
136         icon.eraseColor(Color.RED);
137         sp.setAppIcon(icon);
138         sp.setAppLabel(pkg);
139         PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller();
140         int sessionId = pi.createSession(sp);
141         mDestroyTask.add(() -> pi.abandonSession(sessionId));
142         return sessionId;
143     }
144 
destroy()145     public void destroy() {
146         // When destroying the context, make sure that the model thread is blocked, so that no
147         // new jobs get posted while we are cleaning up
148         CountDownLatch l1 = new CountDownLatch(1);
149         CountDownLatch l2 = new CountDownLatch(1);
150         MODEL_EXECUTOR.execute(() -> {
151             l1.countDown();
152             waitOrThrow(l2);
153         });
154         waitOrThrow(l1);
155         sandboxContext.onDestroy();
156         l2.countDown();
157 
158         mDestroyTask.executeAllAndDestroy();
159     }
160 
waitOrThrow(CountDownLatch latch)161     private void waitOrThrow(CountDownLatch latch) {
162         try {
163             latch.await();
164         } catch (Exception e) {
165             throw new RuntimeException(e);
166         }
167     }
168 
169     /**
170      * Sets up a mock provider to load the provided layout by default, next time the layout loads
171      */
setupDefaultLayoutProvider(LauncherLayoutBuilder builder)172     public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
173             throws Exception {
174         InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext);
175         idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE;
176         idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
177 
178         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
179                 "settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY);
180         ContentProvider cp = new TestInformationProvider() {
181 
182             @Override
183             public ParcelFileDescriptor openFile(Uri uri, String mode)
184                     throws FileNotFoundException {
185                 try {
186                     ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
187                     AutoCloseOutputStream outputStream = new AutoCloseOutputStream(pipe[1]);
188                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
189                     builder.build(new OutputStreamWriter(bos));
190                     outputStream.write(bos.toByteArray());
191                     outputStream.flush();
192                     outputStream.close();
193                     return pipe[0];
194                 } catch (Exception e) {
195                     throw new FileNotFoundException(e.getMessage());
196                 }
197             }
198         };
199         setupProvider(TEST_PROVIDER_AUTHORITY, cp);
200         mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
201                 UiDevice.getInstance(getInstrumentation()).executeShellCommand(
202                         "settings delete secure launcher3.layout.provider")));
203         return this;
204     }
205 
206     /**
207      * Loads the model in memory synchronously
208      */
loadModelSync()209     public void loadModelSync() throws ExecutionException, InterruptedException {
210         Callbacks mockCb = new Callbacks() { };
211         MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
212 
213         Executors.MODEL_EXECUTOR.submit(() -> { }).get();
214         MAIN_EXECUTOR.submit(() -> { }).get();
215         MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
216     }
217 
218     public static class SandboxModelContext extends SandboxContext {
219 
220         private final MockContentResolver mMockResolver = new MockContentResolver();
221         private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
222         private final PackageManager mPm;
223         private final File mDbDir;
224 
SandboxModelContext()225         SandboxModelContext() {
226             super(ApplicationProvider.getApplicationContext(),
227                     UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
228                     LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
229                     DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
230                     SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
231                     LockedUserState.INSTANCE, WallpaperColorHints.INSTANCE,
232                     ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
233 
234             // System settings cache content provider. Ensure that they are statically initialized
235             Settings.Secure.getString(
236                     ApplicationProvider.getApplicationContext().getContentResolver(), "test");
237             Settings.System.getString(
238                     ApplicationProvider.getApplicationContext().getContentResolver(), "test");
239             Settings.Global.getString(
240                     ApplicationProvider.getApplicationContext().getContentResolver(), "test");
241 
242             mPm = spy(getBaseContext().getPackageManager());
243             mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
244         }
245 
246         @Override
createObject(MainThreadInitializedObject<T> object)247         protected <T> T createObject(MainThreadInitializedObject<T> object) {
248             if (object == LauncherAppState.INSTANCE) {
249                 return (T) new LauncherAppState(this, null /* iconCacheFileName */);
250             }
251             return super.createObject(object);
252         }
253 
allow(MainThreadInitializedObject object)254         public SandboxModelContext allow(MainThreadInitializedObject object) {
255             mAllowedObjects.add(object);
256             return this;
257         }
258 
259         @Override
getDatabasePath(String name)260         public File getDatabasePath(String name) {
261             if (!mDbDir.exists()) {
262                 mDbDir.mkdirs();
263             }
264             return new File(mDbDir, name);
265         }
266 
267         @Override
getContentResolver()268         public ContentResolver getContentResolver() {
269             return mMockResolver;
270         }
271 
272         @Override
onDestroy()273         public void onDestroy() {
274             if (deleteContents(mDbDir)) {
275                 mDbDir.delete();
276             }
277             super.onDestroy();
278         }
279 
280         @Override
getPackageManager()281         public PackageManager getPackageManager() {
282             return mPm;
283         }
284 
285         @Override
getSystemService(String name)286         public Object getSystemService(String name) {
287             Object service = mSpiedServices.get(name);
288             return service != null ? service : super.getSystemService(name);
289         }
290 
spyService(Class<T> tClass)291         public <T> T spyService(Class<T> tClass) {
292             String name = getSystemServiceName(tClass);
293             Object service = mSpiedServices.get(name);
294             if (service != null) {
295                 return (T) service;
296             }
297 
298             T result = spy(getSystemService(tClass));
299             mSpiedServices.put(name, result);
300             return result;
301         }
302 
setupProvider(String authority, ContentProvider provider)303         public void setupProvider(String authority, ContentProvider provider) {
304             ProviderInfo providerInfo = new ProviderInfo();
305             providerInfo.authority = authority;
306             providerInfo.applicationInfo = getApplicationInfo();
307             provider.attachInfo(this, providerInfo);
308             mMockResolver.addProvider(providerInfo.authority, provider);
309             doReturn(providerInfo).when(mPm).resolveContentProvider(eq(authority), anyInt());
310         }
311 
deleteContents(File dir)312         private static boolean deleteContents(File dir) {
313             File[] files = dir.listFiles();
314             boolean success = true;
315             if (files != null) {
316                 for (File file : files) {
317                     if (file.isDirectory()) {
318                         success &= deleteContents(file);
319                     }
320                     if (!file.delete()) {
321                         success = false;
322                     }
323                 }
324             }
325             return success;
326         }
327     }
328 }
329