Merge "UX changes for the new cross profile settings page"

This commit is contained in:
TreeHugger Robot
2020-02-14 13:44:59 +00:00
committed by Android (Google) Code Review
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

@@ -8456,33 +8456,55 @@
<!-- Special access > Title for managing the settings where users opt-in to connect a work app <!-- 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] --> 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> <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> <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] --> <!-- 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] --> <!-- Apps > App Details > Connected personal and work apps > Switch title. [CHAR LIMIT=NONE] -->
<string name="interact_across_profiles_app_detail_switch" translatable="false">Connect these apps</string> <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> <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> <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 personal and work apps > Consent dialog title. [CHAR LIMIT=NONE] -->
<!-- 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 %1$s with your personal data?</string>
<string name="interact_across_profiles_consent_dialog_title" translatable="false">Trust work Calendar with your personal data?</string>
<!-- TODO(b/148594054): Replace calendar with actual app name --> <!-- Apps > App Details > Connected personal and work apps > Consent dialog description. [CHAR LIMIT=NONE] -->
<!-- Apps > App Details > Connected work and personal 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>
<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 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] --> <!-- 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> <string name="manage_zen_access_title">Do Not Disturb access</string>

View File

@@ -643,4 +643,49 @@
<item name="android:clipChildren">true</item> <item name="android:clipChildren">true</item>
</style> </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> </resources>

View File

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

View File

@@ -39,6 +39,13 @@
android:value="com.android.settings.Settings$HighPowerApplicationsActivity" /> android:value="com.android.settings.Settings$HighPowerApplicationsActivity" />
</Preference> </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 <Preference
android:key="device_administrators" android:key="device_administrators"
android:title="@string/manage_device_admin" android:title="@string/manage_device_admin"
@@ -146,13 +153,6 @@
android:value="com.android.settings.Settings$ChangeWifiStateActivity" /> android:value="com.android.settings.Settings$ChangeWifiStateActivity" />
</Preference> </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 <Preference
android:key="special_access_more" android:key="special_access_more"
android:title="@string/special_access_more" android:title="@string/special_access_more"

View File

@@ -17,10 +17,13 @@
package com.android.settings.applications.specialaccess.interactacrossprofiles; package com.android.settings.applications.specialaccess.interactacrossprofiles;
import android.content.Context; import android.content.Context;
import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController; import com.android.settings.core.BasePreferenceController;
import java.util.List; import java.util.List;
@@ -33,12 +36,16 @@ public class InteractAcrossProfilesController extends BasePreferenceController {
private final Context mContext; private final Context mContext;
private final UserManager mUserManager; private final UserManager mUserManager;
private final PackageManager mPackageManager;
private final CrossProfileApps mCrossProfileApps;
public InteractAcrossProfilesController(Context context, String preferenceKey) { public InteractAcrossProfilesController(Context context, String preferenceKey) {
super(context, preferenceKey); super(context, preferenceKey);
mContext = context; mContext = context;
mUserManager = mContext.getSystemService(UserManager.class); mUserManager = mContext.getSystemService(UserManager.class);
mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
mPackageManager = mContext.getPackageManager();
} }
@Override @Override
@@ -51,4 +58,17 @@ public class InteractAcrossProfilesController extends BasePreferenceController {
} }
return DISABLED_FOR_USER; 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.DialogInterface;
import android.content.PermissionChecker; import android.content.PermissionChecker;
import android.content.pm.CrossProfileApps; import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@@ -51,6 +53,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
private UserManager mUserManager; private UserManager mUserManager;
private SwitchPreference mSwitchPref; private SwitchPreference mSwitchPref;
private LayoutPreference mHeader; private LayoutPreference mHeader;
private PackageManager mPackageManager;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -59,6 +62,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
mContext = getContext(); mContext = getContext();
mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class); mCrossProfileApps = mContext.getSystemService(CrossProfileApps.class);
mUserManager = mContext.getSystemService(UserManager.class); mUserManager = mContext.getSystemService(UserManager.class);
mPackageManager = mContext.getPackageManager();
addPreferencesFromResource(R.xml.interact_across_profiles_permissions_details); addPreferencesFromResource(R.xml.interact_across_profiles_permissions_details);
mSwitchPref = findPreference(INTERACT_ACROSS_PROFILES_SETTINGS_SWITCH); mSwitchPref = findPreference(INTERACT_ACROSS_PROFILES_SETTINGS_SWITCH);
@@ -72,10 +76,17 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
} }
final UserHandle workProfile = getWorkProfile(); final UserHandle workProfile = getWorkProfile();
final UserHandle personalProfile = mUserManager.getProfileParent(workProfile); final UserHandle personalProfile = mUserManager.getProfileParent(workProfile);
addAppIcons(personalProfile, workProfile); addAppTitleAndIcons(personalProfile, 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);
} }
private void addAppIcons(UserHandle personalProfile, UserHandle workProfile) {
final ImageView personalIconView = mHeader.findViewById(R.id.entity_header_icon_personal); final ImageView personalIconView = mHeader.findViewById(R.id.entity_header_icon_personal);
if (personalIconView != null) { if (personalIconView != null) {
personalIconView.setImageDrawable(IconDrawableFactory.newInstance(mContext) personalIconView.setImageDrawable(IconDrawableFactory.newInstance(mContext)
@@ -114,10 +125,37 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
return true; return true;
} }
if (!isInteractAcrossProfilesEnabled()) { if (!isInteractAcrossProfilesEnabled()) {
// TODO(b/148594054): Create a proper dialogue. showConsentDialog();
new AlertDialog.Builder(getActivity()) }
.setTitle(R.string.interact_across_profiles_consent_dialog_title) return true;
.setMessage(R.string.interact_across_profiles_consent_dialog_summary) }
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() { .setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
enableInteractAcrossProfiles(true); enableInteractAcrossProfiles(true);
@@ -130,11 +168,6 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
} }
}) })
.create().show(); .create().show();
} else {
enableInteractAcrossProfiles(false);
refreshUi();
}
return true;
} }
private boolean isInteractAcrossProfilesEnabled() { private boolean isInteractAcrossProfilesEnabled() {
@@ -142,7 +175,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
mContext, mPackageName, mPackageInfo.applicationInfo.uid); 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 return PermissionChecker.PERMISSION_GRANTED
== PermissionChecker.checkPermissionForPreflight( == PermissionChecker.checkPermissionForPreflight(
context, context,
@@ -178,13 +211,21 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
return false; return false;
} }
mSwitchPref.setChecked(isInteractAcrossProfilesEnabled());
final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz); final ImageView horizontalArrowIcon = mHeader.findViewById(R.id.entity_header_swap_horiz);
if (isInteractAcrossProfilesEnabled()) {
mSwitchPref.setChecked(true);
mSwitchPref.setTitle(R.string.interact_across_profiles_switch_enabled);
if (horizontalArrowIcon != null) { if (horizontalArrowIcon != null) {
final Drawable icon = mSwitchPref.isChecked() horizontalArrowIcon.setImageDrawable(
? mContext.getDrawable(R.drawable.ic_swap_horiz_blue) mContext.getDrawable(R.drawable.ic_swap_horiz_blue));
: mContext.getDrawable(R.drawable.ic_swap_horiz_grey); }
horizontalArrowIcon.setImageDrawable(icon); } 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; return true;
} }

View File

@@ -73,7 +73,7 @@ public class InteractAcrossProfilesSettings extends EmptyTextSettings {
screen.removeAll(); screen.removeAll();
final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps = final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps =
collectConfigurableApps(); collectConfigurableApps(mPackageManager, mUserManager, mCrossProfileApps);
final Context prefContext = getPrefContext(); final Context prefContext = getPrefContext();
for (final Pair<ApplicationInfo, UserHandle> appData : crossProfileApps) { 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 * @return the list of applications for the personal profile in the calling user's profile group
* that can configure interact across profiles. * that can configure interact across profiles.
*/ */
ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps() { static ArrayList<Pair<ApplicationInfo, UserHandle>> collectConfigurableApps(
final UserHandle personalProfile = getPersonalProfileForCallingUser(); PackageManager packageManager, UserManager userManager,
CrossProfileApps crossProfileApps) {
final UserHandle personalProfile = getPersonalProfileForCallingUser(userManager);
if (personalProfile == null) { if (personalProfile == null) {
return new ArrayList<>(); return new ArrayList<>();
} }
final ArrayList<Pair<ApplicationInfo, UserHandle>> crossProfileApps = new ArrayList<>(); final ArrayList<Pair<ApplicationInfo, UserHandle>> apps = new ArrayList<>();
final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser( final List<PackageInfo> installedPackages = packageManager.getInstalledPackagesAsUser(
GET_ACTIVITIES, personalProfile.getIdentifier()); GET_ACTIVITIES, personalProfile.getIdentifier());
for (PackageInfo packageInfo : installedPackages) { for (PackageInfo packageInfo : installedPackages) {
if (mCrossProfileApps.canConfigureInteractAcrossProfiles(packageInfo.packageName)) { if (crossProfileApps.canConfigureInteractAcrossProfiles(packageInfo.packageName)) {
crossProfileApps.add(new Pair<>(packageInfo.applicationInfo, personalProfile)); 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. * Returns null if user is not in a profile group.
*/ */
@Nullable @Nullable
private UserHandle getPersonalProfileForCallingUser() { private static UserHandle getPersonalProfileForCallingUser(UserManager userManager) {
final int callingUser = UserHandle.myUserId(); final int callingUser = UserHandle.myUserId();
if (mUserManager.getProfiles(callingUser).isEmpty()) { if (userManager.getProfiles(callingUser).isEmpty()) {
return null; return null;
} }
final UserInfo parentProfile = mUserManager.getProfileParent(callingUser); final UserInfo parentProfile = userManager.getProfileParent(callingUser);
return parentProfile == null return parentProfile == null
? UserHandle.of(callingUser) : parentProfile.getUserHandle(); ? 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 static org.robolectric.Shadows.shadowOf;
import android.app.AppOpsManager;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps; import android.content.pm.CrossProfileApps;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.util.Pair; import android.util.Pair;
@@ -32,12 +34,10 @@ import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowProcess; import org.robolectric.shadows.ShadowProcess;
import org.robolectric.util.ReflectionHelpers;
import java.util.List; import java.util.List;
@@ -47,6 +47,7 @@ public class InteractAcrossProfilesSettingsTest {
private static final int PERSONAL_PROFILE_ID = 0; private static final int PERSONAL_PROFILE_ID = 0;
private static final int WORK_PROFILE_ID = 10; 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 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_CROSS_PROFILE_PACKAGE = "personalCrossProfilePackage";
private static final String PERSONAL_NON_CROSS_PROFILE_PACKAGE = 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); ImmutableList.of(PERSONAL_CROSS_PROFILE_PACKAGE, PERSONAL_NON_CROSS_PROFILE_PACKAGE);
private static final List<String> WORK_PROFILE_INSTALLED_PACKAGES = private static final List<String> WORK_PROFILE_INSTALLED_PACKAGES =
ImmutableList.of(WORK_CROSS_PROFILE_PACKAGE, WORK_NON_CROSS_PROFILE_PACKAGE); 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 Context mContext = ApplicationProvider.getApplicationContext();
private final PackageManager mPackageManager = mContext.getPackageManager(); private final PackageManager mPackageManager = mContext.getPackageManager();
private final UserManager mUserManager = mContext.getSystemService(UserManager.class); private final UserManager mUserManager = mContext.getSystemService(UserManager.class);
private final CrossProfileApps mCrossProfileApps = private final CrossProfileApps mCrossProfileApps =
mContext.getSystemService(CrossProfileApps.class); mContext.getSystemService(CrossProfileApps.class);
private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
private final InteractAcrossProfilesSettings mFragment = new InteractAcrossProfilesSettings(); 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 @Test
public void collectConfigurableApps_fromPersonal_returnsPersonalPackages() { public void collectConfigurableApps_fromPersonal_returnsPersonalPackages() {
shadowOf(mUserManager).addUser( shadowOf(mUserManager).addUser(
@@ -87,7 +84,8 @@ public class InteractAcrossProfilesSettingsTest {
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE); shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE);
shadowOf(mCrossProfileApps).addCrossProfilePackage(WORK_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.size()).isEqualTo(1);
assertThat(apps.get(0).first.packageName).isEqualTo(PERSONAL_CROSS_PROFILE_PACKAGE); 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(PERSONAL_CROSS_PROFILE_PACKAGE);
shadowOf(mCrossProfileApps).addCrossProfilePackage(WORK_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.size()).isEqualTo(1);
assertThat(apps.get(0).first.packageName).isEqualTo(PERSONAL_CROSS_PROFILE_PACKAGE); 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); PERSONAL_PROFILE_ID, PERSONAL_PROFILE_INSTALLED_PACKAGES);
shadowOf(mCrossProfileApps).addCrossProfilePackage(PERSONAL_CROSS_PROFILE_PACKAGE); 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(); 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;
}
} }