UX changes for the new cross profile settings page

Bug: 136249261
Bug: 140728653
Bug: 148594054
Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesDetailsTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesPreferenceControllerTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=InteractAcrossProfilesSettingsTest

Change-Id: I9666b34a03e5c082eed7c4120e70a07017ab7ef0
This commit is contained in:
kholoud mohamed
2020-02-05 11:14:11 +00:00
parent d4e2f78d9a
commit 00c46041b6
11 changed files with 407 additions and 78 deletions

View File

@@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2020 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.
-->
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#757575" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-5h2v5zM13,17h-2v-3h2v3zM13,12h-2v-2h2v2zM17,17h-2L15,7h2v10z"/>
</vector>

View File

@@ -0,0 +1,21 @@
<!--
~ Copyright (C) 2020 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.
-->
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#757575" android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
</vector>

View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/interact_across_profiles_dialog_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:orientation="horizontal">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/interact_across_profiles_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="start"
android:orientation="vertical">
<TextView
android:id="@+id/interact_across_profiles_consent_dialog_title"
android:text="@string/interact_across_profiles_consent_dialog_title"
style="@style/CrossProfileConsentDialogTitle"/>
<TextView
android:id="@+id/interact_across_profiles_consent_dialog_summary"
android:text="@string/interact_across_profiles_consent_dialog_summary"
style="@style/CrossProfileConsentDialogDescription"/>
<LinearLayout
android:id="@+id/app_data_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16pt"
android:gravity="start"
android:orientation="horizontal">
<ImageView
android:id="@+id/app_data_icon"
android:src="@drawable/ic_analytics_grey"
style="@style/CrossProfileConsentDialogIcon"/>
<LinearLayout
android:id="@+id/app_data_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:gravity="start"
android:orientation="vertical">
<TextView
android:id="@+id/app_data_title"
android:text="@string/interact_across_profiles_consent_dialog_app_data_title"
style="@style/CrossProfileConsentDialogSubTitle"/>
<TextView
android:id="@+id/app_data_summary"
android:text="@string/interact_across_profiles_consent_dialog_app_data_summary"
style="@style/CrossProfileConsentDialogSubDescription"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/permissions_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:orientation="horizontal">
<ImageView
android:id="@+id/permissions_icon"
android:src="@drawable/ic_storage_grey"
style="@style/CrossProfileConsentDialogIcon"/>
<LinearLayout
android:id="@+id/permissions_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/permissions_title"
android:text="@string/interact_across_profiles_consent_dialog_permissions_title"
style="@style/CrossProfileConsentDialogSubTitle"/>
<TextView
android:id="@+id/permissions_summary"
android:text="@string/interact_across_profiles_consent_dialog_permissions_summary"
style="@style/CrossProfileConsentDialogSubDescription"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>

View File

@@ -8422,33 +8422,55 @@
<!-- Special access > Title for managing the settings where users opt-in to connect a work app
to its personal equivalent, allowing cross-profile communication. [CHAR LIMIT=50] -->
<string name="interact_across_profiles_title" translatable="false">Connected work and personal apps</string>
<string name="interact_across_profiles_title" translatable="false">Connected personal and work apps</string>
<!-- Special access > Connected work and personal apps > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
<!-- Special access > Connected personal and work apps > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_empty_text" translatable="false">No connected apps</string>
<!-- Special access > Connected work and personal apps > Additional keywords to search for. [CHAR LIMIT=NONE] -->
<!-- Special access > Connected personal and work apps > Additional keywords to search for. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_keywords" translatable="false">cross profile connected app apps work and personal</string>
<!-- Apps > App Details > Advanced section string title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_app_detail_title" translatable="false">Connected work and personal apps</string>
<string name="interact_across_profiles_app_detail_title" translatable="false">Connected personal and work apps</string>
<!-- Apps > App Details > Connected work and personal apps > Switch title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_app_detail_switch" translatable="false">Connect these apps</string>
<!-- Apps > App Details > Connected personal and work apps > Switch title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_switch_enabled" translatable="false">Connected</string>
<!-- Apps > App Details > Connected work and personal apps > Description. [CHAR LIMIT=NONE] -->
<!-- Apps > App Details > Connected personal and work apps > Switch title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_switch_disabled" translatable="false">Connect these apps</string>
<!-- Apps > App Details > Connected personal and work apps > Description. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_summary_1" translatable="false">Connected apps share permissions and can access each other\u2019s data.</string>
<!-- Apps > App Details > Connected work and personal apps > Description. [CHAR LIMIT=NONE] -->
<!-- Apps > App Details > Connected personal and work apps > Description. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_summary_2" translatable="false">Only connect apps that you trust with your personal data.Your data may be exposed to your IT admin.</string>
<!-- TODO(b/148594054): Replace calendar with actual app name -->
<!-- Apps > App Details > Connected work and personal apps > Consent dialog title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_title" translatable="false">Trust work Calendar with your personal data?</string>
<!-- Apps > App Details > Connected personal and work apps > Consent dialog title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_title" translatable="false">Trust work %1$s with your personal data?</string>
<!-- TODO(b/148594054): Replace calendar with actual app name -->
<!-- Apps > App Details > Connected work and personal apps > Consent dialog description. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_summary" translatable="false">Calendar may expose your personal data to your IT admin</string>
<!-- Apps > App Details > Connected personal and work apps > Consent dialog description. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_summary" translatable="false">%1$s may expose your personal data to your IT admin.</string>
<!-- Apps > App Details > Connected personal and work apps > Consent dialog App data title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_app_data_title" translatable="false">App data</string>
<!-- Apps > App Details > Connected personal and work apps > Consent dialog App data description. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_app_data_summary" translatable="false">It can access data in your personal %1$s app.</string>
<!-- Apps > App Details > Connected personal and work apps > Consent dialog Permissions title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_permissions_title" translatable="false">Permissions</string>
<!-- Apps > App Details > Connected personal and work apps > Consent dialog Permissions description. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_consent_dialog_permissions_summary" translatable="false">It can use your personal %1$s app\u2019s permissions, like access to location, storage, or contacts.</string>
<!-- Summary of preference to manage connected personal and work apps, informing the user that currently no apps are connected -->
<string name="interact_across_profiles_number_of_connected_apps_none" translatable="false">No apps connected</string>
<!-- Summary of preference to manage connected personal and work apps, informing the user how many apps are connected -->
<plurals name="interact_across_profiles_number_of_connected_apps" translatable="false">
<item quantity="one"><xliff:g id="count">%d</xliff:g> app connected</item>
<item quantity="other"><xliff:g id="count">%d</xliff:g> apps connected</item>
</plurals>
<!-- Sound & notification > Advanced section: Title for managing Do Not Disturb access option. [CHAR LIMIT=40] -->
<string name="manage_zen_access_title">Do Not Disturb access</string>

View File

@@ -637,4 +637,49 @@
<item name="android:clipChildren">true</item>
</style>
<style name="CrossProfileConsentDialogTitle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item>
<item name="android:textSize">20sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:fontFamily">google-sans-medium</item>
<item name="android:paddingTop">36dp</item>
<item name="android:paddingBottom">16dp</item>
</style>
<style name="CrossProfileConsentDialogDescription">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">36dp</item>
<item name="android:textSize">16sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:gravity">start</item>
</style>
<style name="CrossProfileConsentDialogIcon">
<item name="android:layout_width">24dp</item>
<item name="android:layout_height">24dp</item>
<item name="android:antialias">true</item>
<item name="android:gravity">start</item>
</style>
<style name="CrossProfileConsentDialogSubTitle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">16sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:gravity">start</item>
</style>
<style name="CrossProfileConsentDialogSubDescription">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:gravity">start</item>
</style>
</resources>

View File

@@ -24,15 +24,13 @@
android:selectable="false"/>
<SwitchPreference
android:key="interact_across_profiles_settings_switch"
android:title="@string/interact_across_profiles_app_detail_switch"/>
android:key="interact_across_profiles_settings_switch" />
<Preference
android:summary="@string/interact_across_profiles_summary_1"
android:selectable="false"/>
android:selectable="false" />
<Preference
android:summary="@string/interact_across_profiles_summary_2"
android:selectable="false"/>
android:selectable="false" />
</PreferenceScreen>

View File

@@ -39,6 +39,13 @@
android:value="com.android.settings.Settings$HighPowerApplicationsActivity" />
</Preference>
<Preference
android:key="interact_across_profiles"
android:title="@string/interact_across_profiles_title"
android:fragment="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesSettings"
settings:keywords="@string/interact_across_profiles_keywords"
settings:controller="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesController" />
<Preference
android:key="device_administrators"
android:title="@string/manage_device_admin"
@@ -146,13 +153,6 @@
android:value="com.android.settings.Settings$ChangeWifiStateActivity" />
</Preference>
<Preference
android:key="interact_across_profiles"
android:title="@string/interact_across_profiles_title"
android:fragment="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesSettings"
settings:keywords="@string/interact_across_profiles_keywords"
settings:controller="com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesController"/>
<Preference
android:key="special_access_more"
android:title="@string/special_access_more"

View File

@@ -17,10 +17,13 @@
package com.android.settings.applications.specialaccess.interactacrossprofiles;
import android.content.Context;
import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import java.util.List;
@@ -33,12 +36,16 @@ public class InteractAcrossProfilesController extends BasePreferenceController {
private final Context mContext;
private final UserManager mUserManager;
private final PackageManager mPackageManager;
private final CrossProfileApps mCrossProfileApps;
public InteractAcrossProfilesController(Context context, String preferenceKey) {
super(context, preferenceKey);
mContext = context;
mUserManager = mContext.getSystemService(UserManager.class);
mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
mPackageManager = mContext.getPackageManager();
}
@Override
@@ -51,4 +58,17 @@ public class InteractAcrossProfilesController extends BasePreferenceController {
}
return DISABLED_FOR_USER;
}
@Override
public CharSequence getSummary() {
final int connectedApps = InteractAcrossProfilesSettings.getNumberOfEnabledApps(
mContext, mPackageManager, mUserManager, mCrossProfileApps);
return connectedApps == 0
? mContext.getResources().getString(
R.string.interact_across_profiles_number_of_connected_apps_none)
: mContext.getResources().getQuantityString(
R.plurals.interact_across_profiles_number_of_connected_apps,
connectedApps,
connectedApps);
}
}

View File

@@ -22,13 +22,15 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.PermissionChecker;
import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.IconDrawableFactory;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
@@ -51,6 +53,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
private UserManager mUserManager;
private SwitchPreference mSwitchPref;
private LayoutPreference mHeader;
private PackageManager mPackageManager;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -59,6 +62,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
mContext = getContext();
mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
mUserManager = mContext.getSystemService(UserManager.class);
mPackageManager = mContext.getPackageManager();
addPreferencesFromResource(R.xml.interact_across_profiles_permissions_details);
mSwitchPref = findPreference(INTERACT_ACROSS_PROFILES_SETTINGS_SWITCH);
@@ -72,10 +76,17 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
}
final UserHandle workProfile = getWorkProfile();
final UserHandle personalProfile = mUserManager.getProfileParent(workProfile);
addAppIcons(personalProfile, workProfile);
addAppTitleAndIcons(personalProfile, workProfile);
}
private void addAppIcons(UserHandle personalProfile, UserHandle workProfile) {
private void addAppTitleAndIcons(UserHandle personalProfile, UserHandle workProfile) {
final TextView title = mHeader.findViewById(R.id.entity_header_title);
if (title != null) {
final String appLabel = mPackageInfo.applicationInfo.loadLabel(
mPackageManager).toString();
title.setText(appLabel);
}
final ImageView personalIconView = mHeader.findViewById(R.id.entity_header_icon_personal);
if (personalIconView != null) {
personalIconView.setImageDrawable(IconDrawableFactory.newInstance(mContext)
@@ -114,35 +125,57 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
return true;
}
if (!isInteractAcrossProfilesEnabled()) {
// TODO(b/148594054): Create a proper dialogue.
new AlertDialog.Builder(getActivity())
.setTitle(R.string.interact_across_profiles_consent_dialog_title)
.setMessage(R.string.interact_across_profiles_consent_dialog_summary)
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
enableInteractAcrossProfiles(true);
refreshUi();
}
})
.setNegativeButton(R.string.deny, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
refreshUi();
}
})
.create().show();
} else {
enableInteractAcrossProfiles(false);
refreshUi();
showConsentDialog();
}
return true;
}
private void showConsentDialog() {
final String appLabel = mPackageInfo.applicationInfo.loadLabel(mPackageManager).toString();
final View dialogView = getLayoutInflater().inflate(
R.layout.interact_across_profiles_consent_dialog, null);
final TextView dialogTitle = dialogView.findViewById(
R.id.interact_across_profiles_consent_dialog_title);
dialogTitle.setText(
getString(R.string.interact_across_profiles_consent_dialog_title, appLabel));
final TextView dialogSummary = dialogView.findViewById(
R.id.interact_across_profiles_consent_dialog_summary);
dialogSummary.setText(
getString(R.string.interact_across_profiles_consent_dialog_summary, appLabel));
final TextView appDataSummary = dialogView.findViewById(R.id.app_data_summary);
appDataSummary.setText(getString(
R.string.interact_across_profiles_consent_dialog_app_data_summary, appLabel));
final TextView permissionsSummary = dialogView.findViewById(R.id.permissions_summary);
permissionsSummary.setText(getString(
R.string.interact_across_profiles_consent_dialog_permissions_summary, appLabel));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(dialogView)
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
enableInteractAcrossProfiles(true);
refreshUi();
}
})
.setNegativeButton(R.string.deny, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
refreshUi();
}
})
.create().show();
}
private boolean isInteractAcrossProfilesEnabled() {
return isInteractAcrossProfilesEnabled(
mContext, mPackageName, mPackageInfo.applicationInfo.uid);
}
private static boolean isInteractAcrossProfilesEnabled(Context context, String packageName, int uid) {
static boolean isInteractAcrossProfilesEnabled(Context context, String packageName, int uid) {
return PermissionChecker.PERMISSION_GRANTED
== PermissionChecker.checkPermissionForPreflight(
context,
@@ -178,13 +211,21 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
return false;
}
mSwitchPref.setChecked(isInteractAcrossProfilesEnabled());
final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz);
if (horizontalArrowIcon != null) {
final Drawable icon = mSwitchPref.isChecked()
? mContext.getDrawable(R.drawable.ic_swap_horiz_blue)
: mContext.getDrawable(R.drawable.ic_swap_horiz_grey);
horizontalArrowIcon.setImageDrawable(icon);
if (isInteractAcrossProfilesEnabled()) {
mSwitchPref.setChecked(true);
mSwitchPref.setTitle(R.string.interact_across_profiles_switch_enabled);
if (horizontalArrowIcon != null) {
horizontalArrowIcon.setImageDrawable(
mContext.getDrawable(R.drawable.ic_swap_horiz_blue));
}
} else {
mSwitchPref.setChecked(false);
mSwitchPref.setTitle(R.string.interact_across_profiles_switch_disabled);
if (horizontalArrowIcon != null) {
horizontalArrowIcon.setImageDrawable(
mContext.getDrawable(R.drawable.ic_swap_horiz_grey));
}
}
return true;
}

View File

@@ -73,7 +73,7 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
screen.removeAll();
final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps =
collectConfigurableApps();
collectConfigurableApps(mPackageManager, mUserManager, mCrossProfileApps);
final Context prefContext = getPrefContext();
for (final Pair<ApplicationInfo, UserHandle> appData : crossProfileApps) {
@@ -124,21 +124,37 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
* @return the list of applications for the personal profile in the calling user's profile group
* that can configure interact across profiles.
*/
ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps() {
final UserHandle personalProfile = getPersonalProfileForCallingUser();
static ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps(
PackageManager packageManager, UserManager userManager,
CrossProfileApps crossProfileApps) {
final UserHandle personalProfile = getPersonalProfileForCallingUser(userManager);
if (personalProfile == null) {
return new ArrayList<>();
}
final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps = new ArrayList<>();
final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
final ArrayList<Pair<ApplicationInfo, UserHandle>> apps = new ArrayList<>();
final List<PackageInfo> installedPackages = packageManager.getInstalledPackagesAsUser(
GET_ACTIVITIES, personalProfile.getIdentifier());
for (PackageInfo packageInfo : installedPackages) {
if (mCrossProfileApps.canConfigureInteractAcrossProfiles(packageInfo.packageName)) {
crossProfileApps.add(new Pair<>(packageInfo.applicationInfo, personalProfile));
if (crossProfileApps.canConfigureInteractAcrossProfiles(packageInfo.packageName)) {
apps.add(new Pair<>(packageInfo.applicationInfo, personalProfile));
}
}
return crossProfileApps;
return apps;
}
/**
* @return the number of applications that can interact across profiles.
*/
static int getNumberOfEnabledApps(
Context context, PackageManager packageManager, UserManager userManager,
CrossProfileApps crossProfileApps) {
final ArrayList<Pair<ApplicationInfo, UserHandle>> apps =
collectConfigurableApps(packageManager, userManager, crossProfileApps);
apps.removeIf(
app -> !InteractAcrossProfilesDetails.isInteractAcrossProfilesEnabled(
context, app.first.packageName, app.first.uid));
return apps.size();
}
/**
@@ -146,12 +162,12 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
* Returns null if user is not in a profile group.
*/
@Nullable
private UserHandle getPersonalProfileForCallingUser() {
private static UserHandle getPersonalProfileForCallingUser(UserManager userManager) {
final int callingUser = UserHandle.myUserId();
if (mUserManager.getProfiles(callingUser).isEmpty()) {
if (userManager.getProfiles(callingUser).isEmpty()) {
return null;
}
final UserInfo parentProfile = mUserManager.getProfileParent(callingUser);
final UserInfo parentProfile = userManager.getProfileParent(callingUser);
return parentProfile == null
? UserHandle.of(callingUser) : parentProfile.getUserHandle();
}

View File

@@ -20,10 +20,12 @@ import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Pair;
@@ -32,12 +34,10 @@ import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowProcess;
import org.robolectric.util.ReflectionHelpers;
import java.util.List;
@@ -47,6 +47,7 @@ public class InteractAcrossProfilesSettingsTest {
private static final int PERSONAL_PROFILE_ID = 0;
private static final int WORK_PROFILE_ID = 10;
private static final int WORK_UID = UserHandle.PER_USER_RANGE * WORK_PROFILE_ID;
private static final int PACKAGE_UID = 0;
private static final String PERSONAL_CROSS_PROFILE_PACKAGE = "personalCrossProfilePackage";
private static final String PERSONAL_NON_CROSS_PROFILE_PACKAGE =
@@ -58,21 +59,17 @@ public class InteractAcrossProfilesSettingsTest {
ImmutableList.of(PERSONAL_CROSS_PROFILE_PACKAGE, PERSONAL_NON_CROSS_PROFILE_PACKAGE);
private static final List<String> WORK_PROFILE_INSTALLED_PACKAGES =
ImmutableList.of(WORK_CROSS_PROFILE_PACKAGE, WORK_NON_CROSS_PROFILE_PACKAGE);
public static final String INTERACT_ACROSS_PROFILES_PERMISSION =
"android.permission.INTERACT_ACROSS_PROFILES";
private final Context mContext = ApplicationProvider.getApplicationContext();
private final PackageManager mPackageManager = mContext.getPackageManager();
private final UserManager mUserManager = mContext.getSystemService(UserManager.class);
private final CrossProfileApps mCrossProfileApps =
mContext.getSystemService(CrossProfileApps.class);
private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
private final InteractAcrossProfilesSettings mFragment = new InteractAcrossProfilesSettings();
@Before
public void setup() {
ReflectionHelpers.setField(mFragment, "mPackageManager", mPackageManager);
ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
ReflectionHelpers.setField(mFragment, "mCrossProfileApps", mCrossProfileApps);
}
@Test
public void collectConfigurableApps_fromPersonal_returnsPersonalPackages() {
shadowOf(mUserManager).addUser(
@@ -87,7 +84,8 @@ public class InteractAcrossProfilesSettingsTest {
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE);
shadowOf(mCrossProfileApps).addCrossProfilePackage(WORK_CROSS_PROFILE_PACKAGE);
List<Pair<ApplicationInfo, UserHandle>> apps = mFragment.collectConfigurableApps();
List<Pair<ApplicationInfo, UserHandle>> apps = mFragment.collectConfigurableApps(
mPackageManager, mUserManager, mCrossProfileApps);
assertThat(apps.size()).isEqualTo(1);
assertThat(apps.get(0).first.packageName).isEqualTo(PERSONAL_CROSS_PROFILE_PACKAGE);
@@ -108,7 +106,8 @@ public class InteractAcrossProfilesSettingsTest {
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE);
shadowOf(mCrossProfileApps).addCrossProfilePackage(WORK_CROSS_PROFILE_PACKAGE);
List<Pair<ApplicationInfo, UserHandle>> apps = mFragment.collectConfigurableApps();
List<Pair<ApplicationInfo, UserHandle>> apps = mFragment.collectConfigurableApps(
mPackageManager, mUserManager, mCrossProfileApps);
assertThat(apps.size()).isEqualTo(1);
assertThat(apps.get(0).first.packageName).isEqualTo(PERSONAL_CROSS_PROFILE_PACKAGE);
@@ -122,8 +121,40 @@ public class InteractAcrossProfilesSettingsTest {
PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES);
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE);
List<Pair<ApplicationInfo, UserHandle>> apps = mFragment.collectConfigurableApps();
List<Pair<ApplicationInfo, UserHandle>> apps = mFragment.collectConfigurableApps(
mPackageManager, mUserManager, mCrossProfileApps);
assertThat(apps).isEmpty();
}
@Test
public void getNumberOfEnabledApps_returnsNumberOfEnabledApps() {
shadowOf(mUserManager).addUser(
PERSONAL_PROFILE_ID, "personal-profile"/* name */, 0/* flags */);
shadowOf(mUserManager).addProfile(
PERSONAL_PROFILE_ID, WORK_PROFILE_ID,
"work-profile"/* profileName */, 0/* profileFlags */);
shadowOf(mPackageManager).setInstalledPackagesForUserId(
PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES);
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE);
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_NON_CROSS_PROFILE_PACKAGE);
String appOp = AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION);
shadowOf(mAppOpsManager).setMode(
appOp, PACKAGE_UID, PERSONAL_CROSS_PROFILE_PACKAGE, AppOpsManager.MODE_ALLOWED);
shadowOf(mAppOpsManager).setMode(
appOp, PACKAGE_UID, PERSONAL_CROSS_PROFILE_PACKAGE, AppOpsManager.MODE_IGNORED);
shadowOf(mPackageManager).addPermissionInfo(createCrossProfilesPermissionInfo());
int numOfApps = mFragment.getNumberOfEnabledApps(
mContext, mPackageManager, mUserManager, mCrossProfileApps);
assertThat(numOfApps).isEqualTo(1);
}
private PermissionInfo createCrossProfilesPermissionInfo() {
PermissionInfo permissionInfo = new PermissionInfo();
permissionInfo.name = INTERACT_ACROSS_PROFILES_PERMISSION;
permissionInfo.protectionLevel = PermissionInfo.PROTECTION_FLAG_APPOP;
return permissionInfo;
}
}