Merge "Offer to cache ringtones in system DE storage." into nyc-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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