aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/contacts/AbstractContactsProvider.java4
-rw-r--r--src/com/android/providers/contacts/CallLogDatabaseHelper.java93
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java415
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForNickname.java6
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForOrganization.java8
-rw-r--r--src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java42
-rw-r--r--src/com/android/providers/contacts/util/LogFields.java14
-rw-r--r--src/com/android/providers/contacts/util/LogUtils.java7
-rw-r--r--src/com/android/providers/contacts/util/UserUtils.java49
9 files changed, 496 insertions, 142 deletions
diff --git a/src/com/android/providers/contacts/AbstractContactsProvider.java b/src/com/android/providers/contacts/AbstractContactsProvider.java
index e00bd862..38daa50c 100644
--- a/src/com/android/providers/contacts/AbstractContactsProvider.java
+++ b/src/com/android/providers/contacts/AbstractContactsProvider.java
@@ -228,7 +228,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
if (++opCount >= BULK_INSERTS_PER_YIELD_POINT) {
opCount = 0;
try {
- yield(transaction);
+ this.yield(transaction);
} catch (RuntimeException re) {
transaction.markYieldFailed();
throw re;
@@ -274,7 +274,7 @@ public abstract class AbstractContactsProvider extends ContentProvider
}
opCount = 0;
try {
- if (yield(transaction)) {
+ if (this.yield(transaction)) {
ypCount++;
}
} catch (RuntimeException re) {
diff --git a/src/com/android/providers/contacts/CallLogDatabaseHelper.java b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
index c5052d70..63ba17da 100644
--- a/src/com/android/providers/contacts/CallLogDatabaseHelper.java
+++ b/src/com/android/providers/contacts/CallLogDatabaseHelper.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.preference.PreferenceManager;
@@ -203,7 +204,6 @@ public class CallLogDatabaseHelper {
VoicemailContract.Status.SOURCE_TYPE + " TEXT" +
");");
- migrateFromLegacyTables(db);
}
@Override
@@ -532,87 +532,6 @@ public class CallLogDatabaseHelper {
mPhoneAccountHandleMigrationUtils.migrateIccIdToSubId(db);
}
- /**
- * Perform the migration from the contacts2.db (of the latest version) to the current calllog/
- * voicemail status tables.
- */
- private void migrateFromLegacyTables(SQLiteDatabase calllog) {
- final SQLiteDatabase contacts = getContactsWritableDatabaseForMigration();
-
- if (contacts == null) {
- Log.w(TAG, "Contacts DB == null, skipping migration. (running tests?)");
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "migrateFromLegacyTables");
- }
-
- if ("1".equals(PropertyUtils.getProperty(calllog, DbProperties.DATA_MIGRATED, ""))) {
- return;
- }
-
- Log.i(TAG, "Migrating from old tables...");
-
- contacts.beginTransaction();
- try {
- if (!tableExists(contacts, LegacyConstants.CALLS_LEGACY)
- || !tableExists(contacts, LegacyConstants.VOICEMAIL_STATUS_LEGACY)) {
- // This is fine on new devices. (or after a "clear data".)
- Log.i(TAG, "Source tables don't exist.");
- return;
- }
- calllog.beginTransaction();
- try {
-
- final ContentValues cv = new ContentValues();
-
- try (Cursor source = contacts.rawQuery(
- "SELECT * FROM " + LegacyConstants.CALLS_LEGACY, null)) {
- while (source.moveToNext()) {
- cv.clear();
-
- DatabaseUtils.cursorRowToContentValues(source, cv);
-
- calllog.insertOrThrow(Tables.CALLS, null, cv);
- }
- }
-
- try (Cursor source = contacts.rawQuery("SELECT * FROM " +
- LegacyConstants.VOICEMAIL_STATUS_LEGACY, null)) {
- while (source.moveToNext()) {
- cv.clear();
-
- DatabaseUtils.cursorRowToContentValues(source, cv);
-
- calllog.insertOrThrow(Tables.VOICEMAIL_STATUS, null, cv);
- }
- }
-
- contacts.execSQL("DROP TABLE " + LegacyConstants.CALLS_LEGACY + ";");
- contacts.execSQL("DROP TABLE " + LegacyConstants.VOICEMAIL_STATUS_LEGACY + ";");
-
- // Also copy the last sync time.
- PropertyUtils.setProperty(calllog, DbProperties.CALL_LOG_LAST_SYNCED,
- PropertyUtils.getProperty(contacts,
- LegacyConstants.CALL_LOG_LAST_SYNCED_LEGACY, null));
-
- Log.i(TAG, "Migration completed.");
-
- calllog.setTransactionSuccessful();
- } finally {
- calllog.endTransaction();
- }
-
- contacts.setTransactionSuccessful();
- } catch (RuntimeException e) {
- // We don't want to be stuck here, so we just swallow exceptions...
- Log.w(TAG, "Exception caught during migration", e);
- } finally {
- contacts.endTransaction();
- }
- PropertyUtils.setProperty(calllog, DbProperties.DATA_MIGRATED, "1");
- }
-
@VisibleForTesting
static boolean tableExists(SQLiteDatabase db, String table) {
return DatabaseUtils.longForQuery(db,
@@ -621,9 +540,15 @@ public class CallLogDatabaseHelper {
}
@VisibleForTesting
- @Nullable // We return null during tests when migration is not needed.
+ @Nullable // We return null during tests when migration is not needed or database
+ // is unavailable.
SQLiteDatabase getContactsWritableDatabaseForMigration() {
- return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase();
+ try {
+ return ContactsDatabaseHelper.getInstance(mContext).getWritableDatabase();
+ } catch (SQLiteCantOpenDatabaseException e) {
+ Log.i(TAG, "Exception caught during opening database for migration: " + e);
+ return null;
+ }
}
public PhoneAccountHandleMigrationUtils getPhoneAccountHandleMigrationUtils() {
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 3f61a15a..56162916 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -22,13 +22,15 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.providers.contacts.util.PhoneAccountHandleMigrationUtils.TELEPHONY_COMPONENT_NAME;
-import android.os.Looper;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
@@ -47,6 +49,7 @@ import android.content.UriMatcher;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
+import android.content.pm.UserInfo;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
@@ -69,6 +72,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.RemoteException;
@@ -129,10 +133,14 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
import com.android.common.content.ProjectionMap;
import com.android.common.content.SyncStateContentProviderHelper;
import com.android.common.io.MoreCloseables;
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.appcloning.AppCloningDeviceConfigHelper;
import com.android.internal.util.ArrayUtils;
import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
@@ -282,6 +290,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
/** Rate limit (in milliseconds) for dangling contacts cleanup. Do it at most once per day. */
private static final int DANGLING_CONTACTS_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
+ /** Time after which an entry in the launchable clone packages cache is invalidated and needs to
+ * be refreshed.
+ */
+ private static final int LAUNCHABLE_CLONE_APPS_CACHE_ENTRY_REFRESH_INTERVAL = 10 * 60 * 1000;
+
+ /** This value indicates the frequency of cleanup of the launchable clone apps cache */
+ private static final int LAUNCHABLE_CLONE_APPS_CACHE_CLEANUP_LIMIT = 7 * 24 * 60 * 60 * 1000;
+
/** Maximum length of a phone number that can be inserted into the database */
private static final int PHONE_NUMBER_LENGTH_LIMIT = 1000;
@@ -341,6 +357,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
public static final int CONTACTS_ID_PHOTO_CORP = 1027;
public static final int CONTACTS_ID_DISPLAY_PHOTO_CORP = 1028;
public static final int CONTACTS_FILTER_ENTERPRISE = 1029;
+ public static final int CONTACTS_ENTERPRISE = 1030;
public static final int RAW_CONTACTS = 2002;
public static final int RAW_CONTACTS_ID = 2003;
@@ -1206,6 +1223,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
matcher.addURI(ContactsContract.AUTHORITY, "contacts/frequent", CONTACTS_FREQUENT);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/delete_usage", CONTACTS_DELETE_USAGE);
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/enterprise", CONTACTS_ENTERPRISE);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter_enterprise",
CONTACTS_FILTER_ENTERPRISE);
matcher.addURI(ContactsContract.AUTHORITY, "contacts/filter_enterprise/*",
@@ -1355,6 +1373,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
String authority;
String accountName;
String accountType;
+ String packageName;
}
/**
@@ -1368,6 +1387,18 @@ public class ContactsProvider2 extends AbstractContactsProvider
long groupId;
}
+ @VisibleForTesting
+ protected static class LaunchableCloneAppsCacheEntry {
+ boolean doesAppHaveLaunchableActivity;
+ long lastUpdatedAt;
+
+ public LaunchableCloneAppsCacheEntry(boolean doesAppHaveLaunchableActivity,
+ long lastUpdatedAt) {
+ this.doesAppHaveLaunchableActivity = doesAppHaveLaunchableActivity;
+ this.lastUpdatedAt = lastUpdatedAt;
+ }
+ }
+
/**
* The thread-local holder of the active transaction. Shared between this and the profile
* provider, to keep transactions on both databases synchronized.
@@ -1431,6 +1462,18 @@ public class ContactsProvider2 extends AbstractContactsProvider
private ArrayMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache = new ArrayMap<>();
/**
+ * Cache to store info on whether a cloned app has a launchable activity. This will be used to
+ * provide it access to query cross-profile contacts.
+ * The key to this map is the uid of the cloned app. The entries in the cache are refreshed
+ * after {@link LAUNCHABLE_CLONE_APPS_CACHE_ENTRY_REFRESH_INTERVAL} to ensure the uid values
+ * stored in the cache aren't stale.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLaunchableCloneAppsCache")
+ protected final SparseArray<LaunchableCloneAppsCacheEntry> mLaunchableCloneAppsCache =
+ new SparseArray<>();
+
+ /**
* Sub-provider for handling profile requests against the profile database.
*/
private ProfileProvider mProfileProvider;
@@ -1481,6 +1524,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
private long mLastDanglingContactsCleanup = 0;
+ @GuardedBy("mLaunchableCloneAppsCache")
+ private long mLastLaunchableCloneAppsCacheCleanup = 0;
+
private FastScrollingIndexCache mFastScrollingIndexCache;
// Stats about FastScrollingIndex.
@@ -1493,6 +1539,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
private Set<PhoneAccountHandle> mMigratedPhoneAccountHandles;
+ private AppCloningDeviceConfigHelper mAppCloningDeviceConfigHelper;
+
/**
* Subscription change will trigger ACTION_PHONE_ACCOUNT_REGISTERED that broadcasts new
* PhoneAccountHandle that is created based on the new subscription. This receiver is used
@@ -1568,6 +1616,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext());
mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
+ mAppCloningDeviceConfigHelper = AppCloningDeviceConfigHelper.getInstance(getContext());
mContactsHelper = getDatabaseHelper();
mDbHelper.set(mContactsHelper);
@@ -1579,7 +1628,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (mContactsHelper.getPhoneAccountHandleMigrationUtils()
.isPhoneAccountMigrationPending()) {
- IntentFilter filter = new IntentFilter(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+ IntentFilter filter = new IntentFilter(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
getContext().registerReceiver(mBroadcastReceiver, filter);
}
@@ -1720,6 +1769,13 @@ public class ContactsProvider2 extends AbstractContactsProvider
return new PhotoPriorityResolver(context);
}
+ @VisibleForTesting
+ protected SparseArray<LaunchableCloneAppsCacheEntry> getLaunchableCloneAppsCacheForTesting() {
+ synchronized (mLaunchableCloneAppsCache) {
+ return mLaunchableCloneAppsCache;
+ }
+ }
+
protected void scheduleBackgroundTask(int task) {
scheduleBackgroundTask(task, null);
}
@@ -2138,6 +2194,20 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
/**
+ * Returns whether contacts sharing is enabled allowing the clone contacts provider to use the
+ * parent contacts providers contacts data to serve its requests. The method returns true if
+ * the device supports clone profile contacts sharing and the feature flag for the same is
+ * turned on.
+ *
+ * @return true/false if contact sharing is enabled/disabled
+ */
+ @VisibleForTesting
+ protected boolean isContactSharingEnabledForCloneProfile() {
+ return getContext().getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks)
+ && mAppCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+ }
+
+ /**
* Maximum dimension (height or width) of photo thumbnails.
*/
public int getMaxThumbnailDim() {
@@ -2297,7 +2367,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.setUriType(sUriMatcher.match(uri))
.setCallerIsSyncAdapter(readBooleanQueryParameter(
uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
+ .setStartNanos(SystemClock.elapsedRealtimeNanos())
+ .setUid(Binder.getCallingUid());
Uri resultUri = null;
try {
@@ -2305,6 +2376,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
mContactsHelper.validateContentValues(getCallingPackage(), values);
+ if (!areContactWritesEnabled()) {
+ // Returning fake uri since the insert was rejected
+ Log.w(TAG, "Blocked insert with uri [" + uri + "]. Contact writes not enabled "
+ + "for the user");
+ return rejectInsert(uri, values);
+ }
if (mapsToProfileDbWithInsertedValues(uri, values)) {
switchToProfileMode();
resultUri = mProfileProvider.insert(uri, values);
@@ -2330,7 +2407,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.setUriType(sUriMatcher.match(uri))
.setCallerIsSyncAdapter(readBooleanQueryParameter(
uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
+ .setStartNanos(SystemClock.elapsedRealtimeNanos())
+ .setUid(Binder.getCallingUid());
int updates = 0;
try {
@@ -2339,6 +2417,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
mContactsHelper.validateContentValues(getCallingPackage(), values);
mContactsHelper.validateSql(getCallingPackage(), selection);
+ if (!areContactWritesEnabled()) {
+ // Returning 0, no rows were updated as writes are disabled
+ Log.w(TAG, "Blocked update with uri [" + uri + "]. Contact writes not enabled "
+ + "for the user");
+ return 0;
+ }
if (mapsToProfileDb(uri)) {
switchToProfileMode();
updates = mProfileProvider.update(uri, values, selection, selectionArgs);
@@ -2362,7 +2446,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.setUriType(sUriMatcher.match(uri))
.setCallerIsSyncAdapter(readBooleanQueryParameter(
uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
+ .setStartNanos(SystemClock.elapsedRealtimeNanos())
+ .setUid(Binder.getCallingUid());
int deletes = 0;
try {
@@ -2370,6 +2455,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
mContactsHelper.validateSql(getCallingPackage(), selection);
+ if (!areContactWritesEnabled()) {
+ // Returning 0, no rows were deleted as writes are disabled
+ Log.w(TAG, "Blocked delete with uri [" + uri + "]. Contact writes not enabled "
+ + "for the user");
+ return 0;
+ }
if (mapsToProfileDb(uri)) {
switchToProfileMode();
deletes = mProfileProvider.delete(uri, selection, selectionArgs);
@@ -2386,9 +2477,25 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
+ private void notifySimAccountsChanged() {
+ // This allows us to discard older broadcasts still waiting to be delivered.
+ final Bundle options = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+ .toBundle();
+
+ getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED), null,
+ options);
+ }
+
@Override
public Bundle call(String method, String arg, Bundle extras) {
waitForAccess(mReadAccessLatch);
+ if (!areContactWritesEnabled()) {
+ // Returning EMPTY Bundle since the call was rejected and no rows were affected
+ Log.w(TAG, "Blocked call to method [" + method + "], not enabled for the user");
+ return Bundle.EMPTY;
+ }
switchToContactMode();
if (Authorization.AUTHORIZATION_METHOD.equals(method)) {
Uri uri = extras.getParcelable(Authorization.KEY_URI_TO_AUTHORIZE);
@@ -2438,7 +2545,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
} finally {
db.endTransaction();
}
- getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+ notifySimAccountsChanged();
return response;
} else if (SimContacts.REMOVE_SIM_ACCOUNT_METHOD.equals(method)) {
ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
@@ -2458,7 +2565,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
} finally {
db.endTransaction();
}
- getContext().sendBroadcast(new Intent(SimContacts.ACTION_SIM_ACCOUNTS_CHANGED));
+ notifySimAccountsChanged();
return response;
} else if (SimContacts.QUERY_SIM_ACCOUNTS_METHOD.equals(method)) {
ContactsPermissions.enforceCallingOrSelfPermission(getContext(), READ_PERMISSION);
@@ -2607,6 +2714,12 @@ public class ContactsProvider2 extends AbstractContactsProvider
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
waitForAccess(mWriteAccessLatch);
+ if (!areContactWritesEnabled()) {
+ // Returning 0, no rows were affected since writes are disabled
+ Log.w(TAG, "Blocked bulkInsert with uri [" + uri + "]. Contact writes not enabled "
+ + "for the user");
+ return 0;
+ }
return super.bulkInsert(uri, values);
}
@@ -5552,7 +5665,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
.setUriType(sUriMatcher.match(uri))
.setCallerIsSyncAdapter(readBooleanQueryParameter(
uri, ContactsContract.CALLER_IS_SYNCADAPTER, false))
- .setStartNanos(SystemClock.elapsedRealtimeNanos());
+ .setStartNanos(SystemClock.elapsedRealtimeNanos())
+ .setUid(Binder.getCallingUid());
Cursor cursor = null;
try {
@@ -5590,10 +5704,15 @@ public class ContactsProvider2 extends AbstractContactsProvider
// If caller does not come from same profile, Check if it's privileged or allowed by
// enterprise policy
- if (!queryAllowedByEnterprisePolicy(uri)) {
+ if (!isCrossUserQueryAllowed(uri)) {
return null;
}
+ if (shouldRedirectQueryToParentProvider()) {
+ return queryParentProfileContactsProvider(uri, projection, selection, selectionArgs,
+ sortOrder, cancellationSignal);
+ }
+
// Query the profile DB if appropriate.
if (mapsToProfileDb(uri)) {
switchToProfileMode();
@@ -5613,7 +5732,68 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
- private boolean queryAllowedByEnterprisePolicy(Uri uri) {
+ /**
+ * Check if the query should be redirected to the parent profile's contacts provider.
+ */
+ private boolean shouldRedirectQueryToParentProvider() {
+ return isContactSharingEnabledForCloneProfile() &&
+ UserUtils.shouldUseParentsContacts(getContext()) &&
+ isAppAllowedToUseParentUsersContacts(getCallingPackage());
+ }
+
+ /**
+ * Check if the app with the given package name is allowed to use parent user's contacts to
+ * serve the contacts read queries.
+ */
+ @VisibleForTesting
+ protected boolean isAppAllowedToUseParentUsersContacts(@Nullable String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final UserHandle user = Binder.getCallingUserHandle();
+
+ synchronized (mLaunchableCloneAppsCache) {
+ maybeCleanupLaunchableCloneAppsCacheLocked();
+
+ final long now = System.currentTimeMillis();
+ LaunchableCloneAppsCacheEntry cacheEntry = mLaunchableCloneAppsCache.get(callingUid);
+ if (cacheEntry == null || ((now - cacheEntry.lastUpdatedAt)
+ >= LAUNCHABLE_CLONE_APPS_CACHE_ENTRY_REFRESH_INTERVAL)) {
+ boolean result = doesPackageHaveALauncherActivity(packageName, user);
+ mLaunchableCloneAppsCache.put(callingUid,
+ new LaunchableCloneAppsCacheEntry(result, now));
+ }
+ return mLaunchableCloneAppsCache.get(callingUid).doesAppHaveLaunchableActivity;
+ }
+ }
+
+ /**
+ * Clean up the launchable clone apps cache to ensure it doesn't have any stale entries taking
+ * up additional space. The frequency of the cleanup is governed by {@link
+ * LAUNCHABLE_CLONE_APPS_CACHE_CLEANUP_LIMIT}.
+ */
+ @GuardedBy("mLaunchableCloneAppsCache")
+ private void maybeCleanupLaunchableCloneAppsCacheLocked() {
+ long now = System.currentTimeMillis();
+ if (now - mLastLaunchableCloneAppsCacheCleanup
+ >= LAUNCHABLE_CLONE_APPS_CACHE_CLEANUP_LIMIT) {
+ mLaunchableCloneAppsCache.clear();
+ mLastLaunchableCloneAppsCacheCleanup = now;
+ }
+ }
+
+ @VisibleForTesting
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ protected boolean doesPackageHaveALauncherActivity(String packageName, UserHandle user) {
+ Intent launcherCategoryIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(packageName);
+ final PackageManager pm = getContext().getPackageManager();
+ return !pm.queryIntentActivitiesAsUser(launcherCategoryIntent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_ACTIVITIES),
+ user)
+ .isEmpty();
+ }
+
+ private boolean isCrossUserQueryAllowed(Uri uri) {
if (isCallerFromSameUser()) {
// Caller is on the same user; query allowed.
return true;
@@ -5621,14 +5801,22 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (!doesCallerHoldInteractAcrossUserPermission()) {
// Cross-user and the caller has no INTERACT_ACROSS_USERS; don't allow query.
// Technically, in a cross-profile sharing case, this would be a valid query.
- // But for now we don't allow it. (We never allowe it and no one complained about it.)
+ // But for now we don't allow it. (We never allowed it and no one complained about it.)
return false;
}
if (isCallerAnotherSelf()) {
- // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), meaning the reuest
+ // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), meaning the request
// is on behalf of a "real" client app.
+
+ if (isContactSharingEnabledForCloneProfile() &&
+ doesCallingProviderUseCurrentUsersContacts()) {
+ // The caller is the other CP2 (which has INTERACT_ACROSS_USERS), from the child
+ // user (of the current user) profile with the property of using parent's contacts
+ // set.
+ return true;
+ }
// Consult the enterprise policy.
- return mEnterprisePolicyGuard.isCrossProfileAllowed(uri);
+ return mEnterprisePolicyGuard.isCrossProfileAllowed(uri, getRealCallerPackageName(uri));
}
return true;
}
@@ -5638,6 +5826,22 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
/**
+ * Returns true if calling contacts provider instance uses current users contacts.
+ * This can happen when the current user is the parent of the calling user and the calling user
+ * has the corresponding user property to use parent's contacts set. Please note that this
+ * cross-profile contact access will only be allowed if the call is redirected from the child
+ * user's CP2.
+ */
+ private boolean doesCallingProviderUseCurrentUsersContacts() {
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
+ UserHandle currentUserHandle = android.os.Process.myUserHandle();
+ boolean isCallerFromSameUser = callingUserHandle.equals(currentUserHandle);
+ return isCallerFromSameUser ||
+ (UserUtils.shouldUseParentsContacts(getContext(), callingUserHandle) &&
+ UserUtils.isParentUser(getContext(), currentUserHandle, callingUserHandle));
+ }
+
+ /**
* Returns true if called by a different user's CP2.
*/
private boolean isCallerAnotherSelf() {
@@ -5655,7 +5859,19 @@ public class ContactsProvider2 extends AbstractContactsProvider
|| context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
}
- private Cursor queryDirectoryIfNecessary(Uri uri, String[] projection, String selection,
+ /**
+ * Returns true if the contact writes are enabled for the current instance of ContactsProvider.
+ * The ContactsProvider instance running in the clone profile should block inserts, updates
+ * and deletes and hence should return false.
+ */
+ @VisibleForTesting
+ protected boolean areContactWritesEnabled() {
+ return !isContactSharingEnabledForCloneProfile() ||
+ !UserUtils.shouldUseParentsContacts(getContext());
+ }
+
+ @VisibleForTesting
+ protected Cursor queryDirectoryIfNecessary(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
String directory = getQueryParameter(uri, ContactsContract.DIRECTORY_PARAM_KEY);
final long directoryId =
@@ -5703,14 +5919,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
final String passedPackage = queryUri.getQueryParameter(
Directory.CALLER_PACKAGE_PARAM_KEY);
if (TextUtils.isEmpty(passedPackage)) {
- Log.wtfStack(TAG,
- "Cross-profile query with no " + Directory.CALLER_PACKAGE_PARAM_KEY);
- return "UNKNOWN";
+ throw new IllegalArgumentException(
+ "Cross-profile query with no " + Directory.CALLER_PACKAGE_PARAM_KEY
+ + " param. Uri: " + queryUri);
}
return passedPackage;
} else {
// Otherwise, just return the real calling package name.
- return getCallingPackage();
+ return getCallingPackageUnchecked();
}
}
@@ -5749,8 +5965,20 @@ public class ContactsProvider2 extends AbstractContactsProvider
if (projection == null) {
projection = getDefaultProjection(uri);
}
+ int galUid = -1;
+ try {
+ galUid = getContext().getPackageManager().getPackageUid(directoryInfo.packageName,
+ PackageManager.MATCH_ALL);
+ } catch (NameNotFoundException e) {
+ // Shouldn't happen, but just in case.
+ Log.w(TAG, "getPackageUid() failed", e);
+ }
+ final LogFields.Builder logBuilder = LogFields.Builder.aLogFields()
+ .setApiType(LogUtils.ApiType.GAL_CALL)
+ .setUriType(sUriMatcher.match(uri))
+ .setUid(galUid);
- Cursor cursor;
+ Cursor cursor = null;
try {
if (VERBOSE_LOGGING) {
Log.v(TAG, "Making directory query: uri=" + directoryUri +
@@ -5768,6 +5996,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
} catch (RuntimeException e) {
Log.w(TAG, "Directory query failed", e);
return null;
+ } finally {
+ LogUtils.log(
+ logBuilder.setResultCount(cursor == null ? 0 : cursor.getCount()).build());
}
if (cursor.getCount() > 0) {
@@ -5804,11 +6035,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
return createEmptyCursor(localUri, projection);
}
// Make sure authority is CP2 not other providers
- if (!ContactsContract.AUTHORITY.equals(localUri.getAuthority())) {
- Log.w(TAG, "Invalid authority: " + localUri.getAuthority());
- throw new IllegalArgumentException(
- "Authority " + localUri.getAuthority() + " is not a valid CP2 authority.");
- }
+ validateAuthority(localUri.getAuthority());
// Add the "user-id @" to the URI, and also pass the caller package name.
final Uri remoteUri = maybeAddUserId(localUri, corpUserId).buildUpon()
.appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY, getCallingPackage())
@@ -5821,6 +6048,69 @@ public class ContactsProvider2 extends AbstractContactsProvider
return cursor;
}
+ private Uri getParentProviderUri(Uri uri, @NonNull UserInfo parentUserInfo) {
+ // Add the "user-id @" of the parent to the URI
+ final Builder remoteUriBuilder =
+ maybeAddUserId(uri, parentUserInfo.getUserHandle().getIdentifier())
+ .buildUpon();
+ // Pass the caller package name query param and build the uri
+ return remoteUriBuilder
+ .appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+ getRealCallerPackageName(uri))
+ .build();
+ }
+
+ protected AssetFileDescriptor openAssetFileThroughParentProvider(Uri uri, String mode)
+ throws FileNotFoundException {
+ final UserInfo parentUserInfo = UserUtils.getProfileParentUser(getContext());
+ if (parentUserInfo == null) {
+ return null;
+ }
+ validateAuthority(uri.getAuthority());
+ final Uri remoteUri = getParentProviderUri(uri, parentUserInfo);
+ return getContext().getContentResolver().openAssetFile(remoteUri, mode, null);
+ }
+
+ /**
+ * A helper function to query parent CP2, should only be called from users that are allowed to
+ * use parents contacts
+ */
+ @VisibleForTesting
+ protected Cursor queryParentProfileContactsProvider(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder,
+ CancellationSignal cancellationSignal) {
+ final UserInfo parentUserInfo = UserUtils.getProfileParentUser(getContext());
+ if (parentUserInfo == null) {
+ return createEmptyCursor(uri, projection);
+ }
+ // Make sure authority is CP2 not other providers
+ validateAuthority(uri.getAuthority());
+ Cursor cursor = queryContactsProviderForUser(uri, projection, selection, selectionArgs,
+ sortOrder, cancellationSignal, parentUserInfo);
+ if (cursor == null) {
+ Log.w(TAG, "null cursor returned from primary CP2");
+ return createEmptyCursor(uri, projection);
+ }
+ return cursor;
+ }
+
+ @VisibleForTesting
+ protected Cursor queryContactsProviderForUser(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal,
+ UserInfo parentUserInfo) {
+ final Uri remoteUri = getParentProviderUri(uri, parentUserInfo);
+ return getContext().getContentResolver().query(remoteUri, projection, selection,
+ selectionArgs, sortOrder, cancellationSignal);
+ }
+
+ private void validateAuthority(String authority) {
+ if (!ContactsContract.AUTHORITY.equals(authority)) {
+ Log.w(TAG, "Invalid authority: " + authority);
+ throw new IllegalArgumentException(
+ "Authority " + authority + " is not a valid CP2 authority.");
+ }
+ }
+
private Cursor addSnippetExtrasToCursor(Uri uri, Cursor cursor) {
// If the cursor doesn't contain a snippet column, don't bother wrapping it.
@@ -5862,7 +6152,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
Directory._ID,
Directory.DIRECTORY_AUTHORITY,
Directory.ACCOUNT_NAME,
- Directory.ACCOUNT_TYPE
+ Directory.ACCOUNT_TYPE,
+ Directory.PACKAGE_NAME
};
public static final int DIRECTORY_ID = 0;
@@ -5888,6 +6179,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
info.authority = cursor.getString(DirectoryQuery.AUTHORITY);
info.accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
info.accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
+ info.packageName =
+ cursor.getString(cursor.getColumnIndex(Directory.PACKAGE_NAME));
mDirectoryCache.put(id, info);
}
} finally {
@@ -6341,8 +6634,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
new Object[] {getMaxDisplayPhotoDim(), getMaxThumbnailDim()});
}
case PHONES_ENTERPRISE: {
- ContactsPermissions.enforceCallingOrSelfPermission(getContext(),
- INTERACT_ACROSS_USERS);
return queryMergedDataPhones(uri, projection, selection, selectionArgs, sortOrder,
cancellationSignal);
}
@@ -6510,6 +6801,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
break;
}
+ case CONTACTS_ENTERPRISE:
+ return queryMergedContacts(projection, selection, selectionArgs, sortOrder,
+ cancellationSignal);
case PHONES_FILTER_ENTERPRISE:
case CALLABLES_FILTER_ENTERPRISE:
case EMAILS_FILTER_ENTERPRISE:
@@ -7273,8 +7567,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
final Cursor[] cursorArray = new Cursor[] {
primaryCursor, rewriteCorpDirectories(corpCursor)
};
- final MergeCursor mergeCursor = new MergeCursor(cursorArray);
- return mergeCursor;
+ return new MergeCursor(cursorArray);
} catch (Throwable th) {
if (primaryCursor != null) {
primaryCursor.close();
@@ -7288,6 +7581,35 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
/**
+ * Handles {@link Contacts#ENTERPRISE_CONTENT_URI}.
+ */
+ private Cursor queryMergedContacts(String[] projection, String selection,
+ String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+ final Uri localUri = Contacts.CONTENT_URI;
+ final Cursor primaryCursor = queryLocal(localUri, projection, selection, selectionArgs,
+ sortOrder, Directory.DEFAULT, cancellationSignal);
+ try {
+ final int managedUserId = UserUtils.getCorpUserId(getContext());
+ if (managedUserId < 0) {
+ // No managed profile or policy not allowed
+ return primaryCursor;
+ }
+ final Cursor managedCursor = queryCorpContacts(localUri, projection, selection,
+ selectionArgs, sortOrder, new String[] {Contacts._ID},
+ Directory.ENTERPRISE_DEFAULT, cancellationSignal);
+ final Cursor[] cursorArray = new Cursor[] {
+ primaryCursor, managedCursor
+ };
+ return new MergeCursor(cursorArray);
+ } catch (Throwable th) {
+ if (primaryCursor != null) {
+ primaryCursor.close();
+ }
+ throw th;
+ }
+ }
+
+ /**
* Handles {@link Phone#ENTERPRISE_CONTENT_URI}.
*/
private Cursor queryMergedDataPhones(Uri uri, String[] projection, String selection,
@@ -7310,8 +7632,6 @@ public class ContactsProvider2 extends AbstractContactsProvider
final Cursor primaryCursor = queryLocal(localUri, projection, selection, selectionArgs,
sortOrder, directoryId, null);
try {
- // PHONES_ENTERPRISE should not be guarded by EnterprisePolicyGuard as Bluetooth app is
- // responsible to guard it.
final int corpUserId = UserUtils.getCorpUserId(getContext());
if (corpUserId < 0) {
// No Corp user or policy not allowed
@@ -7321,21 +7641,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
final Cursor managedCursor = queryCorpContacts(localUri, projection, selection,
selectionArgs, sortOrder, new String[] {RawContacts.CONTACT_ID}, null,
cancellationSignal);
- if (managedCursor == null) {
- // No corp results. Just return the local result.
- return primaryCursor;
- }
final Cursor[] cursorArray = new Cursor[] {
primaryCursor, managedCursor
};
- // Sort order is not supported yet, will be fixed in M when we have
- // merged provider
- // MergeCursor will copy all the contacts from two cursors, which may
- // cause OOM if there's a lot of contacts. But it's only used by
- // Bluetooth, and Bluetooth will loop through the Cursor and put all
- // content in ArrayList anyway, so we ignore OOM issue here for now
- final MergeCursor mergeCursor = new MergeCursor(cursorArray);
- return mergeCursor;
+ return new MergeCursor(cursorArray);
} catch (Throwable th) {
if (primaryCursor != null) {
primaryCursor.close();
@@ -8702,13 +9011,25 @@ public class ContactsProvider2 extends AbstractContactsProvider
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
boolean success = false;
try {
+ if (!mode.equals("r") && !areContactWritesEnabled()) {
+ Log.w(TAG, "Blocked openAssetFile with uri [" + uri + "]. Contact writes not "
+ + "enabled for the user");
+ return null;
+ }
if (!isDirectoryParamValid(uri)){
return null;
}
- if (!queryAllowedByEnterprisePolicy(uri)) {
+ if (!isCrossUserQueryAllowed(uri)) {
return null;
}
waitForAccess(mode.equals("r") ? mReadAccessLatch : mWriteAccessLatch);
+
+ // Redirect reads to parent provider if the corresponding user property is set and app
+ // is allow-listed to access parent's contacts
+ if (mode.equals("r") && shouldRedirectQueryToParentProvider()) {
+ return openAssetFileThroughParentProvider(uri, mode);
+ }
+
final AssetFileDescriptor ret;
if (mapsToProfileDb(uri)) {
switchToProfileMode();
@@ -9022,6 +9343,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
builder.encodedPath(uri.getEncodedPath());
builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
String.valueOf(directoryId - Directory.ENTERPRISE_DIRECTORY_ID_BASE));
+ builder.appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+ getRealCallerPackageName(uri));
addQueryParametersFromUri(builder, uri, MODIFIED_KEY_SET_FOR_ENTERPRISE_FILTER);
// If work profile is not available, it will throw FileNotFoundException
@@ -9075,12 +9398,14 @@ public class ContactsProvider2 extends AbstractContactsProvider
throw new FileNotFoundException(uri.toString());
}
// Convert the URI into:
- // content://USER@com.android.contacts/contacts_corp/ID/{photo,display_photo}
+ // content://USER@com.android.contacts/contacts/ID/{photo,display_photo}
// If work profile is not available, it will throw FileNotFoundException
final Uri corpUri = maybeAddUserId(
ContentUris.appendId(Contacts.CONTENT_URI.buildUpon(), contactId)
.appendPath(displayPhoto ?
Contacts.Photo.DISPLAY_PHOTO : Contacts.Photo.CONTENT_DIRECTORY)
+ .appendQueryParameter(Directory.CALLER_PACKAGE_PARAM_KEY,
+ getRealCallerPackageName(uri))
.build(), corpUserId);
// TODO Make sure it doesn't leak any FDs.
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
index 03b96a3a..2806cf34 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNickname.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
@@ -35,9 +35,14 @@ public class DataRowHandlerForNickname extends DataRowHandlerForCommonDataKind {
Nickname.LABEL);
}
+ private void applySimpleFieldMaxSize(ContentValues cv) {
+ applySimpleFieldMaxSize(cv, Nickname.NAME);
+ }
+
@Override
public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
ContentValues values) {
+ applySimpleFieldMaxSize(values);
String nickname = values.getAsString(Nickname.NAME);
long dataId = super.insert(db, txContext, rawContactId, values);
@@ -53,6 +58,7 @@ public class DataRowHandlerForNickname extends DataRowHandlerForCommonDataKind {
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
Cursor c, boolean callerIsSyncAdapter) {
+ applySimpleFieldMaxSize(values);
long dataId = c.getLong(DataUpdateQuery._ID);
long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
index 66a3b1bd..74f22595 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
@@ -22,6 +22,7 @@ import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.Data;
+
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
import com.android.providers.contacts.aggregation.AbstractContactAggregator;
@@ -37,9 +38,15 @@ public class DataRowHandlerForOrganization extends DataRowHandlerForCommonDataKi
Organization.CONTENT_ITEM_TYPE, Organization.TYPE, Organization.LABEL);
}
+ private void applySimpleFieldMaxSize(ContentValues cv) {
+ applySimpleFieldMaxSize(cv, Organization.COMPANY);
+ applySimpleFieldMaxSize(cv, Organization.TITLE);
+ }
+
@Override
public long insert(SQLiteDatabase db, TransactionContext txContext, long rawContactId,
ContentValues values) {
+ applySimpleFieldMaxSize(values);
String company = values.getAsString(Organization.COMPANY);
String title = values.getAsString(Organization.TITLE);
@@ -52,6 +59,7 @@ public class DataRowHandlerForOrganization extends DataRowHandlerForCommonDataKi
@Override
public boolean update(SQLiteDatabase db, TransactionContext txContext, ContentValues values,
Cursor c, boolean callerIsSyncAdapter) {
+ applySimpleFieldMaxSize(values);
if (!super.update(db, txContext, values, c, callerIsSyncAdapter)) {
return false;
}
diff --git a/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java b/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java
index 46841050..12a03053 100644
--- a/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java
+++ b/src/com/android/providers/contacts/enterprise/EnterprisePolicyGuard.java
@@ -19,8 +19,10 @@ package com.android.providers.contacts.enterprise;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Directory;
import android.provider.Settings;
@@ -29,8 +31,11 @@ import android.util.Log;
import com.android.providers.contacts.ContactsProvider2;
import com.android.providers.contacts.ProfileAwareUriMatcher;
import com.android.providers.contacts.util.UserUtils;
+
import com.google.common.annotations.VisibleForTesting;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH;
/**
@@ -44,22 +49,25 @@ public class EnterprisePolicyGuard {
final private Context mContext;
final private DevicePolicyManager mDpm;
+ final private PackageManager mPm;
public EnterprisePolicyGuard(Context context) {
mContext = context;
mDpm = context.getSystemService(DevicePolicyManager.class);
+ mPm = context.getPackageManager();
}
/**
* Check if cross profile query is allowed for the given uri
*
* @param uri Uri that we want to check.
+ * @param callingPackage Name of the client package that called CP2 in the other profile
* @return True if cross profile query is allowed for this uri
*/
- public boolean isCrossProfileAllowed(@NonNull Uri uri) {
+ public boolean isCrossProfileAllowed(@NonNull Uri uri, @NonNull String callingPackage) {
final int uriCode = sUriMatcher.match(uri);
final UserHandle currentHandle = new UserHandle(UserUtils.getCurrentUserHandle(mContext));
- if (uriCode == -1 || currentHandle == null) {
+ if (uriCode == -1) {
return false;
}
@@ -67,11 +75,14 @@ public class EnterprisePolicyGuard {
return true;
}
- final boolean isCallerIdEnabled = !mDpm.getCrossProfileCallerIdDisabled(currentHandle);
+ final boolean isCallerIdEnabled =
+ mDpm.hasManagedProfileCallerIdAccess(currentHandle, callingPackage);
final boolean isContactsSearchPolicyEnabled =
- !mDpm.getCrossProfileContactsSearchDisabled(currentHandle);
+ mDpm.hasManagedProfileContactsAccess(currentHandle, callingPackage);
final boolean isBluetoothContactSharingEnabled =
!mDpm.getBluetoothContactSharingDisabled(currentHandle);
+ final boolean isManagedProfileEnabled = !UserUtils.getUserManager(mContext)
+ .isQuietModeEnabled(new UserHandle(UserUtils.getCorpUserId(mContext)));
final boolean isContactRemoteSearchUserEnabled = isContactRemoteSearchUserSettingEnabled();
final String directory = uri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
@@ -81,24 +92,34 @@ public class EnterprisePolicyGuard {
Log.v(TAG, "isContactsSearchPolicyEnabled: " + isContactsSearchPolicyEnabled);
Log.v(TAG, "isBluetoothContactSharingEnabled: " + isBluetoothContactSharingEnabled);
Log.v(TAG, "isContactRemoteSearchUserEnabled: " + isContactRemoteSearchUserEnabled);
+ Log.v(TAG, "isManagedProfileEnabled: " + isManagedProfileEnabled);
}
// If it is a remote directory, it is allowed only when
// (i) The uri supports directory
// (ii) User enables it in settings
+ // (iii) The managed profile is enabled
if (directory != null) {
final long directoryId = Long.parseLong(directory);
if (Directory.isRemoteDirectoryId(directoryId)
&& !(isCrossProfileDirectorySupported(uri)
- && isContactRemoteSearchUserEnabled)) {
+ && isContactRemoteSearchUserEnabled
+ && isManagedProfileEnabled)) {
return false;
}
}
+ final boolean isAllowedByCallerIdPolicy = isCallerIdGuarded(uriCode) && isCallerIdEnabled;
+ final boolean isAllowedByContactSearchPolicy =
+ isContactsSearchGuarded(uriCode) && isContactsSearchPolicyEnabled;
+ final boolean isAllowedByBluetoothSharingPolicy =
+ isBluetoothContactSharing(uriCode) && isBluetoothContactSharingEnabled
+ // Only allow apps with INTERACT_ACROSS_USERS to access the bluetooth APIs
+ && mPm.checkPermission(INTERACT_ACROSS_USERS, callingPackage)
+ == PERMISSION_GRANTED;
// If either guard policy allows access, return true.
- return (isCallerIdGuarded(uriCode) && isCallerIdEnabled)
- || (isContactsSearchGuarded(uriCode) && isContactsSearchPolicyEnabled)
- || (isBluetoothContactSharing(uriCode) && isBluetoothContactSharingEnabled);
+ return isAllowedByCallerIdPolicy || isAllowedByContactSearchPolicy
+ || isAllowedByBluetoothSharingPolicy;
}
private boolean isUriWhitelisted(int uriCode) {
@@ -148,7 +169,9 @@ public class EnterprisePolicyGuard {
switch (uriCode) {
case ContactsProvider2.PHONE_LOOKUP_ENTERPRISE:
case ContactsProvider2.EMAILS_LOOKUP_ENTERPRISE:
+ case ContactsProvider2.CONTACTS_ENTERPRISE:
case ContactsProvider2.CONTACTS_FILTER_ENTERPRISE:
+ case ContactsProvider2.PHONES_ENTERPRISE:
case ContactsProvider2.PHONES_FILTER_ENTERPRISE:
case ContactsProvider2.CALLABLES_FILTER_ENTERPRISE:
case ContactsProvider2.EMAILS_FILTER_ENTERPRISE:
@@ -180,8 +203,10 @@ public class EnterprisePolicyGuard {
switch(uriCode) {
case ContactsProvider2.DIRECTORIES:
case ContactsProvider2.DIRECTORIES_ID:
+ case ContactsProvider2.CONTACTS:
case ContactsProvider2.CONTACTS_FILTER:
case ContactsProvider2.CALLABLES_FILTER:
+ case ContactsProvider2.PHONES:
case ContactsProvider2.PHONES_FILTER:
case ContactsProvider2.EMAILS_FILTER:
case ContactsProvider2.CONTACTS_ID_PHOTO:
@@ -208,4 +233,5 @@ public class EnterprisePolicyGuard {
mContext.getContentResolver(),
MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, 0) == 1;
}
+
}
diff --git a/src/com/android/providers/contacts/util/LogFields.java b/src/com/android/providers/contacts/util/LogFields.java
index fc05c847..1672d3db 100644
--- a/src/com/android/providers/contacts/util/LogFields.java
+++ b/src/com/android/providers/contacts/util/LogFields.java
@@ -37,6 +37,8 @@ public final class LogFields {
private int resultCount;
+ private int uid;
+
public LogFields(
int apiType, int uriType, int taskType, boolean callerIsSyncAdapter, long startNanos) {
this.apiType = apiType;
@@ -78,6 +80,10 @@ public final class LogFields {
return resultCount;
}
+ public int getUid() {
+ return uid;
+ }
+
public static final class Builder {
private int apiType;
private int uriType;
@@ -88,6 +94,8 @@ public final class LogFields {
private Uri resultUri;
private int resultCount;
+ private int uid;
+
private Builder() {
}
@@ -135,12 +143,18 @@ public final class LogFields {
return this;
}
+ public Builder setUid(int uid) {
+ this.uid = uid;
+ return this;
+ }
+
public LogFields build() {
LogFields logFields =
new LogFields(apiType, uriType, taskType, callerIsSyncAdapter, startNanos);
logFields.resultCount = this.resultCount;
logFields.exception = this.exception;
logFields.resultUri = this.resultUri;
+ logFields.uid = this.uid;
return logFields;
}
}
diff --git a/src/com/android/providers/contacts/util/LogUtils.java b/src/com/android/providers/contacts/util/LogUtils.java
index 23e2b140..41409645 100644
--- a/src/com/android/providers/contacts/util/LogUtils.java
+++ b/src/com/android/providers/contacts/util/LogUtils.java
@@ -37,6 +37,8 @@ public class LogUtils {
int INSERT = 2;
int UPDATE = 3;
int DELETE = 4;
+ int CALL = 5;
+ int GAL_CALL = 6;
}
// Keep in sync with ContactsProviderStatus#TaskType in
@@ -54,7 +56,6 @@ public class LogUtils {
private static final int STATSD_LOG_ATOM_ID = 301;
-
// The write methods must be called in the same order as the order of fields in the
// atom (frameworks/proto_logging/stats/atoms.proto) definition.
public static void log(LogFields logFields) {
@@ -67,6 +68,8 @@ public class LogUtils {
.writeInt(logFields.getResultCount())
.writeLong(getLatencyMicros(logFields.getStartNanos()))
.writeInt(logFields.getTaskType())
+ .writeInt(0) // Not used yet.
+ .writeInt(logFields.getUid())
.usePooledBuffer()
.build());
}
@@ -92,5 +95,3 @@ public class LogUtils {
return (SystemClock.elapsedRealtimeNanos() - startNanos) / 1000;
}
}
-
-
diff --git a/src/com/android/providers/contacts/util/UserUtils.java b/src/com/android/providers/contacts/util/UserUtils.java
index 31ea41aa..effe8abe 100644
--- a/src/com/android/providers/contacts/util/UserUtils.java
+++ b/src/com/android/providers/contacts/util/UserUtils.java
@@ -17,9 +17,12 @@ package com.android.providers.contacts.util;
import com.android.providers.contacts.ContactsProvider2;
+import android.annotation.SuppressLint;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -78,4 +81,50 @@ public final class UserUtils {
final UserInfo ui = getCorpUserInfo(context);
return ui == null ? -1 : ui.id;
}
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public static boolean shouldUseParentsContacts(Context context) {
+ try {
+ final UserManager userManager = getUserManager(context);
+ final UserProperties userProperties = userManager.getUserProperties(context.getUser());
+ return userProperties.getUseParentsContacts();
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+ + context.getUser());
+ return false;
+ }
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public static boolean shouldUseParentsContacts(Context context, UserHandle userHandle) {
+ try {
+ final UserManager userManager = getUserManager(context);
+ final UserProperties userProperties = userManager.getUserProperties(userHandle);
+ return userProperties.getUseParentsContacts();
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+ + userHandle);
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the input profile user is the parent of the other user
+ * @return True if user1 is the parent profile of user2, false otherwise
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public static boolean isParentUser(Context context, UserHandle user1, UserHandle user2) {
+ if (user1 == null || user2 == null) return false;
+ final UserManager userManager = getUserManager(context);
+ UserInfo parentUserInfo = userManager.getProfileParent(user2.getIdentifier());
+ return parentUserInfo != null
+ && parentUserInfo.getUserHandle() != null
+ && parentUserInfo.getUserHandle().equals(user1);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public static UserInfo getProfileParentUser(Context context) {
+ final UserManager userManager = getUserManager(context);
+ return userManager.getProfileParent(context.getUserId());
+ }
}