diff options
5 files changed, 250 insertions, 21 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index cf6619fa23ce..cbb0f51b68a3 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3165,6 +3165,22 @@ public class DevicePolicyManager { } /** + * Start Quick Contact on the managed profile for the current user, if the policy allows. + * @hide + */ + public void startManagedQuickContact(String actualLookupKey, long actualContactId, + Intent originalIntent) { + if (mService != null) { + try { + mService.startManagedQuickContact( + actualLookupKey, actualContactId, originalIntent); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** * Called by the profile owner of a managed profile so that some intents sent in the managed * profile can also be resolved in the parent, or vice versa. * Only activity intents are supported. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 9ca52e5ddf38..73b06846ba60 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -189,6 +189,7 @@ interface IDevicePolicyManager { void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled); boolean getCrossProfileCallerIdDisabled(in ComponentName who); boolean getCrossProfileCallerIdDisabledForUser(int userId); + void startManagedQuickContact(String lookupKey, long contactId, in Intent originalIntent); void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent, in PersistableBundle args); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index e4a6f07f323d..bf7f3cbb9864 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -18,6 +18,7 @@ package android.provider; import android.accounts.Account; import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.content.ActivityNotFoundException; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; @@ -1628,7 +1629,6 @@ public final class ContactsContract { */ public static final String CONTENT_VCARD_TYPE = "text/x-vcard"; - /** * Mimimal ID for corp contacts returned from * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}. @@ -1638,6 +1638,14 @@ public final class ContactsContract { public static long ENTERPRISE_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30 /** + * Prefix for corp contacts returned from + * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}. + * + * @hide + */ + public static String ENTERPRISE_CONTACT_LOOKUP_PREFIX = "c-"; + + /** * Return TRUE if a contact ID is from the contacts provider on the enterprise profile. * * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact. @@ -5032,9 +5040,17 @@ public final class ContactsContract { * is from the corp profile, use * {@link ContactsContract.Contacts#isEnterpriseContactId(long)}. * </li> + * <li> + * Corp contacts will get artificial {@link #LOOKUP_KEY}s too. + * </li> * </ul> * <p> - * This URI does NOT support selection nor order-by. + * A contact lookup URL built by + * {@link ContactsContract.Contacts#getLookupUri(long, String)} + * with an {@link #_ID} and a {@link #LOOKUP_KEY} returned by this API can be passed to + * {@link ContactsContract.QuickContact#showQuickContact} even if a contact is from the + * corp profile. + * </p> * * <pre> * Uri lookupUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI, @@ -6025,10 +6041,18 @@ public final class ContactsContract { * a contact * is from the corp profile, use * {@link ContactsContract.Contacts#isEnterpriseContactId(long)}. - * </li> - * </ul> - * <p> - * This URI does NOT support selection nor order-by. + * </li> + * <li> + * Corp contacts will get artificial {@link #LOOKUP_KEY}s too. + * </li> + * </ul> + * <p> + * A contact lookup URL built by + * {@link ContactsContract.Contacts#getLookupUri(long, String)} + * with an {@link #_ID} and a {@link #LOOKUP_KEY} returned by this API can be passed to + * {@link ContactsContract.QuickContact#showQuickContact} even if a contact is from the + * corp profile. + * </p> * * <pre> * Uri lookupUri = Uri.withAppendedPath(Email.ENTERPRISE_CONTENT_LOOKUP_URI, @@ -8182,6 +8206,9 @@ public final class ContactsContract { */ public static final int MODE_LARGE = 3; + /** @hide */ + public static final int MODE_DEFAULT = MODE_LARGE; + /** * Constructs the QuickContacts intent with a view's rect. * @hide @@ -8224,6 +8251,7 @@ public final class ContactsContract { // Launch pivot dialog through intent for now final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags); + // NOTE: This logic and rebuildManagedQuickContactsIntent() must be in sync. intent.setData(lookupUri); intent.setSourceBounds(target); intent.putExtra(EXTRA_MODE, mode); @@ -8232,6 +8260,30 @@ public final class ContactsContract { } /** + * Constructs a QuickContacts intent based on an incoming intent for DevicePolicyManager + * to strip off anything not necessary. + * + * @hide + */ + public static Intent rebuildManagedQuickContactsIntent(String lookupKey, long contactId, + Intent originalIntent) { + final Intent intent = new Intent(ACTION_QUICK_CONTACT); + // Rebuild the URI from a lookup key and a contact ID. + intent.setData(Contacts.getLookupUri(contactId, lookupKey)); + + // Copy flags and always set NEW_TASK because it won't have a parent activity. + intent.setFlags(originalIntent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + + // Copy extras. + intent.setSourceBounds(originalIntent.getSourceBounds()); + intent.putExtra(EXTRA_MODE, originalIntent.getIntExtra(EXTRA_MODE, MODE_DEFAULT)); + intent.putExtra(EXTRA_EXCLUDE_MIMES, + originalIntent.getStringArrayExtra(EXTRA_EXCLUDE_MIMES)); + return intent; + } + + + /** * Trigger a dialog that lists the various methods of interacting with * the requested {@link Contacts} entry. This may be based on available * {@link ContactsContract.Data} rows under that contact, and may also @@ -8259,7 +8311,7 @@ public final class ContactsContract { // Trigger with obtained rectangle Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, excludeMimes); - startActivityWithErrorToast(context, intent); + ContactsInternal.startQuickContactWithErrorToast(context, intent); } /** @@ -8292,7 +8344,7 @@ public final class ContactsContract { String[] excludeMimes) { Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode, excludeMimes); - startActivityWithErrorToast(context, intent); + ContactsInternal.startQuickContactWithErrorToast(context, intent); } /** @@ -8325,10 +8377,10 @@ public final class ContactsContract { // Use MODE_LARGE instead of accepting mode as a parameter. The different mode // values defined in ContactsContract only affect very old implementations // of QuickContacts. - Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE, + Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_DEFAULT, excludeMimes); intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType); - startActivityWithErrorToast(context, intent); + ContactsInternal.startQuickContactWithErrorToast(context, intent); } /** @@ -8363,19 +8415,10 @@ public final class ContactsContract { // Use MODE_LARGE instead of accepting mode as a parameter. The different mode // values defined in ContactsContract only affect very old implementations // of QuickContacts. - Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE, + Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_DEFAULT, excludeMimes); intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType); - startActivityWithErrorToast(context, intent); - } - - private static void startActivityWithErrorToast(Context context, Intent intent) { - try { - context.startActivity(intent); - } catch (ActivityNotFoundException e) { - Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available, - Toast.LENGTH_SHORT).show(); - } + ContactsInternal.startQuickContactWithErrorToast(context, intent); } } diff --git a/core/java/android/provider/ContactsInternal.java b/core/java/android/provider/ContactsInternal.java new file mode 100644 index 000000000000..059a6039d5b6 --- /dev/null +++ b/core/java/android/provider/ContactsInternal.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 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 android.provider; + +import android.app.admin.DevicePolicyManager; +import android.content.ActivityNotFoundException; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.UriMatcher; +import android.net.Uri; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.widget.Toast; + +import java.util.List; + +/** + * Contacts related internal methods. + * + * @hide + */ +public class ContactsInternal { + private ContactsInternal() { + } + + /** URI matcher used to parse contact URIs. */ + private static final UriMatcher sContactsUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + + private static final int CONTACTS_URI_LOOKUP_ID = 1000; + + static { + // Contacts URI matching table + final UriMatcher matcher = sContactsUriMatcher; + matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", CONTACTS_URI_LOOKUP_ID); + } + + /** + * Called by {@link ContactsContract} to star Quick Contact, possibly on the managed profile. + */ + public static void startQuickContactWithErrorToast(Context context, Intent intent) { + final Uri uri = intent.getData(); + + final int match = sContactsUriMatcher.match(uri); + switch (match) { + case CONTACTS_URI_LOOKUP_ID: { + if (maybeStartManagedQuickContact(context, intent)) { + return; // Request handled by DPM. Just return here. + } + break; + } + } + // Launch on the current profile. + startQuickContactWithErrorToastForUser(context, intent, Process.myUserHandle()); + } + + public static void startQuickContactWithErrorToastForUser(Context context, Intent intent, + UserHandle user) { + try { + context.startActivityAsUser(intent, user); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available, + Toast.LENGTH_SHORT).show(); + } + } + + /** + * If the URI in {@code intent} is of a corp contact, launch quick contact on the managed + * profile. + * + * @return the URI in {@code intent} is of a corp contact thus launched on the managed profile. + */ + private static boolean maybeStartManagedQuickContact(Context context, Intent originalIntent) { + final Uri uri = originalIntent.getData(); + + // Decompose into an ID and a lookup key. + final List<String> pathSegments = uri.getPathSegments(); + final long contactId = ContentUris.parseId(uri); + final String lookupKey = pathSegments.get(2); + + // See if it has a corp lookupkey. + if (TextUtils.isEmpty(lookupKey) + || !lookupKey.startsWith( + ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX)) { + return false; // It's not a corp lookup key. + } + + // Launch Quick Contact on the managed profile, if the policy allows. + final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + final String actualLookupKey = lookupKey.substring( + ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX.length()); + final long actualContactId = + (contactId - ContactsContract.Contacts.ENTERPRISE_CONTACT_ID_BASE); + + dpm.startManagedQuickContact(actualLookupKey, actualContactId, originalIntent); + return true; + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 73b5de1b42f9..0c58aefe499e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -78,6 +78,8 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.ContactsContract.QuickContact; +import android.provider.ContactsInternal; import android.provider.Settings; import android.security.Credentials; import android.security.IKeyChainAliasCallback; @@ -146,6 +148,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String LOG_TAG = "DevicePolicyManagerService"; + private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE + private static final String DEVICE_POLICIES_XML = "device_policies.xml"; private static final String LOCK_TASK_COMPONENTS_XML = "lock-task-component"; @@ -5435,6 +5439,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public void startManagedQuickContact(String actualLookupKey, long actualContactId, + Intent originalIntent) { + final Intent intent = QuickContact.rebuildManagedQuickContactsIntent( + actualLookupKey, actualContactId, originalIntent); + final int callingUserId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + final int managedUserId = getManagedUserId(callingUserId); + if (managedUserId < 0) { + return; + } + if (getCrossProfileCallerIdDisabledForUser(managedUserId)) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, + "Cross-profile contacts access disabled for user " + managedUserId); + } + return; + } + ContactsInternal.startQuickContactWithErrorToastForUser( + mContext, intent, new UserHandle(managedUserId)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * @return the user ID of the managed user that is linked to the current user, if any. + * Otherwise -1. + */ + public int getManagedUserId(int callingUserId) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "getManagedUserId: callingUserId=" + callingUserId); + } + + for (UserInfo ui : mUserManager.getProfiles(callingUserId)) { + if (ui.id == callingUserId || !ui.isManagedProfile()) { + continue; // Caller user self, or not a managed profile. Skip. + } + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Managed user=" + ui.id); + } + return ui.id; + } + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Managed user not found."); + } + return -1; + } + /** * Sets which packages may enter lock task mode. * |