diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rwxr-xr-x | core/java/android/provider/Settings.java | 15 | ||||
| -rw-r--r-- | media/java/android/media/MediaPlayer.java | 82 | ||||
| -rw-r--r-- | media/java/android/media/RingtoneManager.java | 34 | ||||
| -rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java | 40 |
7 files changed, 139 insertions, 35 deletions
diff --git a/api/current.txt b/api/current.txt index c7815fa2a18a..01bdc644acfb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21192,6 +21192,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; diff --git a/api/system-current.txt b/api/system-current.txt index 990f09436b6b..13f3998e50ba 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -22700,6 +22700,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; diff --git a/api/test-current.txt b/api/test-current.txt index 79351ece34ab..71f1c680b0a5 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -21201,6 +21201,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; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 00747885a143..5d78008d6b28 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -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. * diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 26e466e181d9..adf85517c344 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -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; + } } /** @@ -1102,6 +1102,26 @@ public class MediaPlayer implements SubtitleController.Listener 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. * diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 025029e75ec1..06ac11b41c1a 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -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. * diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 57d495f6f936..9842e28ffdba 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -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) { |