Merge "[FBE] Introduce shadow calllog provider [1/2]"
This commit is contained in:
@@ -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.
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
@@ -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<UserInfo> 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<UserInfo> 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) {
|
||||
|
||||
Reference in New Issue
Block a user