Merge "Enterprise quick contact 1/2"
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cf6619f..cbb0f51 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3165,6 +3165,22 @@
}
/**
+ * 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 9ca52e5..73b0684 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -189,6 +189,7 @@
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 e4a6f07..6592295 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -18,6 +18,7 @@
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 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 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,16 @@
* 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 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 +6040,17 @@
* 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 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 +8204,9 @@
*/
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 +8249,7 @@
// 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 +8258,30 @@
}
/**
+ * 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 +8309,7 @@
// Trigger with obtained rectangle
Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
excludeMimes);
- startActivityWithErrorToast(context, intent);
+ ContactsInternal.startQuickContactWithErrorToast(context, intent);
}
/**
@@ -8292,7 +8342,7 @@
String[] excludeMimes) {
Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
excludeMimes);
- startActivityWithErrorToast(context, intent);
+ ContactsInternal.startQuickContactWithErrorToast(context, intent);
}
/**
@@ -8325,10 +8375,10 @@
// 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 +8413,10 @@
// 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 0000000..059a603
--- /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 73b5de1..0c58aef 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.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 @@
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 @@
}
}
+ @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.
*