Loading core/java/android/provider/CallLog.java +135 −31 Original line number Original line Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.text.TextUtils; import android.util.Log; import com.android.internal.telephony.CallerInfo; import com.android.internal.telephony.CallerInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants; Loading @@ -49,6 +50,7 @@ import java.util.List; */ */ public class CallLog { public class CallLog { private static final String LOG_TAG = "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"; public static final String AUTHORITY = "call_log"; Loading @@ -58,6 +60,17 @@ public class CallLog { public static final Uri CONTENT_URI = public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); 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. * * <p>See the comment in {@link Calls#addCall} for the details. * * @hide */ public static final String SHADOW_AUTHORITY = "call_log_shadow"; /** /** * Contains the recent calls. * Contains the recent calls. */ */ Loading @@ -68,6 +81,10 @@ public class CallLog { public static final Uri CONTENT_URI = public static final Uri CONTENT_URI = Uri.parse("content://call_log/calls"); 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 * The content:// style URL for filtering this table on phone numbers */ */ Loading Loading @@ -458,8 +475,10 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage) { long start, int duration, Long dataUsage) { return addCall(ci, context, number, "", presentation, callType, features, accountHandle, return addCall(ci, context, number, /* postDialDigits =*/ "", presentation, start, duration, dataUsage, false, null, false); callType, features, accountHandle, start, duration, dataUsage, /* addForAllUsers =*/ false, /* userToBeInsertedTo =*/ null, /* is_read =*/ false); } } Loading Loading @@ -495,7 +514,7 @@ public class CallLog { boolean addForAllUsers, UserHandle userToBeInsertedTo) { boolean addForAllUsers, UserHandle userToBeInsertedTo) { return addCall(ci, context, number, postDialDigits, presentation, callType, features, return addCall(ci, context, number, postDialDigits, presentation, callType, features, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, false); /* is_read =*/ false); } } /** /** Loading Loading @@ -526,13 +545,18 @@ public class CallLog { * Used for call log restore of missed calls. * 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 * @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} * {@hide} */ */ public static Uri addCall(CallerInfo ci, Context context, String number, public static Uri addCall(CallerInfo ci, Context context, String number, String postDialDigits, int presentation, int callType, int features, String postDialDigits, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) { 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(); final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; int numberPresentation = PRESENTATION_ALLOWED; Loading Loading @@ -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; Uri result = null; if (addForAllUsers) { final UserManager userManager = context.getSystemService(UserManager.class); // 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<UserInfo> users = userManager.getUsers(true); final int currentUserId = userManager.getUserHandle(); final int currentUserId = userManager.getUserHandle(); if (addForAllUsers) { // 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<UserInfo> users = userManager.getUsers(true); final int count = users.size(); final int count = users.size(); for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) { final UserInfo user = users.get(i); final UserInfo userInfo = users.get(i); final UserHandle userHandle = user.getUserHandle(); 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) if (userManager.isUserRunning(userHandle) && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, && userManager.isUserUnlocked(userHandle)) { userHandle) final Uri uri = addEntryAndRemoveExpiredEntries(context, userManager, && !user.isManagedProfile()) { userHandle, values); Uri uri = addEntryAndRemoveExpiredEntries(context, if (userId == currentUserId) { ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values); if (user.id == currentUserId) { result = uri; result = uri; } } } } } } } else { } else { Uri uri = CONTENT_URI; // Single-user entry. Just write to that user, assuming it's running. If the if (userToBeInsertedTo != null) { // user is encrypted, we write to the shadow calllog. uri = ContentProvider .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier()); final UserHandle targetUserHandle = userToBeInsertedTo != null } ? userToBeInsertedTo result = addEntryAndRemoveExpiredEntries(context, uri, values); : UserHandle.of(currentUserId); result = addEntryAndRemoveExpiredEntries(context, userManager, targetUserHandle, values); } } return result; 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. * Query the call log database for the last dialed number. * @param context Used to get the content resolver. * @param context Used to get the content resolver. Loading @@ -707,14 +794,31 @@ public class CallLog { } } } } private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri, private static Uri addEntryAndRemoveExpiredEntries(Context context, UserManager userManager, ContentValues values) { UserHandle user, ContentValues values) { final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver(); Uri result = resolver.insert(uri, values); 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 " + resolver.delete(uri, "_id IN " + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER + " LIMIT -1 OFFSET 500)", null); + " LIMIT -1 OFFSET 500)", null); return result; 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) { private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) { Loading Loading
core/java/android/provider/CallLog.java +135 −31 Original line number Original line Diff line number Diff line Loading @@ -38,6 +38,7 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telecom.TelecomManager; import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.text.TextUtils; import android.util.Log; import com.android.internal.telephony.CallerInfo; import com.android.internal.telephony.CallerInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneConstants; Loading @@ -49,6 +50,7 @@ import java.util.List; */ */ public class CallLog { public class CallLog { private static final String LOG_TAG = "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"; public static final String AUTHORITY = "call_log"; Loading @@ -58,6 +60,17 @@ public class CallLog { public static final Uri CONTENT_URI = public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); 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. * * <p>See the comment in {@link Calls#addCall} for the details. * * @hide */ public static final String SHADOW_AUTHORITY = "call_log_shadow"; /** /** * Contains the recent calls. * Contains the recent calls. */ */ Loading @@ -68,6 +81,10 @@ public class CallLog { public static final Uri CONTENT_URI = public static final Uri CONTENT_URI = Uri.parse("content://call_log/calls"); 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 * The content:// style URL for filtering this table on phone numbers */ */ Loading Loading @@ -458,8 +475,10 @@ public class CallLog { public static Uri addCall(CallerInfo ci, Context context, String number, public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage) { long start, int duration, Long dataUsage) { return addCall(ci, context, number, "", presentation, callType, features, accountHandle, return addCall(ci, context, number, /* postDialDigits =*/ "", presentation, start, duration, dataUsage, false, null, false); callType, features, accountHandle, start, duration, dataUsage, /* addForAllUsers =*/ false, /* userToBeInsertedTo =*/ null, /* is_read =*/ false); } } Loading Loading @@ -495,7 +514,7 @@ public class CallLog { boolean addForAllUsers, UserHandle userToBeInsertedTo) { boolean addForAllUsers, UserHandle userToBeInsertedTo) { return addCall(ci, context, number, postDialDigits, presentation, callType, features, return addCall(ci, context, number, postDialDigits, presentation, callType, features, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, accountHandle, start, duration, dataUsage, addForAllUsers, userToBeInsertedTo, false); /* is_read =*/ false); } } /** /** Loading Loading @@ -526,13 +545,18 @@ public class CallLog { * Used for call log restore of missed calls. * 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 * @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} * {@hide} */ */ public static Uri addCall(CallerInfo ci, Context context, String number, public static Uri addCall(CallerInfo ci, Context context, String number, String postDialDigits, int presentation, int callType, int features, String postDialDigits, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean is_read) { 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(); final ContentResolver resolver = context.getContentResolver(); int numberPresentation = PRESENTATION_ALLOWED; int numberPresentation = PRESENTATION_ALLOWED; Loading Loading @@ -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; Uri result = null; if (addForAllUsers) { final UserManager userManager = context.getSystemService(UserManager.class); // 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<UserInfo> users = userManager.getUsers(true); final int currentUserId = userManager.getUserHandle(); final int currentUserId = userManager.getUserHandle(); if (addForAllUsers) { // 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<UserInfo> users = userManager.getUsers(true); final int count = users.size(); final int count = users.size(); for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) { final UserInfo user = users.get(i); final UserInfo userInfo = users.get(i); final UserHandle userHandle = user.getUserHandle(); 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) if (userManager.isUserRunning(userHandle) && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, && userManager.isUserUnlocked(userHandle)) { userHandle) final Uri uri = addEntryAndRemoveExpiredEntries(context, userManager, && !user.isManagedProfile()) { userHandle, values); Uri uri = addEntryAndRemoveExpiredEntries(context, if (userId == currentUserId) { ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values); if (user.id == currentUserId) { result = uri; result = uri; } } } } } } } else { } else { Uri uri = CONTENT_URI; // Single-user entry. Just write to that user, assuming it's running. If the if (userToBeInsertedTo != null) { // user is encrypted, we write to the shadow calllog. uri = ContentProvider .maybeAddUserId(CONTENT_URI, userToBeInsertedTo.getIdentifier()); final UserHandle targetUserHandle = userToBeInsertedTo != null } ? userToBeInsertedTo result = addEntryAndRemoveExpiredEntries(context, uri, values); : UserHandle.of(currentUserId); result = addEntryAndRemoveExpiredEntries(context, userManager, targetUserHandle, values); } } return result; 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. * Query the call log database for the last dialed number. * @param context Used to get the content resolver. * @param context Used to get the content resolver. Loading @@ -707,14 +794,31 @@ public class CallLog { } } } } private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri, private static Uri addEntryAndRemoveExpiredEntries(Context context, UserManager userManager, ContentValues values) { UserHandle user, ContentValues values) { final ContentResolver resolver = context.getContentResolver(); final ContentResolver resolver = context.getContentResolver(); Uri result = resolver.insert(uri, values); 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 " + resolver.delete(uri, "_id IN " + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER + " LIMIT -1 OFFSET 500)", null); + " LIMIT -1 OFFSET 500)", null); return result; 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) { private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) { Loading