• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.cts.launchertests;
17 
18 import static android.os.Process.myUserHandle;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.Truth.assertWithMessage;
22 
23 import static org.junit.Assert.fail;
24 import static org.junit.Assume.assumeFalse;
25 
26 import android.app.Instrumentation;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.ServiceConnection;
33 import android.content.pm.ActivityInfo;
34 import android.content.pm.ApplicationInfo;
35 import android.content.pm.LauncherActivityInfo;
36 import android.content.pm.LauncherApps;
37 import android.content.pm.PackageManager;
38 import android.content.pm.PackageManager.NameNotFoundException;
39 import android.net.Uri;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Looper;
44 import android.os.Message;
45 import android.os.Messenger;
46 import android.os.UserHandle;
47 import android.os.UserManager;
48 import android.util.Log;
49 
50 import androidx.test.InstrumentationRegistry;
51 import androidx.test.runner.AndroidJUnit4;
52 
53 import com.android.compatibility.common.util.SystemUtil;
54 
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.io.IOException;
60 import java.util.List;
61 import java.util.concurrent.Semaphore;
62 import java.util.concurrent.TimeUnit;
63 import java.util.stream.Collectors;
64 
65 /**
66  * Tests for LauncherApps service
67  */
68 @RunWith(AndroidJUnit4.class)
69 public class LauncherAppsTests {
70 
71     private static final String TAG = LauncherAppsTests.class.getSimpleName();
72 
73     public static final String SIMPLE_APP_PACKAGE = "com.android.cts.launcherapps.simpleapp";
74     private static final String HAS_LAUNCHER_ACTIVITY_APP_PACKAGE =
75             "com.android.cts.haslauncheractivityapp";
76     private static final String NO_LAUNCHER_ACTIVITY_APP_PACKAGE =
77             "com.android.cts.nolauncheractivityapp";
78     private static final String NO_PERMISSION_APP_PACKAGE =
79             "com.android.cts.nopermissionapp";
80     private static final String LAUNCHER_ACTIVITY_COMPONENT =
81             "com.android.cts.haslauncheractivityapp/.MainActivity";
82 
83     private static final String SYNTHETIC_APP_DETAILS_ACTIVITY = "android.app.AppDetailsActivity";
84 
85     public static final String USER_EXTRA = "user_extra";
86     public static final String PACKAGE_EXTRA = "package_extra";
87     public static final String REPLY_EXTRA = "reply_extra";
88 
89     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
90 
91     public static final int MSG_RESULT = 0;
92     public static final int MSG_CHECK_PACKAGE_ADDED = 1;
93     public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
94     public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
95     public static final int MSG_CHECK_NO_PACKAGE_ADDED = 4;
96 
97     public static final int RESULT_PASS = 1;
98     public static final int RESULT_FAIL = 2;
99     public static final int RESULT_TIMEOUT = 3;
100 
101     private Context mContext;
102     private UserManager mUserManager;
103     private LauncherApps mLauncherApps;
104     private UserHandle mUser;
105     private Instrumentation mInstrumentation;
106     private Messenger mService;
107     private Connection mConnection;
108     private Result mResult;
109     private Messenger mResultMessenger;
110 
111     @Before
setUp()112     public void setUp() throws Exception {
113         mInstrumentation = InstrumentationRegistry.getInstrumentation();
114         Bundle arguments = InstrumentationRegistry.getArguments();
115         mContext = mInstrumentation.getContext();
116         mUserManager = mContext.getSystemService(UserManager.class);
117         mUser = getUserHandleArgument("testUser", arguments);
118 
119         Log.d(TAG, "Running as user " + myUserHandle() + " and checking for launcher on "
120                 + "user " + mUser);
121         mLauncherApps = mContext.getSystemService(LauncherApps.class);
122 
123         Intent intent = new Intent();
124         intent.setComponent(new ComponentName("com.android.cts.launchertests.support",
125                         "com.android.cts.launchertests.support.LauncherCallbackTestsService"));
126 
127         mConnection = new Connection();
128         mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
129         mConnection.waitForService();
130         mResult = new Result(Looper.getMainLooper());
131         mResultMessenger = new Messenger(mResult);
132     }
133 
134     @Test
testGetActivitiesForUserFails()135     public void testGetActivitiesForUserFails() throws Exception {
136         expectSecurityException(() -> mLauncherApps.getActivityList(null, mUser),
137                 "getActivities for non-profile user failed to throw exception");
138     }
139 
140     @Test
testSimpleAppInstalledForUser()141     public void testSimpleAppInstalledForUser() throws Exception {
142         List<LauncherActivityInfo> activities =
143                 mLauncherApps.getActivityList(null, mUser);
144         // Check simple app is there.
145         boolean foundSimpleApp = false;
146         for (LauncherActivityInfo activity : activities) {
147             if (activity.getComponentName().getPackageName().equals(
148                     SIMPLE_APP_PACKAGE)) {
149                 foundSimpleApp = true;
150                 assertThat(activity.getLoadingProgress()).isWithin(1.0e-10f).of(1.0f);
151             }
152             assertThat(activity.getUser()).isEqualTo(mUser);
153         }
154         assertThat(foundSimpleApp).isTrue();
155 
156         // Also make sure getApplicationInfo works too.
157         ApplicationInfo ai =
158                 mLauncherApps.getApplicationInfo(SIMPLE_APP_PACKAGE, /* flags= */ 0, mUser);
159         assertThat(ai.packageName).isEqualTo(SIMPLE_APP_PACKAGE);
160         assertThat(UserHandle.getUserHandleForUid(ai.uid)).isEqualTo(mUser);
161     }
162 
163     @Test
testAccessPrimaryProfileFromManagedProfile()164     public void testAccessPrimaryProfileFromManagedProfile() throws Exception {
165         assertThat(mLauncherApps.getActivityList(null, mUser)).isEmpty();
166 
167         expectNameNotFoundException(
168                 () -> mLauncherApps.getApplicationInfo(SIMPLE_APP_PACKAGE, /* flags= */ 0, mUser),
169                 "get applicationInfo failed to throw name not found exception");
170         assertThat(mLauncherApps.isPackageEnabled(SIMPLE_APP_PACKAGE, mUser)).isFalse();
171 
172         Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.android.com/"));
173         assertThat(mLauncherApps.resolveActivity(intent, mUser)).isNull();
174     }
175 
176     @Test
testGetProfiles_fromMainProfile()177     public void testGetProfiles_fromMainProfile() {
178         List<UserHandle> profiles = mLauncherApps.getProfiles();
179         assertThat(profiles).hasSize(2);
180         assertThat(profiles).contains(myUserHandle());
181         assertThat(profiles).containsExactlyElementsIn(mUserManager.getUserProfiles());
182     }
183 
184     @Test
testGetProfiles_fromManagedProfile()185     public void testGetProfiles_fromManagedProfile() {
186         final List<UserHandle> profiles = mLauncherApps.getProfiles();
187         assertThat(profiles).containsExactly(myUserHandle());
188     }
189 
190     @Test
testPackageAddedCallbackForUser()191     public void testPackageAddedCallbackForUser() throws Throwable {
192         int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_ADDED,
193                 mUser, SIMPLE_APP_PACKAGE);
194         assertThat(result).isEqualTo(RESULT_PASS);
195     }
196 
197     @Test
testPackageRemovedCallbackForUser()198     public void testPackageRemovedCallbackForUser() throws Throwable {
199         int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_REMOVED,
200                 mUser, SIMPLE_APP_PACKAGE);
201         assertThat(result).isEqualTo(RESULT_PASS);
202     }
203 
204     @Test
testPackageChangedCallbackForUser()205     public void testPackageChangedCallbackForUser() throws Throwable {
206         int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_CHANGED,
207                 mUser, SIMPLE_APP_PACKAGE);
208         assertThat(result).isEqualTo(RESULT_PASS);
209     }
210 
211     @Test
testNoPackageAddedCallbackForUser()212     public void testNoPackageAddedCallbackForUser() throws Throwable {
213         int result = sendMessageToCallbacksService(MSG_CHECK_NO_PACKAGE_ADDED,
214                 mUser, SIMPLE_APP_PACKAGE);
215         assertThat(result).isEqualTo(RESULT_PASS);
216     }
217 
218     @Test
testLaunchNonExportActivityFails()219     public void testLaunchNonExportActivityFails() throws Exception {
220         expectSecurityException(() -> mLauncherApps.startMainActivity(new ComponentName(
221                 SIMPLE_APP_PACKAGE, SIMPLE_APP_PACKAGE + ".NonExportedActivity"),
222                 mUser, null, null),
223                 "starting non-exported activity failed to throw exception");
224     }
225 
226     @Test
testLaunchNonExportLauncherFails()227     public void testLaunchNonExportLauncherFails() throws Exception {
228         expectSecurityException(() -> mLauncherApps.startMainActivity(new ComponentName(
229                 SIMPLE_APP_PACKAGE, SIMPLE_APP_PACKAGE + ".NonLauncherActivity"),
230                 mUser, null, null),
231                 "starting non-launcher activity failed to throw exception");
232     }
233 
234     @Test
testLaunchMainActivity()235     public void testLaunchMainActivity() throws Exception {
236         ActivityLaunchedReceiver receiver = new ActivityLaunchedReceiver();
237         IntentFilter filter = new IntentFilter();
238         filter.addAction(ActivityLaunchedReceiver.ACTIVITY_LAUNCHED_ACTION);
239         mContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
240         ComponentName compName = new ComponentName(SIMPLE_APP_PACKAGE, SIMPLE_APP_PACKAGE
241                 + ".SimpleActivity");
242         Log.i(TAG, "Launching " + compName.flattenToShortString() + " on user " + mUser);
243         mLauncherApps.startMainActivity(compName, mUser, null, null);
244         assertWithMessage("Activity %s launched for user %s", compName.flattenToShortString(),
245                 mUser).that(receiver.waitForActivity()).isEqualTo(RESULT_PASS);
246         mContext.unregisterReceiver(receiver);
247     }
248 
249     @Test
testReverseAccessNoThrow()250     public void testReverseAccessNoThrow() throws Exception {
251         // Trying to access the main profile from a managed profile -> shouldn't throw but
252         // should just return false.
253         assertThat(mLauncherApps.isPackageEnabled("android", mUser)).isFalse();
254     }
255 
256     @Test
testProfileOwnerLauncherActivityInjected()257     public void testProfileOwnerLauncherActivityInjected() throws Exception {
258         assertActivityInjected(MANAGED_PROFILE_PKG);
259     }
260 
261     @Test
testNoLauncherActivityAppNotInjected()262     public void testNoLauncherActivityAppNotInjected() throws Exception {
263         // NoLauncherActivityApp is installed for duration of this test - make sure
264         // it's NOT present on the activity list
265         assertInjectedActivityNotFound(NO_LAUNCHER_ACTIVITY_APP_PACKAGE);
266     }
267 
268     @Test
testNoPermissionAppNotInjected()269     public void testNoPermissionAppNotInjected() throws Exception {
270         // NoPermissionApp is installed for duration of this test - make sure
271         // it's NOT present on the activity list
272         assertInjectedActivityNotFound(NO_PERMISSION_APP_PACKAGE);
273     }
274 
275 
276 
277     @Test
testProfileOwnerInjectedActivityNotFound()278     public void testProfileOwnerInjectedActivityNotFound() throws Exception {
279         assertInjectedActivityNotFound(MANAGED_PROFILE_PKG);
280     }
281 
282     @Test
testNoSystemAppHasSyntheticAppDetailsActivityInjected()283     public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
284         Log.d(TAG, "testNoSystemAppHasSyntheticAppDetailsActivityInjected() for user " + mUser);
285         List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
286         logActivities(activities);
287         for (LauncherActivityInfo activity : activities) {
288             if (!activity.getUser().equals(mUser)) {
289                 continue;
290             }
291             ApplicationInfo appInfo = activity.getApplicationInfo();
292             boolean isSystemApp = ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
293                     || ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
294             if (isSystemApp) {
295                 // make sure we haven't generated a synthetic app details activity for it
296                 assertWithMessage("Found a system app that had a synthetic activity generated,"
297                         + " package name: %s; activity name: %s",
298                         activity.getComponentName().getPackageName(), activity.getName())
299                                 .that(activity.getName())
300                                 .isNotEqualTo(SYNTHETIC_APP_DETAILS_ACTIVITY);
301             }
302         }
303     }
304 
disableLauncherActivity()305     private void disableLauncherActivity() throws IOException {
306         runShellCommand("pm disable --user %d %s", mUser.getIdentifier(),
307                 LAUNCHER_ACTIVITY_COMPONENT);
308     }
309 
expectSecurityException(ExceptionRunnable action, String failMessage)310     private void expectSecurityException(ExceptionRunnable action, String failMessage)
311             throws Exception {
312         try {
313             action.run();
314             fail(failMessage);
315         } catch (SecurityException e) {
316             // expected
317         }
318     }
319 
expectNameNotFoundException(ExceptionRunnable action, String failMessage)320     private void expectNameNotFoundException(ExceptionRunnable action, String failMessage)
321             throws Exception {
322         try {
323             action.run();
324             fail(failMessage);
325         } catch (PackageManager.NameNotFoundException e) {
326             // expected
327         }
328     }
329 
assertActivityInjected(String targetPackage)330     private void assertActivityInjected(String targetPackage) {
331         Log.d(TAG, "Getting activities for package " + targetPackage + " on user " + mUser);
332         List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
333         logActivities(activities);
334 
335         boolean noLaunchableActivityAppFound = false;
336         for (LauncherActivityInfo activity : activities) {
337             UserHandle user = activity.getUser();
338             if (!user.equals(mUser)) {
339                 Log.w(TAG, "Skipping activity " + toString(activity) + " from user " + user);
340                 continue;
341             }
342             ComponentName compName = activity.getComponentName();
343             if (compName.getPackageName().equals(targetPackage)) {
344                 noLaunchableActivityAppFound = true;
345                 // make sure it points to the synthetic app details activity
346                 assertWithMessage("name of synthetic app").that(activity.getName())
347                         .isEqualTo(SYNTHETIC_APP_DETAILS_ACTIVITY);
348                 // make sure it's both exported and enabled
349                 try {
350                     PackageManager pm = mContext.getPackageManager();
351                     ActivityInfo ai = pm.getActivityInfo(compName, /* flags= */ 0);
352                     assertWithMessage("component %s enabled", compName.flattenToShortString())
353                             .that(ai.enabled).isTrue();
354                     assertWithMessage("component %s exported", compName.flattenToShortString())
355                             .that(ai.exported).isTrue();
356                 } catch (NameNotFoundException e) {
357                     fail("Package " + compName.getPackageName() + " not found: " + e);
358                 }
359             }
360         }
361         assertWithMessage("user %s has no launchable activity for app %s", mUser, targetPackage)
362                 .that(noLaunchableActivityAppFound).isTrue();
363     }
364 
365     @FunctionalInterface
366     public interface ExceptionRunnable {
run()367         void run() throws Exception;
368     }
369 
getUserHandleArgument(String key, Bundle arguments)370     private UserHandle getUserHandleArgument(String key, Bundle arguments) throws Exception {
371         String serial = arguments.getString(key);
372         if (serial == null) {
373             return null;
374         }
375         int serialNo = Integer.parseInt(serial);
376         return mUserManager.getUserForSerialNumber(serialNo);
377     }
378 
379     private final class Connection implements ServiceConnection {
380         private final Semaphore mSemaphore = new Semaphore(0);
381 
382         @Override
onServiceConnected(ComponentName className, IBinder service)383         public void onServiceConnected(ComponentName className, IBinder service) {
384             mService = new Messenger(service);
385             mSemaphore.release();
386         }
387 
388         @Override
onServiceDisconnected(ComponentName className)389         public void onServiceDisconnected(ComponentName className) {
390             mService = null;
391         }
392 
waitForService()393         public void waitForService() {
394             try {
395                 if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
396                     return;
397                 }
398             } catch (InterruptedException e) {
399             }
400             fail("failed to connec to service");
401         }
402     };
403 
404     private static final class Result extends Handler {
405 
406         private final Semaphore mSemaphore = new Semaphore(0);
407         public int result = 0;
408 
Result(Looper looper)409         public Result(Looper looper) {
410             super(looper);
411         }
412 
413         @Override
handleMessage(Message msg)414         public void handleMessage(Message msg) {
415             if (msg.what == MSG_RESULT) {
416                 result = msg.arg1;
417                 mSemaphore.release();
418             } else {
419                 super.handleMessage(msg);
420             }
421         }
422 
waitForResult()423         public int waitForResult() {
424             try {
425                 if (mSemaphore.tryAcquire(120, TimeUnit.SECONDS)) {
426                      return result;
427                 }
428             } catch (InterruptedException e) {
429             }
430             return RESULT_TIMEOUT;
431         }
432     }
433 
434     public final class ActivityLaunchedReceiver extends BroadcastReceiver {
435         public static final String ACTIVITY_LAUNCHED_ACTION =
436                 "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
437 
438         private final Semaphore mSemaphore = new Semaphore(0);
439 
440         @Override
onReceive(Context context, Intent intent)441         public void onReceive(Context context, Intent intent) {
442             if (intent.getAction().equals(ACTIVITY_LAUNCHED_ACTION)) {
443                 mSemaphore.release();
444             }
445         }
446 
waitForActivity()447         public int waitForActivity() {
448             try {
449                 if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
450                     return RESULT_PASS;
451                 }
452             } catch (InterruptedException e) {
453             }
454             return RESULT_TIMEOUT;
455         }
456     }
457 
sendMessageToCallbacksService(int msg, UserHandle user, String packageName)458     private int sendMessageToCallbacksService(int msg, UserHandle user, String packageName)
459             throws Throwable {
460         Bundle params = new Bundle();
461         params.putParcelable(USER_EXTRA, user);
462         params.putString(PACKAGE_EXTRA, packageName);
463 
464         Message message = Message.obtain(null, msg, params);
465         message.replyTo = mResultMessenger;
466 
467         mService.send(message);
468 
469         return mResult.waitForResult();
470     }
471 
assertInjectedActivityNotFound(String targetPackage)472     private void assertInjectedActivityNotFound(String targetPackage) {
473         Log.d(TAG, "Searching for package " + targetPackage + " on user " + mUser);
474         List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(null, mUser);
475         logActivities(activities);
476         for (LauncherActivityInfo activity : activities) {
477             if (!activity.getUser().equals(mUser)) {
478                 continue;
479             }
480             ComponentName compName = activity.getComponentName();
481             if (compName.getPackageName().equals(targetPackage)) {
482                 fail("Injected activity found: " + compName.flattenToString());
483             }
484         }
485     }
486 
logActivities(List<LauncherActivityInfo> activities)487     private void logActivities(List<LauncherActivityInfo> activities) {
488         Log.d(TAG, "Got " + activities.size() + " activities: " + activities.stream()
489                 .map((info) -> toString(info))
490                 .collect(Collectors.toList()));
491     }
492 
runShellCommand(String format, Object...args)493     private void runShellCommand(String format, Object...args) throws IOException {
494         String command = String.format(format, args);
495         Log.i(TAG, "Running command: " + command);
496         String output = SystemUtil.runShellCommand(mInstrumentation, command);
497         Log.d(TAG, "Output: " + output);
498     }
499 
toString(LauncherActivityInfo info)500     private String toString(LauncherActivityInfo info) {
501         return info == null ? null : info.getComponentName().flattenToShortString();
502     }
503 
assumeNotHeadlessSystemUserMode()504     private void assumeNotHeadlessSystemUserMode() {
505         // On headless system user mode, the current user is a profile owner, and hence
506         // the synthetic activity is not listed by LauncherApps.getActivityList()
507         assumeFalse("test skipped on headless system user mode",
508                 UserManager.isHeadlessSystemUserMode());
509     }
510 }
511