Offer to cache ringtones in system DE storage.
Ringtones often live on shared media, which is now encrypted with CE keys and not available until after the user is unlocked. To improve the user experience while locked, cache the default ringtone, notification sound, and alarm sound in a DE storage area. Bug: 26730753 Change-Id: Ie6ad7790af4c87dd25759df3ed017e3b91a2fb87
This commit is contained in:
@@ -21187,6 +21187,7 @@ package android.media {
|
||||
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
|
||||
@@ -22687,6 +22687,7 @@ package android.media {
|
||||
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
|
||||
@@ -21196,6 +21196,7 @@ package android.media {
|
||||
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
|
||||
method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
method public void setDataSource(android.media.MediaDataSource) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
|
||||
|
||||
@@ -2780,6 +2780,11 @@ public final class Settings {
|
||||
*/
|
||||
public static final Uri DEFAULT_RINGTONE_URI = getUriFor(RINGTONE);
|
||||
|
||||
/** {@hide} */
|
||||
public static final String RINGTONE_CACHE = "ringtone_cache";
|
||||
/** {@hide} */
|
||||
public static final Uri RINGTONE_CACHE_URI = getUriFor(RINGTONE_CACHE);
|
||||
|
||||
/**
|
||||
* Persistent store for the system-wide default notification sound.
|
||||
*
|
||||
@@ -2798,6 +2803,11 @@ public final class Settings {
|
||||
*/
|
||||
public static final Uri DEFAULT_NOTIFICATION_URI = getUriFor(NOTIFICATION_SOUND);
|
||||
|
||||
/** {@hide} */
|
||||
public static final String NOTIFICATION_SOUND_CACHE = "notification_sound_cache";
|
||||
/** {@hide} */
|
||||
public static final Uri NOTIFICATION_SOUND_CACHE_URI = getUriFor(NOTIFICATION_SOUND_CACHE);
|
||||
|
||||
/**
|
||||
* Persistent store for the system-wide default alarm alert.
|
||||
*
|
||||
@@ -2816,6 +2826,11 @@ public final class Settings {
|
||||
*/
|
||||
public static final Uri DEFAULT_ALARM_ALERT_URI = getUriFor(ALARM_ALERT);
|
||||
|
||||
/** {@hide} */
|
||||
public static final String ALARM_ALERT_CACHE = "alarm_alert_cache";
|
||||
/** {@hide} */
|
||||
public static final Uri ALARM_ALERT_CACHE_URI = getUriFor(ALARM_ALERT_CACHE);
|
||||
|
||||
/**
|
||||
* Persistent store for the system default media button event receiver.
|
||||
*
|
||||
|
||||
@@ -57,6 +57,7 @@ import android.media.SubtitleTrack.RenderingWidget;
|
||||
import android.media.SyncParams;
|
||||
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import libcore.io.IoBridge;
|
||||
import libcore.io.Libcore;
|
||||
@@ -964,8 +965,8 @@ public class MediaPlayer implements SubtitleController.Listener
|
||||
* @param uri the Content URI of the data you want to play
|
||||
* @throws IllegalStateException if it is called in an invalid state
|
||||
*/
|
||||
public void setDataSource(Context context, Uri uri)
|
||||
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
|
||||
public void setDataSource(@NonNull Context context, @NonNull Uri uri)
|
||||
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
|
||||
setDataSource(context, uri, null);
|
||||
}
|
||||
|
||||
@@ -981,47 +982,46 @@ public class MediaPlayer implements SubtitleController.Listener
|
||||
* to disallow or allow cross domain redirection.
|
||||
* @throws IllegalStateException if it is called in an invalid state
|
||||
*/
|
||||
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
|
||||
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
|
||||
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
|
||||
@Nullable Map<String, String> headers) throws IOException, IllegalArgumentException,
|
||||
SecurityException, IllegalStateException {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final String scheme = uri.getScheme();
|
||||
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
|
||||
setDataSource(uri.getPath());
|
||||
return;
|
||||
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|
||||
&& Settings.AUTHORITY.equals(uri.getAuthority())) {
|
||||
// Redirect ringtones to go directly to underlying provider
|
||||
uri = RingtoneManager.getActualDefaultRingtoneUri(context,
|
||||
RingtoneManager.getDefaultType(uri));
|
||||
if (uri == null) {
|
||||
throw new FileNotFoundException("Failed to resolve default ringtone");
|
||||
}
|
||||
}
|
||||
|
||||
AssetFileDescriptor fd = null;
|
||||
try {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
fd = resolver.openAssetFileDescriptor(uri, "r");
|
||||
if (fd == null) {
|
||||
// Try cached ringtone first since the actual provider may not be
|
||||
// encryption aware, or it may be stored on CE media storage
|
||||
final int type = RingtoneManager.getDefaultType(uri);
|
||||
final Uri cacheUri = RingtoneManager.getCacheForType(type);
|
||||
final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
|
||||
if (attemptDataSource(resolver, cacheUri)) {
|
||||
return;
|
||||
} else if (attemptDataSource(resolver, actualUri)) {
|
||||
return;
|
||||
}
|
||||
// Note: using getDeclaredLength so that our behavior is the same
|
||||
// as previous versions when the content provider is returning
|
||||
// a full file.
|
||||
if (fd.getDeclaredLength() < 0) {
|
||||
setDataSource(fd.getFileDescriptor());
|
||||
} else {
|
||||
setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
|
||||
setDataSource(uri.toString(), headers);
|
||||
}
|
||||
return;
|
||||
} catch (SecurityException | IOException ex) {
|
||||
Log.w(TAG, "Couldn't open file on client side; trying server side: " + ex);
|
||||
} finally {
|
||||
if (fd != null) {
|
||||
fd.close();
|
||||
} else {
|
||||
// Try requested Uri locally first, or fallback to media server
|
||||
if (attemptDataSource(resolver, uri)) {
|
||||
return;
|
||||
} else {
|
||||
setDataSource(uri.toString(), headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDataSource(uri.toString(), headers);
|
||||
private boolean attemptDataSource(ContentResolver resolver, Uri uri) {
|
||||
try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
|
||||
setDataSource(afd);
|
||||
return true;
|
||||
} catch (NullPointerException | SecurityException | IOException ex) {
|
||||
Log.w(TAG, "Couldn't open " + uri + ": " + ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1101,6 +1101,26 @@ public class MediaPlayer implements SubtitleController.Listener
|
||||
IBinder httpServiceBinder, String path, String[] keys, String[] values)
|
||||
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* Sets the data source (AssetFileDescriptor) to use. It is the caller's
|
||||
* responsibility to close the file descriptor. It is safe to do so as soon
|
||||
* as this call returns.
|
||||
*
|
||||
* @param afd the AssetFileDescriptor for the file you want to play
|
||||
*/
|
||||
public void setDataSource(@NonNull AssetFileDescriptor afd)
|
||||
throws IOException, IllegalArgumentException, IllegalStateException {
|
||||
Preconditions.checkNotNull(afd);
|
||||
// Note: using getDeclaredLength so that our behavior is the same
|
||||
// as previous versions when the content provider is returning
|
||||
// a full file.
|
||||
if (afd.getDeclaredLength() < 0) {
|
||||
setDataSource(afd.getFileDescriptor());
|
||||
} else {
|
||||
setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data source (FileDescriptor) to use. It is the caller's responsibility
|
||||
* to close the file descriptor. It is safe to do so as soon as this call returns.
|
||||
|
||||
@@ -18,9 +18,12 @@ package android.media;
|
||||
|
||||
import com.android.internal.database.SortCursor;
|
||||
|
||||
import libcore.io.Streams;
|
||||
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -33,6 +36,9 @@ import android.provider.Settings;
|
||||
import android.provider.Settings.System;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -654,8 +660,19 @@ public class RingtoneManager {
|
||||
if (setting == null) return;
|
||||
Settings.System.putString(context.getContentResolver(), setting,
|
||||
ringtoneUri != null ? ringtoneUri.toString() : null);
|
||||
|
||||
// Stream selected ringtone into cache so it's available for playback
|
||||
// when CE storage is still locked
|
||||
final ContentResolver cr = context.getContentResolver();
|
||||
final Uri cacheUri = getCacheForType(type);
|
||||
try (InputStream in = cr.openInputStream(ringtoneUri);
|
||||
OutputStream out = cr.openOutputStream(cacheUri)) {
|
||||
Streams.copy(in, out);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to cache ringtone: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String getSettingForType(int type) {
|
||||
if ((type & TYPE_RINGTONE) != 0) {
|
||||
return Settings.System.RINGTONE;
|
||||
@@ -667,7 +684,20 @@ public class RingtoneManager {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** {@hide} */
|
||||
public static Uri getCacheForType(int type) {
|
||||
if ((type & TYPE_RINGTONE) != 0) {
|
||||
return Settings.System.RINGTONE_CACHE_URI;
|
||||
} else if ((type & TYPE_NOTIFICATION) != 0) {
|
||||
return Settings.System.NOTIFICATION_SOUND_CACHE_URI;
|
||||
} else if ((type & TYPE_ALARM) != 0) {
|
||||
return Settings.System.ALARM_ALERT_CACHE_URI;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Uri} is one of the default ringtones.
|
||||
*
|
||||
|
||||
@@ -49,6 +49,7 @@ import android.os.Message;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SELinux;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
@@ -457,8 +458,28 @@ public class SettingsProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
throw new FileNotFoundException("Direct file access no longer supported; "
|
||||
+ "ringtone playback is available through android.media.Ringtone");
|
||||
final String cacheName;
|
||||
if (Settings.System.RINGTONE_CACHE_URI.equals(uri)) {
|
||||
cacheName = Settings.System.RINGTONE_CACHE;
|
||||
} else if (Settings.System.NOTIFICATION_SOUND_CACHE_URI.equals(uri)) {
|
||||
cacheName = Settings.System.NOTIFICATION_SOUND_CACHE;
|
||||
} else if (Settings.System.ALARM_ALERT_CACHE_URI.equals(uri)) {
|
||||
cacheName = Settings.System.ALARM_ALERT_CACHE;
|
||||
} else {
|
||||
throw new FileNotFoundException("Direct file access no longer supported; "
|
||||
+ "ringtone playback is available through android.media.Ringtone");
|
||||
}
|
||||
|
||||
final File cacheFile = new File(
|
||||
getRingtoneCacheDir(UserHandle.getCallingUserId()), cacheName);
|
||||
return ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
private File getRingtoneCacheDir(int userId) {
|
||||
final File cacheDir = new File(Environment.getDataSystemDeDirectory(userId), "ringtones");
|
||||
cacheDir.mkdir();
|
||||
SELinux.restorecon(cacheDir);
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -901,6 +922,21 @@ public class SettingsProvider extends ContentProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invalidate any relevant cache files
|
||||
String cacheName = null;
|
||||
if (Settings.System.RINGTONE.equals(name)) {
|
||||
cacheName = Settings.System.RINGTONE_CACHE;
|
||||
} else if (Settings.System.NOTIFICATION_SOUND.equals(name)) {
|
||||
cacheName = Settings.System.NOTIFICATION_SOUND_CACHE;
|
||||
} else if (Settings.System.ALARM_ALERT.equals(name)) {
|
||||
cacheName = Settings.System.ALARM_ALERT_CACHE;
|
||||
}
|
||||
if (cacheName != null) {
|
||||
final File cacheFile = new File(
|
||||
getRingtoneCacheDir(UserHandle.getCallingUserId()), cacheName);
|
||||
cacheFile.delete();
|
||||
}
|
||||
|
||||
// Mutate the value.
|
||||
synchronized (mLock) {
|
||||
switch (operation) {
|
||||
|
||||
Reference in New Issue
Block a user