diff options
author | Jeff Sharkey <jsharkey@android.com> | 2012-09-25 17:22:27 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2012-09-26 10:49:35 -0700 |
commit | 65c4a2b26cd8776b0927e9b0e07ecf53bd31b627 (patch) | |
tree | 7dbd0b29b66336c738a0c599335ad70bb86ba4a2 | |
parent | 5e21bf934b2a71b595deb9856a2044eea4dbce86 (diff) | |
download | frameworks_base-65c4a2b26cd8776b0927e9b0e07ecf53bd31b627.zip frameworks_base-65c4a2b26cd8776b0927e9b0e07ecf53bd31b627.tar.gz frameworks_base-65c4a2b26cd8776b0927e9b0e07ecf53bd31b627.tar.bz2 |
Multi-user ringtone playback.
Change RingtonePlayer to open content:// Uris based on requesting
UserHandle. Grant SystemUI visibility to all emulated storage so
it can play ringtones for apps without READ_EXTERNAL_STORAGE.
Resolve canonical file:// Uris before passing out of source app,
replacing any /emulated_legacy/-style paths with user-specific
variant so they can be opened by SystemUI. Calling for RemoteViews,
Ringtones, and Notifications.
Bug: 7202982
Change-Id: Ibf0eca8df80c1486711144a7b648f464aadfe099
-rw-r--r-- | core/java/android/app/NotificationManager.java | 6 | ||||
-rw-r--r-- | core/java/android/net/Uri.java | 38 | ||||
-rw-r--r-- | core/java/android/os/UserHandle.aidl | 19 | ||||
-rw-r--r-- | core/java/android/widget/RemoteViews.java | 2 | ||||
-rw-r--r-- | media/java/android/media/IRingtonePlayer.aidl | 3 | ||||
-rw-r--r-- | media/java/android/media/Ringtone.java | 3 | ||||
-rw-r--r-- | packages/SystemUI/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java | 30 | ||||
-rwxr-xr-x | services/java/com/android/server/NotificationManagerService.java | 4 |
9 files changed, 95 insertions, 11 deletions
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index c095280..0acad75 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -124,6 +124,9 @@ public class NotificationManager int[] idOut = new int[1]; INotificationManager service = getService(); String pkg = mContext.getPackageName(); + if (notification.sound != null) { + notification.sound = notification.sound.getCanonicalUri(); + } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut, @@ -143,6 +146,9 @@ public class NotificationManager int[] idOut = new int[1]; INotificationManager service = getService(); String pkg = mContext.getPackageName(); + if (notification.sound != null) { + notification.sound = notification.sound.getCanonicalUri(); + } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut, diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 3b990e3..cc6903d 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -16,10 +16,13 @@ package android.net; +import android.os.Environment; import android.os.Parcel; import android.os.Parcelable; +import android.os.Environment.UserEnvironment; import android.util.Log; import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charsets; @@ -2288,4 +2291,39 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { builder = builder.appendEncodedPath(pathSegment); return builder.build(); } + + /** + * If this {@link Uri} is {@code file://}, then resolve and return its + * canonical path. Also fixes legacy emulated storage paths so they are + * usable across user boundaries. Should always be called from the app + * process before sending elsewhere. + * + * @hide + */ + public Uri getCanonicalUri() { + if ("file".equals(getScheme())) { + final String canonicalPath; + try { + canonicalPath = new File(getPath()).getCanonicalPath(); + } catch (IOException e) { + return this; + } + + if (Environment.isExternalStorageEmulated()) { + final String legacyPath = Environment.getLegacyExternalStorageDirectory() + .toString(); + + // Splice in user-specific path when legacy path is found + if (canonicalPath.startsWith(legacyPath)) { + return Uri.fromFile(new File( + Environment.getExternalStorageDirectory().toString(), + canonicalPath.substring(legacyPath.length() + 1))); + } + } + + return Uri.fromFile(new File(canonicalPath)); + } else { + return this; + } + } } diff --git a/core/java/android/os/UserHandle.aidl b/core/java/android/os/UserHandle.aidl new file mode 100644 index 0000000..4892d32 --- /dev/null +++ b/core/java/android/os/UserHandle.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2012, 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.os; + +parcelable UserHandle; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 90f55bf..4b5dfb8 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2166,6 +2166,8 @@ public class RemoteViews implements Parcelable, Filter { * @param value The value to pass to the method. */ public void setUri(int viewId, String methodName, Uri value) { + // Resolve any filesystem path before sending remotely + value = value.getCanonicalUri(); addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); } diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 44a0333..0872f1d 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -17,6 +17,7 @@ package android.media; import android.net.Uri; +import android.os.UserHandle; /** * @hide @@ -28,6 +29,6 @@ interface IRingtonePlayer { boolean isPlaying(IBinder token); /** Used for Notification sound playback. */ - void playAsync(in Uri uri, boolean looping, int streamType); + void playAsync(in Uri uri, in UserHandle user, boolean looping, int streamType); void stopAsync(); } diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 23f7b55..f190eb9 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -225,8 +225,9 @@ public class Ringtone { mLocalPlayer.start(); } } else if (mAllowRemote) { + final Uri canonicalUri = mUri.getCanonicalUri(); try { - mRemotePlayer.play(mRemoteToken, mUri, mStreamType); + mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType); } catch (RemoteException e) { Log.w(TAG, "Problem playing ringtone: " + e); } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4d241ed..a7294ec 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -5,6 +5,7 @@ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.INJECT_EVENTS" /> diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 3502b62..0c6e59c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -17,6 +17,7 @@ package com.android.systemui.media; import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; import android.media.IAudioService; import android.media.IRingtonePlayer; import android.media.Ringtone; @@ -26,6 +27,7 @@ import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Slog; import com.android.systemui.SystemUI; @@ -70,9 +72,10 @@ public class RingtonePlayer extends SystemUI { private final IBinder mToken; private final Ringtone mRingtone; - public Client(IBinder token, Uri uri, int streamType) { + public Client(IBinder token, Uri uri, UserHandle user, int streamType) { mToken = token; - mRingtone = new Ringtone(mContext, false); + + mRingtone = new Ringtone(getContextForUser(user), false); mRingtone.setStreamType(streamType); mRingtone.setUri(uri); } @@ -90,12 +93,16 @@ public class RingtonePlayer extends SystemUI { private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() { @Override public void play(IBinder token, Uri uri, int streamType) throws RemoteException { - if (LOGD) Slog.d(TAG, "play(token=" + token + ", uri=" + uri + ")"); + if (LOGD) { + Slog.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid=" + + Binder.getCallingUid() + ")"); + } Client client; synchronized (mClients) { client = mClients.get(token); if (client == null) { - client = new Client(token, uri, streamType); + final UserHandle user = Binder.getCallingUserHandle(); + client = new Client(token, uri, user, streamType); token.linkToDeath(client, 0); mClients.put(token, client); } @@ -131,12 +138,13 @@ public class RingtonePlayer extends SystemUI { } @Override - public void playAsync(Uri uri, boolean looping, int streamType) { - if (LOGD) Slog.d(TAG, "playAsync(uri=" + uri + ")"); + public void playAsync(Uri uri, UserHandle user, boolean looping, int streamType) { + if (LOGD) Slog.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")"); if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Async playback only available from system UID."); } - mAsyncPlayer.play(mContext, uri, looping, streamType); + + mAsyncPlayer.play(getContextForUser(user), uri, looping, streamType); } @Override @@ -149,6 +157,14 @@ public class RingtonePlayer extends SystemUI { } }; + private Context getContextForUser(UserHandle user) { + try { + return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Clients:"); diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index bab4f7a..848edf8 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -890,6 +890,7 @@ public class NotificationManagerService extends INotificationManager.Stub userId = ActivityManager.handleIncomingUser(callingPid, callingUid, userId, true, true, "enqueueNotification", pkg); + final UserHandle user = new UserHandle(userId); // Limit the number of notifications that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. @@ -991,7 +992,6 @@ public class NotificationManagerService extends INotificationManager.Stub } if (notification.icon != 0) { - final UserHandle user = new UserHandle(userId); final StatusBarNotification n = new StatusBarNotification( pkg, id, tag, r.uid, r.initialPid, score, notification, user); if (old != null && old.statusBarKey != null) { @@ -1063,7 +1063,7 @@ public class NotificationManagerService extends INotificationManager.Stub try { final IRingtonePlayer player = mAudioService.getRingtonePlayer(); if (player != null) { - player.playAsync(uri, looping, audioStreamType); + player.playAsync(uri, user, looping, audioStreamType); } } catch (RemoteException e) { } finally { |