1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.media; 18 19 import android.app.Notification; 20 import android.media.MediaController2; 21 import android.media.Session2CommandGroup; 22 import android.media.Session2Token; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.ResultReceiver; 26 import android.os.UserHandle; 27 import android.util.Log; 28 import android.view.KeyEvent; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.io.PrintWriter; 33 34 /** 35 * Keeps the record of {@link Session2Token} to help send command to the corresponding session. 36 */ 37 // TODO(jaewan): Do not call service method directly -- introduce listener instead. 38 public class MediaSession2Record extends MediaSessionRecordImpl { 39 private static final String TAG = "MediaSession2Record"; 40 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 41 private final Object mLock = new Object(); 42 43 @GuardedBy("mLock") 44 private final Session2Token mSessionToken; 45 @GuardedBy("mLock") 46 private final HandlerExecutor mHandlerExecutor; 47 @GuardedBy("mLock") 48 private final MediaController2 mController; 49 @GuardedBy("mLock") 50 private final MediaSessionService mService; 51 @GuardedBy("mLock") 52 private boolean mIsConnected; 53 @GuardedBy("mLock") 54 private int mPolicies; 55 @GuardedBy("mLock") 56 private boolean mIsClosed; 57 58 private final int mPid; 59 MediaSession2Record( Session2Token sessionToken, MediaSessionService service, Looper handlerLooper, int pid, int policies)60 public MediaSession2Record( 61 Session2Token sessionToken, 62 MediaSessionService service, 63 Looper handlerLooper, 64 int pid, 65 int policies) { 66 // The lock is required to prevent `Controller2Callback` from using partially initialized 67 // `MediaSession2Record.this`. 68 synchronized (mLock) { 69 mSessionToken = sessionToken; 70 mService = service; 71 mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper)); 72 mController = new MediaController2.Builder(service.getContext(), sessionToken) 73 .setControllerCallback(mHandlerExecutor, new Controller2Callback()) 74 .build(); 75 mPid = pid; 76 mPolicies = policies; 77 } 78 } 79 80 @Override getPackageName()81 public String getPackageName() { 82 return mSessionToken.getPackageName(); 83 } 84 getSession2Token()85 public Session2Token getSession2Token() { 86 return mSessionToken; 87 } 88 89 @Override getUid()90 public int getUid() { 91 return mSessionToken.getUid(); 92 } 93 94 @Override getUserId()95 public int getUserId() { 96 return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier(); 97 } 98 99 @Override hasLinkedNotificationSupport()100 public boolean hasLinkedNotificationSupport() { 101 return false; 102 } 103 104 @Override isSystemPriority()105 public boolean isSystemPriority() { 106 // System priority session is currently only allowed for telephony, so it's OK to stick to 107 // the media1 API at this moment. 108 return false; 109 } 110 111 @Override adjustVolume(String packageName, String opPackageName, int pid, int uid, boolean asSystemService, int direction, int flags, boolean useSuggested)112 public void adjustVolume(String packageName, String opPackageName, int pid, int uid, 113 boolean asSystemService, int direction, int flags, boolean useSuggested) { 114 // TODO(jaewan): Add API to adjust volume. 115 } 116 117 @Override isActive()118 public boolean isActive() { 119 synchronized (mLock) { 120 return mIsConnected; 121 } 122 } 123 124 @Override checkPlaybackActiveState(boolean expected)125 public boolean checkPlaybackActiveState(boolean expected) { 126 synchronized (mLock) { 127 return (mIsConnected && mController.isPlaybackActive()) == expected; 128 } 129 } 130 131 @Override isPlaybackTypeLocal()132 public boolean isPlaybackTypeLocal() { 133 // TODO(jaewan): Implement -- need API to know whether the playback is remote or local. 134 return true; 135 } 136 137 @Override close()138 public void close() { 139 synchronized (mLock) { 140 mIsClosed = true; 141 // Call close regardless of the mIsConnected. This may be called when it's not yet 142 // connected. 143 mController.close(); 144 } 145 } 146 147 @Override isClosed()148 public boolean isClosed() { 149 synchronized (mLock) { 150 return mIsClosed; 151 } 152 } 153 154 @Override expireTempEngaged()155 public void expireTempEngaged() { 156 // NA as MediaSession2 doesn't support UserEngagementStates for FGS. 157 } 158 159 @Override onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive)160 public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { 161 // NA as MediaSession2 doesn't support UserEngagementStates for FGS. 162 } 163 164 @Override sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb)165 public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, 166 KeyEvent ke, int sequenceId, ResultReceiver cb) { 167 // TODO(jaewan): Implement. 168 return false; 169 } 170 171 @Override canHandleVolumeKey()172 public boolean canHandleVolumeKey() { 173 // TODO: Implement when MediaSession2 starts to get key events. 174 return false; 175 } 176 177 @Override isLinkedToNotification(Notification notification)178 boolean isLinkedToNotification(Notification notification) { 179 // Currently it's not possible to link MediaSession2 with a Notification 180 return false; 181 } 182 183 @Override getSessionPolicies()184 public int getSessionPolicies() { 185 synchronized (mLock) { 186 return mPolicies; 187 } 188 } 189 190 @Override setSessionPolicies(int policies)191 public void setSessionPolicies(int policies) { 192 synchronized (mLock) { 193 mPolicies = policies; 194 } 195 } 196 197 @Override dump(PrintWriter pw, String prefix)198 public void dump(PrintWriter pw, String prefix) { 199 pw.println(prefix + "uniqueId=" + getUniqueId()); 200 pw.println(prefix + "token=" + mSessionToken); 201 pw.println(prefix + "controller=" + mController); 202 203 final String indent = prefix + " "; 204 pw.println(indent + "playbackActive=" + mController.isPlaybackActive()); 205 } 206 207 @Override toString()208 public String toString() { 209 return getPackageName() + "/" + getUniqueId() + " (userId=" + getUserId() + ")"; 210 } 211 212 private class Controller2Callback extends MediaController2.ControllerCallback { 213 @Override onConnected(MediaController2 controller, Session2CommandGroup allowedCommands)214 public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { 215 if (DEBUG) { 216 Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands); 217 } 218 MediaSessionService service; 219 synchronized (mLock) { 220 mIsConnected = true; 221 service = mService; 222 } 223 service.onSessionActiveStateChanged(MediaSession2Record.this, 224 /* playbackState= */ null); 225 } 226 227 @Override onDisconnected(MediaController2 controller)228 public void onDisconnected(MediaController2 controller) { 229 if (DEBUG) { 230 Log.d(TAG, "disconnected from " + mSessionToken); 231 } 232 MediaSessionService service; 233 synchronized (mLock) { 234 mIsConnected = false; 235 service = mService; 236 } 237 service.onSessionDied(MediaSession2Record.this); 238 } 239 240 @Override onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive)241 public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) { 242 if (DEBUG) { 243 Log.d(TAG, "playback active changed, " + mSessionToken + ", active=" 244 + playbackActive); 245 } 246 MediaSessionService service; 247 synchronized (mLock) { 248 service = mService; 249 } 250 service.onSessionPlaybackStateChanged( 251 MediaSession2Record.this, playbackActive, /* playbackState= */ null); 252 } 253 } 254 } 255