From dc50b548ff68af057ea7768ad67c17aff1b5f7d8 Mon Sep 17 00:00:00 2001 From: Andre Lago Date: Thu, 25 Aug 2016 15:21:07 +0100 Subject: [PATCH] [RingtoneManager] Option for cursor to list parent sounds Modify RingtoneManager.getCursor so that the cursor returned also retrieves the parent profile's ringtones, adding the parent's id to those ringtones Test: manual (use ringtone picker in associated CL) Bug: 30658854 Change-Id: I3b52b0dc41a5a3ff1a71a89fd8a0d33023021aba --- .../media/ExternalRingtonesCursorWrapper.java | 46 ++++++++ media/java/android/media/RingtoneManager.java | 107 +++++++++++++++--- 2 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 media/java/android/media/ExternalRingtonesCursorWrapper.java diff --git a/media/java/android/media/ExternalRingtonesCursorWrapper.java b/media/java/android/media/ExternalRingtonesCursorWrapper.java new file mode 100644 index 0000000000000..dd4c77a045778 --- /dev/null +++ b/media/java/android/media/ExternalRingtonesCursorWrapper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.content.ContentProvider; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.net.Uri; + +/** + * Cursor that adds the user id to fetched URIs. This is especially useful for {@link getCursor} as + * a managed profile should also list its parent's ringtones + * + * @hide + */ +public class ExternalRingtonesCursorWrapper extends CursorWrapper { + + private int mUserId; + + public ExternalRingtonesCursorWrapper(Cursor cursor, int userId) { + super(cursor); + mUserId = userId; + } + + public String getString(int index) { + String result = super.getString(index); + if (index == RingtoneManager.URI_COLUMN_INDEX) { + result = ContentProvider.maybeAddUserId(Uri.parse(result), mUserId).toString(); + } + return result; + } +} diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 715fe57a81f06..7614999074c62 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -261,6 +261,8 @@ public class RingtoneManager { private boolean mStopPreviousRingtone = true; private Ringtone mPreviousRingtone; + private boolean mIncludeParentRingtones; + /** * Constructs a RingtoneManager. This constructor is recommended as its * constructed instance manages cursor(s). @@ -268,9 +270,24 @@ public class RingtoneManager { * @param activity The activity used to get a managed cursor. */ public RingtoneManager(Activity activity) { + this(activity, /* includeParentRingtones */ false); + } + + /** + * Constructs a RingtoneManager. This constructor is recommended if there's the need to also + * list ringtones from the user's parent. + * + * @param activity The activity used to get a managed cursor. + * @param includeParentRingtones if true, this ringtone manager's cursor will also retrieve + * ringtones from the parent of the user specified in the given activity + * + * @hide + */ + public RingtoneManager(Activity activity, boolean includeParentRingtones) { mActivity = activity; mContext = activity; setType(mType); + mIncludeParentRingtones = includeParentRingtones; } /** @@ -281,9 +298,23 @@ public class RingtoneManager { * @param context The context to used to get a cursor. */ public RingtoneManager(Context context) { + this(context, /* includeParentRingtones */ false); + } + + /** + * Constructs a RingtoneManager. + * + * @param context The context to used to get a cursor. + * @param includeParentRingtones if true, this ringtone manager's cursor will also retrieve + * ringtones from the parent of the user specified in the given context + * + * @hide + */ + public RingtoneManager(Context context, boolean includeParentRingtones) { mActivity = null; mContext = context; setType(mType); + mIncludeParentRingtones = includeParentRingtones; } /** @@ -403,14 +434,38 @@ public class RingtoneManager { if (mCursor != null && mCursor.requery()) { return mCursor; } - - final Cursor internalCursor = getInternalRingtones(); - final Cursor mediaCursor = getMediaRingtones(); - - return mCursor = new SortCursor(new Cursor[] { internalCursor, mediaCursor }, + + ArrayList ringtoneCursors = new ArrayList(); + ringtoneCursors.add(getInternalRingtones()); + ringtoneCursors.add(getMediaRingtones()); + + if (mIncludeParentRingtones) { + Cursor parentRingtonesCursor = getParentProfileRingtones(); + if (parentRingtonesCursor != null) { + ringtoneCursors.add(parentRingtonesCursor); + } + } + + return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]), MediaStore.Audio.Media.DEFAULT_SORT_ORDER); } + private Cursor getParentProfileRingtones() { + final UserManager um = UserManager.get(mContext); + final UserInfo parentInfo = um.getProfileParent(mContext.getUserId()); + if (parentInfo != null && parentInfo.id != mContext.getUserId()) { + final Context parentContext = createPackageContextAsUser(mContext, parentInfo.id); + if (parentContext != null) { + // We don't need to re-add the internal ringtones for the work profile since + // they are the same as the personal profile. We just need the external + // ringtones. + return new ExternalRingtonesCursorWrapper(getMediaRingtones(parentContext), + parentInfo.id); + } + } + return null; + } + /** * Gets a {@link Ringtone} for the ringtone at the given position in the * {@link Cursor}. @@ -559,7 +614,11 @@ public class RingtoneManager { } private Cursor getMediaRingtones() { - if (PackageManager.PERMISSION_GRANTED != mContext.checkPermission( + return getMediaRingtones(mContext); + } + + private Cursor getMediaRingtones(Context context) { + if (PackageManager.PERMISSION_GRANTED != context.checkPermission( android.Manifest.permission.READ_EXTERNAL_STORAGE, Process.myPid(), Process.myUid())) { Log.w(TAG, "No READ_EXTERNAL_STORAGE permission, ignoring ringtones on ext storage"); @@ -573,7 +632,7 @@ public class RingtoneManager { ? query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, constructBooleanTrueWhereClause(mFilterColumns), null, - MediaStore.Audio.Media.DEFAULT_SORT_ORDER) + MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context) : null; } @@ -628,10 +687,19 @@ public class RingtoneManager { String selection, String[] selectionArgs, String sortOrder) { + return query(uri, projection, selection, selectionArgs, sortOrder, mContext); + } + + private Cursor query(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder, + Context context) { if (mActivity != null) { return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder); } else { - return mContext.getContentResolver().query(uri, projection, selection, selectionArgs, + return context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); } } @@ -717,17 +785,14 @@ public class RingtoneManager { UserManager um = UserManager.get(userContext); UserInfo parentInfo = um.getProfileParent(userContext.getUserId()); if (parentInfo != null) { - try { - Context targetContext = userContext.createPackageContextAsUser( - userContext.getPackageName(), 0 /* flags */, UserHandle.of(parentInfo.id)); + final Context targetContext = createPackageContextAsUser(userContext, parentInfo.id); + if (targetContext != null) { for (int ringtoneType : RINGTONE_TYPES) { Uri ringtoneUri = getActualDefaultRingtoneUri(targetContext, ringtoneType); // Add user id of parent so that custom ringtones can be read and played RingtoneManager.setActualDefaultRingtoneUri(userContext, ringtoneType, maybeAddUserId(ringtoneUri, parentInfo.id)); } - } catch (NameNotFoundException e) { - Log.e(TAG, "Unable to create parent context", e); } } } @@ -1113,4 +1178,20 @@ public class RingtoneManager { return mQueue.take(); } } + + /** + * Attempts to create a context for the given user. + * + * @return created context, or null if package does not exist + * @hide + */ + private static Context createPackageContextAsUser(Context context, int userId) { + try { + return context.createPackageContextAsUser(context.getPackageName(), 0 /* flags */, + UserHandle.of(userId)); + } catch (NameNotFoundException e) { + Log.e(TAG, "Unable to create package context", e); + return null; + } + } }