From 60b2960cbb4df5310d054da57e2ec134b3fca0e6 Mon Sep 17 00:00:00 2001 From: Denis Kuznetsov Date: Wed, 12 Apr 2017 15:15:53 +0200 Subject: [PATCH] DO Disclosure: add UI that lists apps that were managed by owner: - had permissions granted by admin - were installed by owner via policy Bug: 32692748 Test: m RunSettingsRoboTests Change-Id: I365e2f8f351671e68f83cceb7c0ca241d7a5a588 --- res/xml/app_list_disclosure_settings.xml | 27 ++ res/xml/enterprise_privacy_settings.xml | 8 +- .../settings/applications/AppLister.java | 69 ++++++ ...AppWithAdminGrantedPermissionsCounter.java | 19 +- .../AppWithAdminGrantedPermissionsLister.java | 46 ++++ .../ApplicationFeatureProvider.java | 26 ++ .../ApplicationFeatureProviderImpl.java | 51 ++++ .../applications/InstalledAppCounter.java | 15 +- .../applications/InstalledAppLister.java | 34 +++ .../settings/applications/UserAppInfo.java | 57 +++++ ...edPermissionsPreferenceControllerBase.java | 16 +- .../enterprise/ApplicationListFragment.java | 110 +++++++++ .../ApplicationListPreferenceController.java | 86 +++++++ ...randfather_not_implementing_index_provider | 4 + ...ithAdminGrantedPermissionsCounterTest.java | 214 +++++++++------- ...WithAdminGrantedPermissionsListerTest.java | 223 +++++++++++++++++ .../ApplicationFeatureProviderImplTest.java | 48 +++- .../applications/InstalledAppCounterTest.java | 157 +++++++----- .../applications/InstalledAppListerTest.java | 233 ++++++++++++++++++ ...rmissionsPreferenceControllerTestBase.java | 24 +- .../ApplicationListFragmentTest.java | 131 ++++++++++ ...plicationListPreferenceControllerTest.java | 132 ++++++++++ 22 files changed, 1544 insertions(+), 186 deletions(-) create mode 100644 res/xml/app_list_disclosure_settings.xml create mode 100644 src/com/android/settings/applications/AppLister.java create mode 100644 src/com/android/settings/applications/AppWithAdminGrantedPermissionsLister.java create mode 100644 src/com/android/settings/applications/InstalledAppLister.java create mode 100644 src/com/android/settings/applications/UserAppInfo.java create mode 100644 src/com/android/settings/enterprise/ApplicationListFragment.java create mode 100644 src/com/android/settings/enterprise/ApplicationListPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsListerTest.java create mode 100644 tests/robotests/src/com/android/settings/applications/InstalledAppListerTest.java create mode 100644 tests/robotests/src/com/android/settings/enterprise/ApplicationListFragmentTest.java create mode 100644 tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java diff --git a/res/xml/app_list_disclosure_settings.xml b/res/xml/app_list_disclosure_settings.xml new file mode 100644 index 00000000000..2aae95ee5e5 --- /dev/null +++ b/res/xml/app_list_disclosure_settings.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/res/xml/enterprise_privacy_settings.xml b/res/xml/enterprise_privacy_settings.xml index c2b77b5f37b..c84a33d1699 100644 --- a/res/xml/enterprise_privacy_settings.xml +++ b/res/xml/enterprise_privacy_settings.xml @@ -16,8 +16,8 @@ + android:key="enterprise_privacy_settings" + android:title="@string/enterprise_privacy_settings"> > { + protected final PackageManagerWrapper mPm; + protected final UserManager mUm; + + public AppLister(PackageManagerWrapper packageManager, UserManager userManager) { + mPm = packageManager; + mUm = userManager; + } + + @Override + protected List doInBackground(Void... params) { + final List result = new ArrayList<>(); + for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) { + final List list = + mPm.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS + | (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0), + user.id); + for (ApplicationInfo info : list) { + if (includeInCount(info)) { + result.add(new UserAppInfo(user, info)); + } + } + } + return result; + } + + @Override + protected void onPostExecute(List list) { + onAppListBuilt(list); + } + + protected abstract void onAppListBuilt(List list); + protected abstract boolean includeInCount(ApplicationInfo info); +} diff --git a/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java index 52f1da5aace..c7d0a627268 100644 --- a/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java +++ b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java @@ -31,7 +31,6 @@ import com.android.settings.enterprise.DevicePolicyManagerWrapper; public abstract class AppWithAdminGrantedPermissionsCounter extends AppCounter { private final String[] mPermissions; - private final PackageManagerWrapper mPackageManager; private final IPackageManagerWrapper mPackageManagerService; private final DevicePolicyManagerWrapper mDevicePolicyManager; @@ -40,18 +39,24 @@ public abstract class AppWithAdminGrantedPermissionsCounter extends AppCounter { DevicePolicyManagerWrapper devicePolicyManager) { super(context, packageManager); mPermissions = permissions; - mPackageManager = packageManager; mPackageManagerService = packageManagerService; mDevicePolicyManager = devicePolicyManager; } @Override protected boolean includeInCount(ApplicationInfo info) { + return includeInCount(mPermissions, mDevicePolicyManager, mPm, mPackageManagerService, + info); + } + + public static boolean includeInCount(String[] permissions, + DevicePolicyManagerWrapper devicePolicyManager, PackageManagerWrapper packageManager, + IPackageManagerWrapper packageManagerService, ApplicationInfo info) { if (info.targetSdkVersion >= Build.VERSION_CODES.M) { // The app uses run-time permissions. Check whether one or more of the permissions were // granted by enterprise policy. - for (final String permission : mPermissions) { - if (mDevicePolicyManager.getPermissionGrantState(null /* admin */, info.packageName, + for (final String permission : permissions) { + if (devicePolicyManager.getPermissionGrantState(null /* admin */, info.packageName, permission) == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED) { return true; } @@ -61,14 +66,14 @@ public abstract class AppWithAdminGrantedPermissionsCounter extends AppCounter { // The app uses install-time permissions. Check whether the app requested one or more of the // permissions and was installed by enterprise policy, implicitly granting permissions. - if (mPackageManager.getInstallReason(info.packageName, + if (packageManager.getInstallReason(info.packageName, new UserHandle(UserHandle.getUserId(info.uid))) != PackageManager.INSTALL_REASON_POLICY) { return false; } try { - for (final String permission : mPermissions) { - if (mPackageManagerService.checkUidPermission(permission, info.uid) + for (final String permission : permissions) { + if (packageManagerService.checkUidPermission(permission, info.uid) == PackageManager.PERMISSION_GRANTED) { return true; } diff --git a/src/com/android/settings/applications/AppWithAdminGrantedPermissionsLister.java b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsLister.java new file mode 100644 index 00000000000..b21f31f3a7d --- /dev/null +++ b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsLister.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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.applications; + +import android.content.pm.ApplicationInfo; +import android.os.UserManager; +import com.android.settings.enterprise.DevicePolicyManagerWrapper; + +/** + * Lists installed apps across all users that have been granted one or more specific permissions by + * the admin. + */ +public abstract class AppWithAdminGrantedPermissionsLister extends AppLister { + private final String[] mPermissions; + private final IPackageManagerWrapper mPackageManagerService; + private final DevicePolicyManagerWrapper mDevicePolicyManager; + + public AppWithAdminGrantedPermissionsLister(String[] permissions, + PackageManagerWrapper packageManager, IPackageManagerWrapper packageManagerService, + DevicePolicyManagerWrapper devicePolicyManager, UserManager userManager) { + super(packageManager, userManager); + mPermissions = permissions; + mPackageManagerService = packageManagerService; + mDevicePolicyManager = devicePolicyManager; + } + + @Override + protected boolean includeInCount(ApplicationInfo info) { + return AppWithAdminGrantedPermissionsCounter.includeInCount(mPermissions, + mDevicePolicyManager, mPm, mPackageManagerService, info); + } +} diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java index 5e986db959c..e3ad4c9327c 100644 --- a/src/com/android/settings/applications/ApplicationFeatureProvider.java +++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java @@ -22,6 +22,7 @@ import android.app.Fragment; import android.content.Intent; import android.view.View; +import java.util.List; import java.util.Set; public interface ApplicationFeatureProvider { @@ -48,6 +49,14 @@ public interface ApplicationFeatureProvider { */ void calculateNumberOfPolicyInstalledApps(boolean async, NumberOfAppsCallback callback); + /** + * Asynchronously builds the list of apps installed on the device via policy in the current user + * and all its managed profiles. + * + * @param callback The callback to invoke with the result + */ + void listPolicyInstalledApps(ListOfAppsCallback callback); + /** * Asynchronously calculates the total number of apps installed in the current user and all its * managed profiles that have been granted one or more of the given permissions by the admin. @@ -60,6 +69,16 @@ public interface ApplicationFeatureProvider { void calculateNumberOfAppsWithAdminGrantedPermissions(String[] permissions, boolean async, NumberOfAppsCallback callback); + /** + * Asynchronously builds the list of apps installed in the current user and all its + * managed profiles that have been granted one or more of the given permissions by the admin. + * + * @param permissions Only consider apps that have been granted one or more of these permissions + * by the admin, either at run-time or install-time + * @param callback The callback to invoke with the result + */ + void listAppsWithAdminGrantedPermissions(String[] permissions, ListOfAppsCallback callback); + /** * Return the persistent preferred activities configured by the admin for the current user and * all its managed profiles. A persistent preferred activity is an activity that the admin @@ -79,6 +98,13 @@ public interface ApplicationFeatureProvider { void onNumberOfAppsResult(int num); } + /** + * Callback that receives the list of packages installed on the device. + */ + interface ListOfAppsCallback { + void onListOfAppsResult(List result); + } + public static class PersistentPreferredActivityInfo { public final String packageName; public final int userId; diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java index 417185787dd..353252d8572 100644 --- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java +++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java @@ -73,6 +73,13 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide } } + @Override + public void listPolicyInstalledApps(ListOfAppsCallback callback) { + final CurrentUserPolicyInstalledAppLister lister = + new CurrentUserPolicyInstalledAppLister(mPm, mUm, callback); + lister.execute(); + } + @Override public void calculateNumberOfAppsWithAdminGrantedPermissions(String[] permissions, boolean async, NumberOfAppsCallback callback) { @@ -86,6 +93,15 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide } } + @Override + public void listAppsWithAdminGrantedPermissions(String[] permissions, + ListOfAppsCallback callback) { + final CurrentUserAppWithAdminGrantedPermissionsLister lister = + new CurrentUserAppWithAdminGrantedPermissionsLister(permissions, mPm, mPms, mDpm, + mUm, callback); + lister.execute(); + } + @Override public Set findPersistentPreferredActivities( Intent[] intents) { @@ -152,4 +168,39 @@ public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvide mCallback.onNumberOfAppsResult(num); } } + + private static class CurrentUserPolicyInstalledAppLister extends InstalledAppLister { + private ListOfAppsCallback mCallback; + + CurrentUserPolicyInstalledAppLister(PackageManagerWrapper packageManager, + UserManager userManager, ListOfAppsCallback callback) { + super(packageManager, userManager); + mCallback = callback; + } + + @Override + protected void onAppListBuilt(List list) { + mCallback.onListOfAppsResult(list); + } + } + + private static class CurrentUserAppWithAdminGrantedPermissionsLister extends + AppWithAdminGrantedPermissionsLister { + private ListOfAppsCallback mCallback; + + CurrentUserAppWithAdminGrantedPermissionsLister(String[] permissions, + PackageManagerWrapper packageManager, IPackageManagerWrapper packageManagerService, + DevicePolicyManagerWrapper devicePolicyManager, UserManager userManager, + ListOfAppsCallback callback) { + super(permissions, packageManager, packageManagerService, devicePolicyManager, + userManager); + mCallback = callback; + } + + @Override + protected void onAppListBuilt(List list) { + mCallback.onListOfAppsResult(list); + } + } + } diff --git a/src/com/android/settings/applications/InstalledAppCounter.java b/src/com/android/settings/applications/InstalledAppCounter.java index 8065d490b42..932facee6f5 100644 --- a/src/com/android/settings/applications/InstalledAppCounter.java +++ b/src/com/android/settings/applications/InstalledAppCounter.java @@ -31,21 +31,24 @@ public abstract class InstalledAppCounter extends AppCounter { public static final int IGNORE_INSTALL_REASON = -1; private final int mInstallReason; - private final PackageManagerWrapper mPackageManager; public InstalledAppCounter(Context context, int installReason, PackageManagerWrapper packageManager) { super(context, packageManager); mInstallReason = installReason; - mPackageManager = packageManager; } @Override protected boolean includeInCount(ApplicationInfo info) { + return includeInCount(mInstallReason, mPm, info); + } + + public static boolean includeInCount(int installReason, PackageManagerWrapper pm, + ApplicationInfo info) { final int userId = UserHandle.getUserId(info.uid); - if (mInstallReason != IGNORE_INSTALL_REASON - && mPackageManager.getInstallReason(info.packageName, - new UserHandle(userId)) != mInstallReason) { + if (installReason != IGNORE_INSTALL_REASON + && pm.getInstallReason(info.packageName, + new UserHandle(userId)) != installReason) { return false; } if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { @@ -57,7 +60,7 @@ public abstract class InstalledAppCounter extends AppCounter { Intent launchIntent = new Intent(Intent.ACTION_MAIN, null) .addCategory(Intent.CATEGORY_LAUNCHER) .setPackage(info.packageName); - List intents = mPm.queryIntentActivitiesAsUser( + List intents = pm.queryIntentActivitiesAsUser( launchIntent, PackageManager.GET_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE diff --git a/src/com/android/settings/applications/InstalledAppLister.java b/src/com/android/settings/applications/InstalledAppLister.java new file mode 100644 index 00000000000..1c3b0e9b28a --- /dev/null +++ b/src/com/android/settings/applications/InstalledAppLister.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 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.applications; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.UserManager; + +public abstract class InstalledAppLister extends AppLister { + + public InstalledAppLister(PackageManagerWrapper packageManager, + UserManager userManager) { + super(packageManager, userManager); + } + + @Override + protected boolean includeInCount(ApplicationInfo info) { + return InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, mPm, info); + } +} diff --git a/src/com/android/settings/applications/UserAppInfo.java b/src/com/android/settings/applications/UserAppInfo.java new file mode 100644 index 00000000000..3af5f19167f --- /dev/null +++ b/src/com/android/settings/applications/UserAppInfo.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017 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.applications; + +import android.content.pm.ApplicationInfo; +import android.content.pm.UserInfo; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Simple class for bringing together information about application and user for which it was + * installed. + */ +public class UserAppInfo { + public final UserInfo userInfo; + public final ApplicationInfo appInfo; + + public UserAppInfo(UserInfo mUserInfo, ApplicationInfo mAppInfo) { + this.userInfo = mUserInfo; + this.appInfo = mAppInfo; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + final UserAppInfo that = (UserAppInfo) other; + + return that.userInfo.id == userInfo.id && TextUtils.equals(that.appInfo.packageName, + appInfo.packageName); + } + + @Override + public int hashCode() { + return Objects.hash(userInfo.id, appInfo.packageName); + } +} diff --git a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java index 0db9e15319c..f0aca01a1a5 100644 --- a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java +++ b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java @@ -16,7 +16,6 @@ package com.android.settings.enterprise; import android.content.Context; -import android.content.Intent; import android.support.v7.preference.Preference; import com.android.settings.R; @@ -32,6 +31,7 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase private final String mPermissionGroup; private final ApplicationFeatureProvider mFeatureProvider; private final boolean mAsync; + private boolean mHasApps; public AdminGrantedPermissionsPreferenceControllerBase(Context context, Lifecycle lifecycle, boolean async, String[] permissions, String permissionGroup) { @@ -41,6 +41,7 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase mFeatureProvider = FeatureFactory.getFactory(context) .getApplicationFeatureProvider(context); mAsync = async; + mHasApps = false; } @Override @@ -50,11 +51,13 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase (num) -> { if (num == 0) { preference.setVisible(false); + mHasApps = false; } else { preference.setVisible(true); preference.setSummary(mContext.getResources().getQuantityString( R.plurals.enterprise_privacy_number_packages_lower_bound, num, num)); + mHasApps = true; } }); } @@ -76,7 +79,8 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase final Boolean[] haveAppsWithAdminGrantedPermissions = { null }; mFeatureProvider.calculateNumberOfAppsWithAdminGrantedPermissions(mPermissions, false /* async */, (num) -> haveAppsWithAdminGrantedPermissions[0] = num > 0); - return haveAppsWithAdminGrantedPermissions[0]; + mHasApps = haveAppsWithAdminGrantedPermissions[0]; + return mHasApps; } @Override @@ -84,9 +88,9 @@ public abstract class AdminGrantedPermissionsPreferenceControllerBase if (!getPreferenceKey().equals(preference.getKey())) { return false; } - final Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS) - .putExtra(Intent.EXTRA_PERMISSION_NAME, mPermissionGroup); - mContext.startActivity(intent); - return true; + if (!mHasApps) { + return false; + } + return super.handlePreferenceTreeClick(preference); } } diff --git a/src/com/android/settings/enterprise/ApplicationListFragment.java b/src/com/android/settings/enterprise/ApplicationListFragment.java new file mode 100644 index 00000000000..9a887d8d10b --- /dev/null +++ b/src/com/android/settings/enterprise/ApplicationListFragment.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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.enterprise; + +import android.Manifest; +import android.content.Context; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.applications.ApplicationFeatureProvider; +import com.android.settings.core.PreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.overlay.FeatureFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base fragment for displaying a list of applications on a device. + * Inner static classes are concrete implementations. + */ +public abstract class ApplicationListFragment extends DashboardFragment + implements ApplicationListPreferenceController.ApplicationListBuilder { + + static final String TAG = "EnterprisePrivacySettings"; + + @Override + public int getMetricsCategory() { + return MetricsEvent.ENTERPRISE_PRIVACY_SETTINGS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.app_list_disclosure_settings; + } + + @Override + protected List getPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + ApplicationListPreferenceController controller = new ApplicationListPreferenceController( + context, this, context.getPackageManager(), this); + controllers.add(controller); + return controllers; + } + + private abstract static class AdminGrantedPermission extends ApplicationListFragment { + private final String[] mPermissions; + + public AdminGrantedPermission(String[] permissions) { + mPermissions = permissions; + } + + @Override + public void buildApplicationList(Context context, + ApplicationFeatureProvider.ListOfAppsCallback callback) { + FeatureFactory.getFactory(context).getApplicationFeatureProvider(context) + .listAppsWithAdminGrantedPermissions(mPermissions, callback); + } + } + + public static class AdminGrantedPermissionCamera extends AdminGrantedPermission { + public AdminGrantedPermissionCamera() { + super(new String[] {Manifest.permission.CAMERA}); + } + } + + public static class AdminGrantedPermissionLocation extends AdminGrantedPermission { + public AdminGrantedPermissionLocation() { + super(new String[] {Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION}); + } + } + + public static class AdminGrantedPermissionMicrophone extends AdminGrantedPermission { + public AdminGrantedPermissionMicrophone() { + super(new String[] {Manifest.permission.RECORD_AUDIO}); + } + } + + public static class EnterpriseInstalledPackages extends ApplicationListFragment { + public EnterpriseInstalledPackages() { + } + + @Override + public void buildApplicationList(Context context, + ApplicationFeatureProvider.ListOfAppsCallback callback) { + FeatureFactory.getFactory(context).getApplicationFeatureProvider(context). + listPolicyInstalledApps(callback); + } + } +} diff --git a/src/com/android/settings/enterprise/ApplicationListPreferenceController.java b/src/com/android/settings/enterprise/ApplicationListPreferenceController.java new file mode 100644 index 00000000000..f9fefa1837b --- /dev/null +++ b/src/com/android/settings/enterprise/ApplicationListPreferenceController.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 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.enterprise; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.ApplicationFeatureProvider; +import com.android.settings.applications.UserAppInfo; +import com.android.settings.core.PreferenceController; + +import java.util.List; + +/** + * PreferenceController that builds a dynamic list of applications provided by + * {@link ApplicationListBuilder} instance. + */ +public class ApplicationListPreferenceController extends PreferenceController + implements ApplicationFeatureProvider.ListOfAppsCallback { + private final PackageManager mPm; + private SettingsPreferenceFragment mParent; + + public ApplicationListPreferenceController(Context context, ApplicationListBuilder builder, + PackageManager packageManager, SettingsPreferenceFragment parent) { + super(context); + mPm = packageManager; + mParent = parent; + builder.buildApplicationList(context, this); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return null; + } + + @Override + public void onListOfAppsResult(List result) { + final PreferenceScreen screen = mParent.getPreferenceScreen(); + if (screen == null) { + return; + } + final Context prefContext = mParent.getPreferenceManager().getContext(); + for (int position = 0; position < result.size(); position++) { + final UserAppInfo item = result.get(position); + final Preference preference = new Preference(prefContext); + preference.setLayoutResource(R.layout.preference_app); + preference.setTitle(item.appInfo.loadLabel(mPm)); + preference.setIcon(item.appInfo.loadIcon(mPm)); + preference.setOrder(position); + preference.setSelectable(false); + screen.addPreference(preference); + } + } + + /** + * Simple interface for building application list within { + * @link ApplicationListPreferenceController} + */ + public interface ApplicationListBuilder { + void buildApplicationList(Context context, + ApplicationFeatureProvider.ListOfAppsCallback callback); + } +} diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 240b8eb18fb..a83bf5001b2 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -7,3 +7,7 @@ com.android.settings.fuelgauge.PowerUsageDetail com.android.settings.fuelgauge.AdvancedPowerUsageDetail com.android.settings.deviceinfo.StorageProfileFragment com.android.settings.wifi.details.WifiNetworkDetailsFragment +com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionCamera +com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionLocation +com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionMicrophone +com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages diff --git a/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java index aeb38266b27..4a7dc8fa5e5 100644 --- a/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java +++ b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java @@ -18,15 +18,18 @@ package com.android.settings.applications; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Build; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.enterprise.DevicePolicyManagerWrapper; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -83,102 +86,31 @@ public final class AppWithAdminGrantedPermissionsCounterTest { @Mock private DevicePolicyManagerWrapper mDevicePolicyManager; private int mAppCount = -1; + private ApplicationInfo mApp1; + private ApplicationInfo mApp2; + private ApplicationInfo mApp3; + private ApplicationInfo mApp4; + private ApplicationInfo mApp5; + private ApplicationInfo mApp6; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + + mApp1 = buildInfo(APP_1_UID, APP_1, 0 /* flags */, Build.VERSION_CODES.M); + mApp2 = buildInfo(APP_2_UID, APP_2, 0 /* flags */, Build.VERSION_CODES.M); + mApp3 = buildInfo(APP_3_UID, APP_3, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP); + mApp4 = buildInfo(APP_4_UID, APP_4, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP); + mApp5 = buildInfo(APP_5_UID, APP_5, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP); + mApp6 = buildInfo(APP_6_UID, APP_6, 0 /* flags */, Build.VERSION_CODES.M); } private void verifyCountInstalledApps(boolean async) throws Exception { - // There are two users. - when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList( - new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN), - new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0))); - - // The first user has five apps installed: - // * app1 uses run-time permissions. It has been granted one of the permissions by the - // admin. It should be counted. - // * app2 uses run-time permissions. It has not been granted any of the permissions by the - // admin. It should not be counted. - // * app3 uses install-time permissions. It was installed by the admin and requested one of - // the permissions. It should be counted. - // * app4 uses install-time permissions. It was not installed by the admin but did request - // one of the permissions. It should not be counted. - // * app5 uses install-time permissions. It was installed by the admin but did not request - // any of the permissions. It should not be counted. - when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS - | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_ANY_USER, - MAIN_USER_ID)).thenReturn(Arrays.asList( - buildInfo(APP_1_UID, APP_1, 0 /* flags */, Build.VERSION_CODES.M), - buildInfo(APP_2_UID, APP_2, 0 /* flags */, Build.VERSION_CODES.M), - buildInfo(APP_3_UID, APP_3, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP), - buildInfo(APP_4_UID, APP_4, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP), - buildInfo(APP_5_UID, APP_5, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP))); - - // Grant run-time permissions as appropriate. - when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_1)) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); - when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_2)) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); - when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_2), anyObject())) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); - when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_3), anyObject())) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); - when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_4), anyObject())) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); - when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_5), anyObject())) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); - - // Grant install-time permissions as appropriate. - when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_1_UID))) - .thenReturn(PackageManager.PERMISSION_DENIED); - when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_2_UID))) - .thenReturn(PackageManager.PERMISSION_DENIED); - when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_3_UID)) - .thenReturn(PackageManager.PERMISSION_DENIED); - when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_3_UID)) - .thenReturn(PackageManager.PERMISSION_GRANTED); - when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_4_UID)) - .thenReturn(PackageManager.PERMISSION_DENIED); - when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_4_UID)) - .thenReturn(PackageManager.PERMISSION_GRANTED); - when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_5_UID))) - .thenReturn(PackageManager.PERMISSION_DENIED); - - // app3 and app5 were installed by enterprise policy. - final UserHandle mainUser = new UserHandle(MAIN_USER_ID); - when(mPackageManager.getInstallReason(APP_1, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); - when(mPackageManager.getInstallReason(APP_2, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); - when(mPackageManager.getInstallReason(APP_3, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_POLICY); - when(mPackageManager.getInstallReason(APP_4, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); - when(mPackageManager.getInstallReason(APP_5, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_POLICY); - - // The second user has one app installed. This app uses run-time permissions. It has been - // granted both permissions by the admin. It should be counted. - when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS - | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, - MANAGED_PROFILE_ID)).thenReturn(Arrays.asList( - buildInfo(APP_6_UID, APP_6, 0 /* flags */, Build.VERSION_CODES.M))); - - // Grant run-time permissions as appropriate. - when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_6), anyObject())) - .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); - - // Grant install-time permissions as appropriate. - when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_6_UID))) - .thenReturn(PackageManager.PERMISSION_DENIED); - - // app6 was not installed by enterprise policy. - final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID); - when(mPackageManager.getInstallReason(APP_6, managedProfileUser)) - .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + configureUserManager(); + configurePackageManager(); + configureRunTimePermissions(); + configureInstallTimePermissions(); // Count the number of all apps installed that were granted on or more permissions by the // admin. @@ -200,6 +132,28 @@ public final class AppWithAdminGrantedPermissionsCounterTest { verifyNoMoreInteractions(mPackageManager); } + @Test + public void testIncludeInCount() throws Exception { + configurePackageManager(); + configureRunTimePermissions(); + configureInstallTimePermissions(); + + assertThat(AppWithAdminGrantedPermissionsCounter.includeInCount(PERMISSIONS, + mDevicePolicyManager, mPackageManager, mPackageManagerService, mApp1)).isTrue(); + + assertThat(AppWithAdminGrantedPermissionsCounter.includeInCount(PERMISSIONS, + mDevicePolicyManager, mPackageManager, mPackageManagerService, mApp2)).isFalse(); + + assertThat(AppWithAdminGrantedPermissionsCounter.includeInCount(PERMISSIONS, + mDevicePolicyManager, mPackageManager, mPackageManagerService, mApp3)).isTrue(); + + assertThat(AppWithAdminGrantedPermissionsCounter.includeInCount(PERMISSIONS, + mDevicePolicyManager, mPackageManager, mPackageManagerService, mApp4)).isFalse(); + + assertThat(AppWithAdminGrantedPermissionsCounter.includeInCount(PERMISSIONS, + mDevicePolicyManager, mPackageManager, mPackageManagerService, mApp5)).isFalse(); + } + @Test public void testCountInstalledAppsSync() throws Exception { verifyCountInstalledApps(false /* async */); @@ -210,6 +164,90 @@ public final class AppWithAdminGrantedPermissionsCounterTest { verifyCountInstalledApps(true /* async */); } + private void configureInstallTimePermissions() throws RemoteException { + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_1_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_2_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_3_UID)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_3_UID)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_4_UID)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_4_UID)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_5_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_6_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + } + + private void configureRunTimePermissions() { + when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_1)) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_2)) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_2), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_3), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_4), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_5), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_6), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + } + + private void configurePackageManager() { + // The first user has five apps installed: + // * app1 uses run-time permissions. It has been granted one of the permissions by the + // admin. It should be counted. + // * app2 uses run-time permissions. It has not been granted any of the permissions by the + // admin. It should not be counted. + // * app3 uses install-time permissions. It was installed by the admin and requested one of + // the permissions. It should be counted. + // * app4 uses install-time permissions. It was not installed by the admin but did request + // one of the permissions. It should not be counted. + // * app5 uses install-time permissions. It was installed by the admin but did not request + // any of the permissions. It should not be counted. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_ANY_USER, + MAIN_USER_ID)).thenReturn(Arrays.asList(mApp1, mApp2, mApp3, mApp4, mApp5)); + // The second user has one app installed. This app uses run-time permissions. It has been + // granted both permissions by the admin. It should be counted. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, + MANAGED_PROFILE_ID)).thenReturn(Arrays.asList(mApp6)); + + // app3 and app5 were installed by enterprise policy. + final UserHandle mainUser = new UserHandle(MAIN_USER_ID); + when(mPackageManager.getInstallReason(APP_1, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_2, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_3, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_4, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_5, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + // app6 was not installed by enterprise policy. + final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID); + when(mPackageManager.getInstallReason(APP_6, managedProfileUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + + } + + private void configureUserManager() { + // There are two users. + when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList( + new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN), + new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0))); + } + private class AppWithAdminGrantedPermissionsCounterTestable extends AppWithAdminGrantedPermissionsCounter { public AppWithAdminGrantedPermissionsCounterTestable(String[] permissions) { diff --git a/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsListerTest.java b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsListerTest.java new file mode 100644 index 00000000000..73bba044c48 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsListerTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2017 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.applications; + +import android.app.admin.DevicePolicyManager; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.enterprise.DevicePolicyManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.android.settings.testutils.ApplicationTestUtils.buildInfo; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link AppWithAdminGrantedPermissionsLister}. + */ +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public final class AppWithAdminGrantedPermissionsListerTest { + + private final String APP_1 = "app1"; + private final String APP_2 = "app2"; + private final String APP_3 = "app3"; + private final String APP_4 = "app4"; + private final String APP_5 = "app5"; + private final String APP_6 = "app6"; + + private final int MAIN_USER_ID = 0; + private final int MANAGED_PROFILE_ID = 10; + + private final int PER_USER_UID_RANGE = 100000; + private final int APP_1_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 1; + private final int APP_2_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 2; + private final int APP_3_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 3; + private final int APP_4_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 4; + private final int APP_5_UID = MAIN_USER_ID * PER_USER_UID_RANGE + 5; + private final int APP_6_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE + 1; + + private final String PERMISSION_1 = "some.permission.1"; + private final String PERMISSION_2 = "some.permission.2"; + private final String[] PERMISSIONS = {PERMISSION_1, PERMISSION_2}; + + @Mock private UserManager mUserManager; + @Mock private PackageManagerWrapper mPackageManager; + @Mock private IPackageManagerWrapper mPackageManagerService; + @Mock private DevicePolicyManagerWrapper mDevicePolicyManager; + + private List mAppList = Collections.emptyList(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void verifyListInstalledApps() throws Exception { + // There are two users. + when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList( + new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN), + new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0))); + + // The first user has five apps installed: + // * app1 uses run-time permissions. It has been granted one of the permissions by the + // admin. It should be listed. + // * app2 uses run-time permissions. It has not been granted any of the permissions by the + // admin. It should not be listed. + // * app3 uses install-time permissions. It was installed by the admin and requested one of + // the permissions. It should be listed. + // * app4 uses install-time permissions. It was not installed by the admin but did request + // one of the permissions. It should not be listed. + // * app5 uses install-time permissions. It was installed by the admin but did not request + // any of the permissions. It should not be listed. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_ANY_USER, + MAIN_USER_ID)).thenReturn(Arrays.asList( + buildInfo(APP_1_UID, APP_1, 0 /* flags */, Build.VERSION_CODES.M), + buildInfo(APP_2_UID, APP_2, 0 /* flags */, Build.VERSION_CODES.M), + buildInfo(APP_3_UID, APP_3, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP), + buildInfo(APP_4_UID, APP_4, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP), + buildInfo(APP_5_UID, APP_5, 0 /* flags */, Build.VERSION_CODES.LOLLIPOP))); + + // Grant run-time permissions as appropriate. + when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_1)) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION_2)) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_2), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_3), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_4), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_5), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + + // Grant install-time permissions as appropriate. + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_1_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_2_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_3_UID)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_3_UID)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mPackageManagerService.checkUidPermission(PERMISSION_1, APP_4_UID)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION_2, APP_4_UID)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_5_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + // app3 and app5 were installed by enterprise policy. + final UserHandle mainUser = new UserHandle(MAIN_USER_ID); + when(mPackageManager.getInstallReason(APP_1, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_2, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_3, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_4, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_5, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + + // The second user has one app installed. This app uses run-time permissions. It has been + // granted both permissions by the admin. It should be listed. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, + MANAGED_PROFILE_ID)).thenReturn(Arrays.asList( + buildInfo(APP_6_UID, APP_6, 0 /* flags */, Build.VERSION_CODES.M))); + + // Grant run-time permissions as appropriate. + when(mDevicePolicyManager.getPermissionGrantState(eq(null), eq(APP_6), anyObject())) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + + // Grant install-time permissions as appropriate. + when(mPackageManagerService.checkUidPermission(anyObject(), eq(APP_6_UID))) + .thenReturn(PackageManager.PERMISSION_DENIED); + + // app6 was not installed by enterprise policy. + final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID); + when(mPackageManager.getInstallReason(APP_6, managedProfileUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + + // List all apps installed that were granted one or more permissions by the + // admin. + (new AppWithAdminGrantedPermissionsListerTestable(PERMISSIONS)).execute(); + + // Wait for the background task to finish. + ShadowApplication.runBackgroundTasks(); + assertThat(mAppList.size()).isEqualTo(3); + InstalledAppListerTest.verifyListUniqueness(mAppList); + + assertThat(InstalledAppListerTest.checkAppFound(mAppList, APP_1, MAIN_USER_ID)).isTrue(); + assertThat(InstalledAppListerTest.checkAppFound(mAppList, APP_2, MAIN_USER_ID)).isFalse(); + assertThat(InstalledAppListerTest.checkAppFound(mAppList, APP_3, MAIN_USER_ID)).isTrue(); + assertThat(InstalledAppListerTest.checkAppFound(mAppList, APP_4, MAIN_USER_ID)).isFalse(); + assertThat(InstalledAppListerTest.checkAppFound(mAppList, APP_5, MAIN_USER_ID)).isFalse(); + assertThat(InstalledAppListerTest.checkAppFound(mAppList, APP_6, MANAGED_PROFILE_ID)). + isTrue(); + + // Verify that installed packages were retrieved the current user and the user's managed + // profile only. + verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(), eq(MAIN_USER_ID)); + verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(), + eq(MANAGED_PROFILE_ID)); + verify(mPackageManager, atLeast(0)).getInstallReason(anyObject(), anyObject()); + verifyNoMoreInteractions(mPackageManager); + } + + private class AppWithAdminGrantedPermissionsListerTestable extends + AppWithAdminGrantedPermissionsLister { + + public AppWithAdminGrantedPermissionsListerTestable(String[] permissions) { + super(permissions, mPackageManager, mPackageManagerService, + mDevicePolicyManager, mUserManager); + } + + @Override + protected void onAppListBuilt(List list) { + mAppList = list; + } + } +} diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java index f46bb902043..cb68ccb4f13 100644 --- a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java @@ -33,6 +33,7 @@ import com.android.settings.TestConfig; import com.android.settings.enterprise.DevicePolicyManagerWrapper; import com.android.settings.testutils.ApplicationTestUtils; import com.android.settings.testutils.shadow.ShadowUserManager; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +43,7 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; import java.util.Arrays; +import java.util.List; import java.util.Set; import static com.google.common.truth.Truth.assertThat; @@ -76,6 +78,7 @@ public final class ApplicationFeatureProviderImplTest { private ApplicationFeatureProvider mProvider; private int mAppCount = -1; + private List mAppList = null; @Before public void setUp() { @@ -105,6 +108,22 @@ public final class ApplicationFeatureProviderImplTest { assertThat(mAppCount).isEqualTo(1); } + @Test + public void testListPolicyInstalledApps() { + setUpUsersAndInstalledApps(); + + when(mPackageManager.getInstallReason(APP_1, new UserHandle(MAIN_USER_ID))) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_2, new UserHandle(MANAGED_PROFILE_ID))) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + + mAppList = null; + mProvider.listPolicyInstalledApps((list) -> mAppList = list); + assertThat(mAppList).isNotNull(); + assertThat(mAppList.size()).isEqualTo(1); + assertThat(mAppList.get(0).appInfo.packageName).isEqualTo(APP_2); + } + @Test public void testCalculateNumberOfInstalledAppsSync() { verifyCalculateNumberOfPolicyInstalledApps(false /* async */); @@ -139,7 +158,6 @@ public final class ApplicationFeatureProviderImplTest { ShadowApplication.runBackgroundTasks(); } assertThat(mAppCount).isEqualTo(2); - } @Test @@ -152,6 +170,34 @@ public final class ApplicationFeatureProviderImplTest { verifyCalculateNumberOfAppsWithAdminGrantedPermissions(true /* async */); } + @Test + public void testListAppsWithAdminGrantedPermissions() + throws Exception { + setUpUsersAndInstalledApps(); + + when(mDevicePolicyManager.getPermissionGrantState(null, APP_1, PERMISSION)) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED); + when(mDevicePolicyManager.getPermissionGrantState(null, APP_2, PERMISSION)) + .thenReturn(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION, APP_1_UID)) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mPackageManagerService.checkUidPermission(PERMISSION, APP_2_UID)) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mPackageManager.getInstallReason(APP_1, new UserHandle(MAIN_USER_ID))) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_2, new UserHandle(MANAGED_PROFILE_ID))) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + + mAppList = null; + mProvider.listAppsWithAdminGrantedPermissions(new String[] {PERMISSION}, + (list) -> mAppList = list); + assertThat(mAppList).isNotNull(); + assertThat(mAppList.size()).isEqualTo(2); + assertThat(Arrays.asList(mAppList.get(0).appInfo.packageName, + mAppList.get(1).appInfo.packageName).containsAll(Arrays.asList(APP_1, APP_2))) + .isTrue(); + } + @Test public void testFindPersistentPreferredActivities() throws Exception { when(mUserManager.getUserProfiles()).thenReturn(Arrays.asList(new UserHandle(MAIN_USER_ID), diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java index 46a3359c530..37fa9d70bb1 100644 --- a/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java +++ b/tests/robotests/src/com/android/settings/applications/InstalledAppCounterTest.java @@ -22,12 +22,14 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.testutils.shadow.ShadowUserManager; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,16 +77,38 @@ public final class InstalledAppCounterTest { private final int MAIN_USER_APP_UID = MAIN_USER_ID * PER_USER_UID_RANGE; private final int MANAGED_PROFILE_APP_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE; - @Mock private UserManager mUserManager; - @Mock private Context mContext; - @Mock private PackageManagerWrapper mPackageManager; + @Mock + private UserManager mUserManager; + @Mock + private Context mContext; + @Mock + private PackageManagerWrapper mPackageManager; private int mInstalledAppCount = -1; + private ApplicationInfo mApp1; + private ApplicationInfo mApp2; + private ApplicationInfo mApp3; + private ApplicationInfo mApp4; + private ApplicationInfo mApp5; + private ApplicationInfo mApp6; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + + mApp1 = buildInfo(MAIN_USER_APP_UID, APP_1, + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0 /* targetSdkVersion */); + mApp2 = buildInfo(MAIN_USER_APP_UID, APP_2, 0 /* flags */, + 0 /* targetSdkVersion */); + mApp3 = buildInfo(MAIN_USER_APP_UID, APP_3, ApplicationInfo.FLAG_SYSTEM, + 0 /* targetSdkVersion */); + mApp4 = buildInfo(MAIN_USER_APP_UID, APP_4, ApplicationInfo.FLAG_SYSTEM, + 0 /* targetSdkVersion */); + mApp5 = buildInfo(MANAGED_PROFILE_APP_UID, APP_5, 0 /* flags */, + 0 /* targetSdkVersion */); + mApp6 = buildInfo(MANAGED_PROFILE_APP_UID, APP_6, ApplicationInfo.FLAG_SYSTEM, + 0 /* targetSdkVersion */); } private void expectQueryIntentActivities(int userId, String packageName, boolean launchable) { @@ -93,7 +117,7 @@ public final class InstalledAppCounterTest { eq(PackageManager.GET_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), eq(userId))).thenReturn(launchable ? Arrays.asList(new ResolveInfo()) - : new ArrayList()); + : new ArrayList()); } private void testCountInstalledAppsAcrossAllUsers(boolean async) { @@ -101,58 +125,7 @@ public final class InstalledAppCounterTest { when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList( new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN), new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0))); - - // The first user has four apps installed: - // * app1 is an updated system app. It should be counted. - // * app2 is a user-installed app. It should be counted. - // * app3 is a system app that provides a launcher icon. It should be counted. - // * app4 is a system app that provides no launcher icon. It should not be counted. - when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS - | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_ANY_USER, - MAIN_USER_ID)).thenReturn(Arrays.asList( - buildInfo(MAIN_USER_APP_UID, APP_1, - ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0 /* targetSdkVersion */), - buildInfo(MAIN_USER_APP_UID, APP_2, 0 /* flags */, - 0 /* targetSdkVersion */), - buildInfo(MAIN_USER_APP_UID, APP_3, ApplicationInfo.FLAG_SYSTEM, - 0 /* targetSdkVersion */), - buildInfo(MAIN_USER_APP_UID, APP_4, ApplicationInfo.FLAG_SYSTEM, - 0 /* targetSdkVersion */))); - // For system apps, InstalledAppCounter checks whether they handle the default launcher - // intent to decide whether to include them in the count of installed apps or not. - expectQueryIntentActivities(MAIN_USER_ID, APP_3, true /* launchable */); - expectQueryIntentActivities(MAIN_USER_ID, APP_4, false /* launchable */); - - // app1, app3 and app4 are installed by enterprise policy. - final UserHandle mainUser = new UserHandle(MAIN_USER_ID); - when(mPackageManager.getInstallReason(APP_1, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_POLICY); - when(mPackageManager.getInstallReason(APP_2, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); - when(mPackageManager.getInstallReason(APP_3, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_POLICY); - when(mPackageManager.getInstallReason(APP_4, mainUser)) - .thenReturn(PackageManager.INSTALL_REASON_POLICY); - - // The second user has two apps installed: - // * app5 is a user-installed app. It should be counted. - // * app6 is a system app that provides a launcher icon. It should be counted. - when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS - | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, - MANAGED_PROFILE_ID)).thenReturn(Arrays.asList( - buildInfo(MANAGED_PROFILE_APP_UID, APP_5, 0 /* flags */, - 0 /* targetSdkVersion */), - buildInfo(MANAGED_PROFILE_APP_UID, APP_6, ApplicationInfo.FLAG_SYSTEM, - 0 /* targetSdkVersion */))); - expectQueryIntentActivities(MANAGED_PROFILE_ID, APP_6, true /* launchable */); - - // app5 is installed by enterprise policy. - final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID); - when(mPackageManager.getInstallReason(APP_5, managedProfileUser)) - .thenReturn(PackageManager.INSTALL_REASON_POLICY); - when(mPackageManager.getInstallReason(APP_6, managedProfileUser)) - .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + configurePackageManager(); // Count the number of all apps installed, irrespective of install reason. count(InstalledAppCounter.IGNORE_INSTALL_REASON, async); @@ -172,6 +145,36 @@ public final class InstalledAppCounterTest { assertThat(mInstalledAppCount).isEqualTo(3); } + @Test + public void testIncludeInCount() { + configurePackageManager(); + assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON, + mPackageManager, mApp1)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON, + mPackageManager, mApp2)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON, + mPackageManager, mApp3)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON, + mPackageManager, mApp4)).isFalse(); + assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON, + mPackageManager, mApp5)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(InstalledAppCounter.IGNORE_INSTALL_REASON, + mPackageManager, mApp6)).isTrue(); + + assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, + mPackageManager, mApp1)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, + mPackageManager, mApp2)).isFalse(); + assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, + mPackageManager, mApp3)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, + mPackageManager, mApp4)).isFalse(); + assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, + mPackageManager, mApp5)).isTrue(); + assertThat(InstalledAppCounter.includeInCount(PackageManager.INSTALL_REASON_POLICY, + mPackageManager, mApp6)).isFalse(); + } + @Test public void testCountInstalledAppsAcrossAllUsersSync() { testCountInstalledAppsAcrossAllUsers(false /* async */); @@ -194,6 +197,48 @@ public final class InstalledAppCounterTest { } } + private void configurePackageManager() { + // The first user has four apps installed: + // * app1 is an updated system app. It should be counted. + // * app2 is a user-installed app. It should be counted. + // * app3 is a system app that provides a launcher icon. It should be counted. + // * app4 is a system app that provides no launcher icon. It should not be counted. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_ANY_USER, + MAIN_USER_ID)).thenReturn(Arrays.asList(mApp1, mApp2, mApp3, mApp4)); + // For system apps, InstalledAppCounter checks whether they handle the default launcher + // intent to decide whether to include them in the count of installed apps or not. + expectQueryIntentActivities(MAIN_USER_ID, APP_3, true /* launchable */); + expectQueryIntentActivities(MAIN_USER_ID, APP_4, false /* launchable */); + + // app1, app3 and app4 are installed by enterprise policy. + final UserHandle mainUser = new UserHandle(MAIN_USER_ID); + when(mPackageManager.getInstallReason(APP_1, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_2, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_3, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_4, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + + // The second user has two apps installed: + // * app5 is a user-installed app. It should be counted. + // * app6 is a system app that provides a launcher icon. It should be counted. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,MANAGED_PROFILE_ID)) + .thenReturn(Arrays.asList(mApp5, mApp6)); + expectQueryIntentActivities(MANAGED_PROFILE_ID, APP_6, true /* launchable */); + + // app5 is installed by enterprise policy. + final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID); + when(mPackageManager.getInstallReason(APP_5, managedProfileUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_6, managedProfileUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + } + private class InstalledAppCounterTestable extends InstalledAppCounter { public InstalledAppCounterTestable(int installReason) { diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppListerTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppListerTest.java new file mode 100644 index 00000000000..76421c25f88 --- /dev/null +++ b/tests/robotests/src/com/android/settings/applications/InstalledAppListerTest.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2017 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.applications; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.android.settings.testutils.ApplicationTestUtils.buildInfo; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link InstalledAppLister}. + */ +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public final class InstalledAppListerTest { + + private final String APP_1 = "app1"; + private final String APP_2 = "app2"; + private final String APP_3 = "app3"; + private final String APP_4 = "app4"; + private final String APP_5 = "app5"; + private final String APP_6 = "app6"; + + private final int MAIN_USER_ID = 0; + private final int MANAGED_PROFILE_ID = 10; + + private final int PER_USER_UID_RANGE = 100000; + private final int MAIN_USER_APP_UID = MAIN_USER_ID * PER_USER_UID_RANGE; + private final int MANAGED_PROFILE_APP_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE; + + @Mock private UserManager mUserManager; + @Mock private PackageManagerWrapper mPackageManager; + + private List mInstalledAppList = Collections.emptyList(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private void expectQueryIntentActivities(int userId, String packageName, boolean launchable) { + when(mPackageManager.queryIntentActivitiesAsUser( + argThat(new IsLaunchIntentFor(packageName)), + eq(PackageManager.GET_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), + eq(userId))).thenReturn(launchable ? Arrays.asList(new ResolveInfo()) + : new ArrayList()); + } + + @Test + public void testCountInstalledAppsAcrossAllUsers() { + // There are two users. + when(mUserManager.getProfiles(UserHandle.myUserId())).thenReturn(Arrays.asList( + new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN), + new UserInfo(MANAGED_PROFILE_ID, "managed profile", 0))); + + // The first user has four apps installed: + // * app1 is an updated system app. It should be listed. + // * app2 is a user-installed app. It should be listed. + // * app3 is a system app that provides a launcher icon. It should be listed. + // * app4 is a system app that provides no launcher icon. It should not be listed. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS + | PackageManager.MATCH_ANY_USER, + MAIN_USER_ID)).thenReturn(Arrays.asList( + buildInfo(MAIN_USER_APP_UID, APP_1, + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, 0 /* targetSdkVersion */), + buildInfo(MAIN_USER_APP_UID, APP_2, 0 /* flags */, + 0 /* targetSdkVersion */), + buildInfo(MAIN_USER_APP_UID, APP_3, ApplicationInfo.FLAG_SYSTEM, + 0 /* targetSdkVersion */), + buildInfo(MAIN_USER_APP_UID, APP_4, ApplicationInfo.FLAG_SYSTEM, + 0 /* targetSdkVersion */))); + // For system apps, InstalledAppLister checks whether they handle the default launcher + // intent to decide whether to include them in the list of installed apps or not. + expectQueryIntentActivities(MAIN_USER_ID, APP_3, true /* launchable */); + expectQueryIntentActivities(MAIN_USER_ID, APP_4, false /* launchable */); + + // app1, app3 and app4 are installed by enterprise policy. + final UserHandle mainUser = new UserHandle(MAIN_USER_ID); + when(mPackageManager.getInstallReason(APP_1, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_2, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + when(mPackageManager.getInstallReason(APP_3, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_4, mainUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + + // The second user has two apps installed: + // * app5 is a user-installed app. It should be listed. + // * app6 is a system app that provides a launcher icon. It should be listed. + when(mPackageManager.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, + MANAGED_PROFILE_ID)).thenReturn(Arrays.asList( + buildInfo(MANAGED_PROFILE_APP_UID, APP_5, 0 /* flags */, + 0 /* targetSdkVersion */), + buildInfo(MANAGED_PROFILE_APP_UID, APP_6, ApplicationInfo.FLAG_SYSTEM, + 0 /* targetSdkVersion */))); + expectQueryIntentActivities(MANAGED_PROFILE_ID, APP_6, true /* launchable */); + + // app5 is installed by enterprise policy. + final UserHandle managedProfileUser = new UserHandle(MANAGED_PROFILE_ID); + when(mPackageManager.getInstallReason(APP_5, managedProfileUser)) + .thenReturn(PackageManager.INSTALL_REASON_POLICY); + when(mPackageManager.getInstallReason(APP_6, managedProfileUser)) + .thenReturn(PackageManager.INSTALL_REASON_UNKNOWN); + + // List apps, considering apps installed by enterprise policy only. + mInstalledAppList = Collections.emptyList(); + final InstalledAppListerTestable counter = new InstalledAppListerTestable(); + counter.execute(); + // Wait for the background task to finish. + ShadowApplication.runBackgroundTasks(); + + assertThat(mInstalledAppList.size()).isEqualTo(3); + + assertThat(checkAppFound(mInstalledAppList, APP_1, MAIN_USER_ID)).isTrue(); + assertThat(checkAppFound(mInstalledAppList, APP_2, MAIN_USER_ID)).isFalse(); + assertThat(checkAppFound(mInstalledAppList, APP_3, MAIN_USER_ID)).isTrue(); + assertThat(checkAppFound(mInstalledAppList, APP_4, MAIN_USER_ID)).isFalse(); + assertThat(checkAppFound(mInstalledAppList, APP_5, MANAGED_PROFILE_ID)).isTrue(); + assertThat(checkAppFound(mInstalledAppList, APP_6, MANAGED_PROFILE_ID)).isFalse(); + + // Verify that installed packages were retrieved for the current user and the user's + // managed profile. + verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(), eq(MAIN_USER_ID)); + verify(mPackageManager).getInstalledApplicationsAsUser(anyInt(), + eq(MANAGED_PROFILE_ID)); + verify(mPackageManager, atLeast(0)).queryIntentActivitiesAsUser(anyObject(), anyInt(), + anyInt()); + } + + public static boolean checkAppFound(List mInstalledAppList, String appId, + int userId) { + for (UserAppInfo info : mInstalledAppList) { + if (appId.equals(info.appInfo.packageName) && (info.userInfo.id == userId)) { + return true; + } + } + return false; + } + + public static void verifyListUniqueness(List list) { + assertThat((new HashSet<>(list)).size()).isEqualTo(list.size()); + } + + private class InstalledAppListerTestable extends InstalledAppLister { + public InstalledAppListerTestable() { + super(mPackageManager, mUserManager); + } + + @Override + protected void onAppListBuilt(List list) { + mInstalledAppList = list; + } + } + + private static class IsLaunchIntentFor extends ArgumentMatcher { + private final String mPackageName; + + IsLaunchIntentFor(String packageName) { + mPackageName = packageName; + } + + @Override + public boolean matches(Object i) { + final Intent intent = (Intent) i; + if (intent == null) { + return false; + } + if (!Intent.ACTION_MAIN.equals(intent.getAction())) { + return false; + } + final Set categories = intent.getCategories(); + if (categories == null || categories.size() != 1 || + !categories.contains(Intent.CATEGORY_LAUNCHER)) { + return false; + } + if (!mPackageName.equals(intent.getPackage())) { + return false; + } + return true; + } + } +} diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java index f001bd1e73f..96ce081700d 100644 --- a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java +++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java @@ -17,8 +17,6 @@ package com.android.settings.enterprise; import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; import android.support.v7.preference.Preference; import com.android.settings.R; @@ -28,24 +26,21 @@ import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; import org.junit.Test; import org.mockito.Answers; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.anyObject; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Common base for testing subclasses of {@link AdminGrantedPermissionsPreferenceControllerBase}. */ public abstract class AdminGrantedPermissionsPreferenceControllerTestBase { - protected final String mKey; protected final String[] mPermissions; protected final String mPermissionGroup; @@ -123,19 +118,8 @@ public abstract class AdminGrantedPermissionsPreferenceControllerTestBase { @Test public void testHandlePreferenceTreeClick() { - final Preference preference = new Preference(mContext, null, 0, 0); - preference.setKey(mKey); - - assertThat(mController.handlePreferenceTreeClick(preference)).isTrue(); - - final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mContext).startActivity(argumentCaptor.capture()); - - final Intent intent = argumentCaptor.getValue(); - - assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_APPS); - assertThat(intent.getStringExtra(Intent.EXTRA_PERMISSION_NAME)). - isEqualTo(mPermissionGroup); + assertThat(mController.handlePreferenceTreeClick(new Preference(mContext, null, 0, 0))) + .isFalse(); } @Test diff --git a/tests/robotests/src/com/android/settings/enterprise/ApplicationListFragmentTest.java b/tests/robotests/src/com/android/settings/enterprise/ApplicationListFragmentTest.java new file mode 100644 index 00000000000..5fbaf51216e --- /dev/null +++ b/tests/robotests/src/com/android/settings/enterprise/ApplicationListFragmentTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2017 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.enterprise; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.applications.ApplicationFeatureProvider; +import com.android.settings.applications.UserAppInfo; +import com.android.settings.core.PreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.List; + +import static com.android.settings.testutils.ApplicationTestUtils.buildInfo; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class ApplicationListFragmentTest { + private static final int USER_ID = 0; + private static final int USER_APP_UID = 0; + + private static final String APP = "APP"; + + @Mock(answer = RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + @Mock(answer = RETURNS_DEEP_STUBS) + private PreferenceManager mPreferenceManager; + + private ApplicationListFragment mFragment; + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ShadowApplication.getInstance().getApplicationContext(); + when(mPreferenceManager.getContext()).thenReturn(mContext); + + mFragment = new ApplicationListFragmentTestable(mPreferenceManager, mScreen); + } + + @Test + public void getMetricsCategory() { + assertThat(mFragment.getMetricsCategory()) + .isEqualTo(MetricsEvent.ENTERPRISE_PRIVACY_SETTINGS); + } + + @Test + public void getLogTag() { + assertThat(mFragment.getLogTag()) + .isEqualTo("EnterprisePrivacySettings"); + } + + @Test + public void getScreenResource() { + assertThat(mFragment.getPreferenceScreenResId()) + .isEqualTo(R.xml.app_list_disclosure_settings); + } + + @Test + public void getPreferenceControllers() { + final List controllers = mFragment.getPreferenceControllers(mContext); + assertThat(controllers).isNotNull(); + assertThat(controllers.size()).isEqualTo(1); + int position = 0; + assertThat(controllers.get(position++)).isInstanceOf( + ApplicationListPreferenceController.class); + } + + private static class ApplicationListFragmentTestable extends ApplicationListFragment { + + private final PreferenceManager mPreferenceManager; + private final PreferenceScreen mPreferenceScreen; + + public ApplicationListFragmentTestable(PreferenceManager preferenceManager, + PreferenceScreen screen) { + this.mPreferenceManager = preferenceManager; + this.mPreferenceScreen = screen; + } + + @Override + public void buildApplicationList(Context context, + ApplicationFeatureProvider.ListOfAppsCallback callback) { + final List apps = new ArrayList<>(); + final UserInfo user = new UserInfo(USER_ID, "main", UserInfo.FLAG_ADMIN); + apps.add(new UserAppInfo(user, buildInfo(USER_APP_UID, APP, 0, 0))); + callback.onListOfAppsResult(apps); + } + + @Override + public PreferenceManager getPreferenceManager() { + return mPreferenceManager; + } + + @Override + public PreferenceScreen getPreferenceScreen() { + return mPreferenceScreen; + } + } +} diff --git a/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java new file mode 100644 index 00000000000..56a6c62f605 --- /dev/null +++ b/tests/robotests/src/com/android/settings/enterprise/ApplicationListPreferenceControllerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017 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.enterprise; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.applications.ApplicationFeatureProvider; +import com.android.settings.applications.UserAppInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.android.settings.testutils.ApplicationTestUtils.buildInfo; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class ApplicationListPreferenceControllerTest { + + private static final int MAIN_USER_ID = 0; + + private static final int MANAGED_PROFILE_ID = 10; + private static final int PER_USER_UID_RANGE = 100000; + private static final int MAIN_USER_APP_UID = MAIN_USER_ID * PER_USER_UID_RANGE; + private static final int MANAGED_PROFILE_APP_UID = MANAGED_PROFILE_ID * PER_USER_UID_RANGE; + + private static final String APP_1 = "APP_1"; + private static final String APP_2 = "APP_2"; + private static final String APP_3 = "APP_3"; + + @Mock(answer = RETURNS_DEEP_STUBS) + private PreferenceScreen mScreen; + @Mock(answer = RETURNS_DEEP_STUBS) + private PackageManager mPackageManager; + @Mock(answer = RETURNS_DEEP_STUBS) + private SettingsPreferenceFragment mFragment; + + private Context mContext; + private ApplicationListPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final ShadowApplication shadowContext = ShadowApplication.getInstance(); + mContext = shadowContext.getApplicationContext(); + when(mFragment.getPreferenceScreen()).thenReturn(mScreen); + when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext); + when(mPackageManager.getText(eq(APP_1), anyInt(), any())).thenReturn(APP_1); + when(mPackageManager.getText(eq(APP_2), anyInt(), any())).thenReturn(APP_2); + when(mPackageManager.getText(eq(APP_3), anyInt(), any())).thenReturn(APP_3); + + mController = new ApplicationListPreferenceController(mContext, new ThreeAppsBuilder(), + mPackageManager, mFragment); + } + + @Test + public void checkNumberAndTitlesOfApps() { + ArgumentCaptor apps = ArgumentCaptor.forClass(Preference.class); + verify(mScreen, times(3)).addPreference(apps.capture()); + final Set expectedPackages = new HashSet<>(Arrays.asList(APP_1, APP_2, APP_3)); + final Set packages = new HashSet<>(); + + for (Preference p : apps.getAllValues()) { + packages.add(p.getTitle().toString()); + } + assertThat(packages).isEqualTo(expectedPackages); + } + + @Test + public void isAvailable() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void getPreferenceKey() { + assertThat(mController.getPreferenceKey()).isNull(); + } + + private static class ThreeAppsBuilder + implements ApplicationListPreferenceController.ApplicationListBuilder { + @Override + public void buildApplicationList(Context context, + ApplicationFeatureProvider.ListOfAppsCallback callback) { + final List apps = new ArrayList<>(); + final UserInfo user = new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN); + apps.add(new UserAppInfo(user, buildInfo(MAIN_USER_APP_UID, APP_1, 0, 0))); + apps.add(new UserAppInfo(user, buildInfo(MAIN_USER_APP_UID, APP_2, 0, 0))); + apps.add(new UserAppInfo(user, buildInfo(MANAGED_PROFILE_APP_UID, APP_3, 0, 0))); + callback.onListOfAppsResult(apps); + } + } +}