From b524741cb653d6a898431d2c3c7d54a543b5e577 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Tue, 12 Jan 2016 12:57:25 -0800 Subject: [PATCH] [FBE] Introduce shadow calllog provider [1/2] Introduce a new provider that's a clone of the calllog provider but is EA. The provider is hidden and requires MANAGE_USERS to access. Bug 26183949 Change-Id: Ie3181a2a68c522b7e513b45218ad06ecd27cda8c --- core/java/android/provider/CallLog.java | 166 ++++++++++++++++++++++++++------ 1 file changed, 135 insertions(+), 31 deletions(-) diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 6a5d857c4a37..e7c4a075cf72 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -38,6 +38,7 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import android.util.Log; import com.android.internal.telephony.CallerInfo; import com.android.internal.telephony.PhoneConstants; @@ -49,6 +50,7 @@ import java.util.List; */ public class CallLog { private static final String LOG_TAG = "CallLog"; + private static final boolean VERBOSE_LOG = false; // DON'T SUBMIT WITH TRUE. public static final String AUTHORITY = "call_log"; @@ -58,6 +60,17 @@ public class CallLog { public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); + + /** + * The "shadow" provider stores calllog when the real calllog provider is encrypted. The + * real provider will alter copy from it when it starts, and remove the entries in the shadow. + * + *

See the comment in {@link Calls#addCall} for the details. + * + * @hide + */ + public static final String SHADOW_AUTHORITY = "call_log_shadow"; + /** * Contains the recent calls. */ @@ -68,6 +81,10 @@ public class CallLog { public static final Uri CONTENT_URI = Uri.parse("content://call_log/calls"); + /** @hide */ + public static final Uri SHADOW_CONTENT_URI = + Uri.parse("content://call_log_shadow/calls"); + /** * The content:// style URL for filtering this table on phone numbers */ @@ -458,8 +475,10 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage) { - return addCall(ci, context, number, "", presentation, callType, features, accountHandle, - start, duration, dataUsage, false, null, false); + return addCall(ci, context, number, /* postDialDigits =*/ "", presentation, + callType, features, accountHandle, + start, duration, dataUsage, /* addForAllUsers =*/ false, + /* userToBeInsertedTo =*/ null, /* is_read =*/ false); } @@ -495,7 +514,7 @@ public class CallLog { boolean addForAllUsers, UserHandle userToBeInsertedTo) { return addCall(ci, context, number, postDialDigits, presentation, callType, features, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, - false); + /* is_read =*/ false); } /** @@ -526,13 +545,18 @@ public class CallLog { * Used for call log restore of missed calls. * * @result The URI of the call log entry belonging to the user that made or received this - * call. + * call. This could be of the shadow provider. Do not return it to non-system apps, + * as they don't have permissions. * {@hide} */ public static Uri addCall(CallerInfo ci, Context context, String number, String postDialDigits, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s", + number, userToBeInsertedTo, addForAllUsers)); + } final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; @@ -647,41 +671,104 @@ public class CallLog { } } + /* + Writing the calllog works in the following way: + - All user entries + - if user-0 is encrypted, insert to user-0's shadow only. + (other users should also be encrypted, so nothing to do for other users.) + - if user-0 is decrypted, insert to user-0's real provider, as well as + all other users that are running and decrypted and should have calllog. + + - Single user entry. + - If the target user is encryted, insert to its shadow. + - Otherwise insert to its real provider. + + When the (real) calllog provider starts, it copies entries that it missed from + elsewhere. + - When user-0's (real) provider starts, it copies from user-0's shadow, and clears + the shadow. + + - When other users (real) providers start, unless it shouldn't have calllog entries, + - Copy from the user's shadow, and clears the shadow. + - Copy from user-0's entries that are FOR_ALL_USERS = 1. (and don't clear it.) + */ + Uri result = null; + final UserManager userManager = context.getSystemService(UserManager.class); + final int currentUserId = userManager.getUserHandle(); + if (addForAllUsers) { - // Insert the entry for all currently running users, in order to trigger any - // ContentObservers currently set on the call log. - final UserManager userManager = (UserManager) context.getSystemService( - Context.USER_SERVICE); - List users = userManager.getUsers(true); - final int currentUserId = userManager.getUserHandle(); + // First, insert to the system user. + final Uri uriForSystem = addEntryAndRemoveExpiredEntries( + context, userManager, UserHandle.SYSTEM, values); + if (uriForSystem == null + || SHADOW_AUTHORITY.equals(uriForSystem.getAuthority())) { + // This means the system user is still encrypted and the entry has inserted + // into the shadow. This means other users are still all encrypted. + // Nothing further to do; just return null. + return null; + } + if (UserHandle.USER_SYSTEM == currentUserId) { + result = uriForSystem; + } + + // Otherwise, insert to all other users that are running and unlocked. + + final List users = userManager.getUsers(true); + final int count = users.size(); for (int i = 0; i < count; i++) { - final UserInfo user = users.get(i); - final UserHandle userHandle = user.getUserHandle(); + final UserInfo userInfo = users.get(i); + final UserHandle userHandle = userInfo.getUserHandle(); + final int userId = userHandle.getIdentifier(); + + if (userHandle.isSystem()) { + // Already written. + continue; + } + + if (!shouldHaveSharedCallLogEntries(context, userManager, userId)) { + // Shouldn't have calllog entries. + continue; + } + + // For other users, we write only when they're running *and* decrypted. + // Other providers will copy from the system user's real provider, when they + // start. if (userManager.isUserRunning(userHandle) - && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, - userHandle) - && !user.isManagedProfile()) { - Uri uri = addEntryAndRemoveExpiredEntries(context, - ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values); - if (user.id == currentUserId) { + && userManager.isUserUnlocked(userHandle)) { + final Uri uri = addEntryAndRemoveExpiredEntries(context, userManager, + userHandle, values); + if (userId == currentUserId) { result = uri; } } } } else { - Uri uri = CONTENT_URI; - if (userToBeInsertedTo != null) { - uri = ContentProvider - .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier()); - } - result = addEntryAndRemoveExpiredEntries(context, uri, values); + // Single-user entry. Just write to that user, assuming it's running. If the + // user is encrypted, we write to the shadow calllog. + + final UserHandle targetUserHandle = userToBeInsertedTo != null + ? userToBeInsertedTo + : UserHandle.of(currentUserId); + result = addEntryAndRemoveExpiredEntries(context, userManager, targetUserHandle, + values); } return result; } + /** @hide */ + public static boolean shouldHaveSharedCallLogEntries(Context context, + UserManager userManager, int userId) { + if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, + UserHandle.of(userId))) { + return false; + } + final UserInfo userInfo = userManager.getUserInfo(userId); + return userInfo != null && !userInfo.isManagedProfile(); + } + /** * Query the call log database for the last dialed number. * @param context Used to get the content resolver. @@ -707,14 +794,31 @@ public class CallLog { } } - private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri, - ContentValues values) { + private static Uri addEntryAndRemoveExpiredEntries(Context context, UserManager userManager, + UserHandle user, ContentValues values) { final ContentResolver resolver = context.getContentResolver(); - Uri result = resolver.insert(uri, values); - resolver.delete(uri, "_id IN " + - "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER - + " LIMIT -1 OFFSET 500)", null); - return result; + + final Uri uri = ContentProvider.maybeAddUserId( + userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI, + user.getIdentifier()); + + if (VERBOSE_LOG) { + Log.v(LOG_TAG, String.format("Inserting to %s", uri)); + } + + try { + final Uri result = resolver.insert(uri, values); + resolver.delete(uri, "_id IN " + + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER + + " LIMIT -1 OFFSET 500)", null); + return result; + } catch (IllegalArgumentException e) { + Log.w(LOG_TAG, "Failed to insert calllog", e); + // Even though we make sure the target user is running and decrypted before calling + // this method, there's a chance that the user just got shut down, in which case + // we'll still get "IllegalArgumentException: Unknown URL content://call_log/calls". + return null; + } } private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) { -- 2.11.0