diff options
| author | 2015-03-19 11:24:00 -0700 | |
|---|---|---|
| committer | 2015-03-30 16:28:39 -0700 | |
| commit | 1040da1d4eb99fd2588e4c4d5b08b2e3fc0c7777 (patch) | |
| tree | a87aaee36dd2f35aca0e5ba0dbeceb40b5b3e6b9 | |
| parent | 8a78286915a8f71eb09b5ae29a3bd8fb977180e6 (diff) | |
Enterprise quick contact 1/2
Now openQuickContact goes thorough DPM. When a lookup URI is build with
a lookup key returned by the enterprise lookup APIs for a corp contact, the
lookup key will have a special prefix. In that case we go through DPM
and have it launch QC on the managed profile, if the policy allows.
For now we use the same DPM policy as enterprise-caller-id to disable this.
Design doc: go/cp2-mnc-enterprise-dd
Bug 19546108
Change-Id: I831a8190ae902ae3b1248cce6df02e3a48f602d2
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. * |