• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.ActivityThread;
22 import android.app.AppOpsManager;
23 import android.content.Context;
24 import android.os.IBinder;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.os.Process;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.internal.app.IAppOpsCallback;
35 import com.android.internal.app.IAppOpsService;
36 
37 import java.lang.ref.WeakReference;
38 import java.util.Objects;
39 
40 /**
41  * Class to encapsulate a number of common player operations:
42  *   - AppOps for OP_PLAY_AUDIO
43  *   - more to come (routing, transport control)
44  * @hide
45  */
46 public abstract class PlayerBase {
47 
48     private static final String TAG = "PlayerBase";
49     /** Debug app ops */
50     private static final boolean DEBUG_APP_OPS = false;
51     private static final boolean DEBUG = DEBUG_APP_OPS || false;
52     private static IAudioService sService; //lazy initialization, use getService()
53 
54     /** if true, only use OP_PLAY_AUDIO monitoring for logging, and rely on muting to happen
55      *  in AudioFlinger */
56     private static final boolean USE_AUDIOFLINGER_MUTING_FOR_OP = true;
57 
58     // parameters of the player that affect AppOps
59     protected AudioAttributes mAttributes;
60 
61     // volumes of the subclass "player volumes", as seen by the client of the subclass
62     //   (e.g. what was passed in AudioTrack.setVolume(float)). The actual volume applied is
63     //   the combination of the player volume, and the PlayerBase pan and volume multipliers
64     protected float mLeftVolume = 1.0f;
65     protected float mRightVolume = 1.0f;
66     protected float mAuxEffectSendLevel = 0.0f;
67 
68     // NEVER call into AudioService (see getService()) with mLock held: PlayerBase can run in
69     // the same process as AudioService, which can synchronously call back into this class,
70     // causing deadlocks between the two
71     private final Object mLock = new Object();
72 
73     // for AppOps
74     private @Nullable IAppOpsService mAppOps;
75     private @Nullable IAppOpsCallback mAppOpsCallback;
76     @GuardedBy("mLock")
77     private boolean mHasAppOpsPlayAudio = true;
78 
79     private final int mImplType;
80     // uniquely identifies the Player Interface throughout the system (P I Id)
81     private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
82 
83     @GuardedBy("mLock")
84     private int mState;
85     @GuardedBy("mLock")
86     private int mStartDelayMs = 0;
87     @GuardedBy("mLock")
88     private float mPanMultiplierL = 1.0f;
89     @GuardedBy("mLock")
90     private float mPanMultiplierR = 1.0f;
91     @GuardedBy("mLock")
92     private float mVolMultiplier = 1.0f;
93 
94     /**
95      * Constructor. Must be given audio attributes, as they are required for AppOps.
96      * @param attr non-null audio attributes
97      * @param class non-null class of the implementation of this abstract class
98      */
PlayerBase(@onNull AudioAttributes attr, int implType)99     PlayerBase(@NonNull AudioAttributes attr, int implType) {
100         if (attr == null) {
101             throw new IllegalArgumentException("Illegal null AudioAttributes");
102         }
103         mAttributes = attr;
104         mImplType = implType;
105         mState = AudioPlaybackConfiguration.PLAYER_STATE_IDLE;
106     };
107 
108     /**
109      * Call from derived class when instantiation / initialization is successful
110      */
baseRegisterPlayer()111     protected void baseRegisterPlayer() {
112         if (!USE_AUDIOFLINGER_MUTING_FOR_OP) {
113             IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
114             mAppOps = IAppOpsService.Stub.asInterface(b);
115             // initialize mHasAppOpsPlayAudio
116             updateAppOpsPlayAudio();
117             // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
118             mAppOpsCallback = new IAppOpsCallbackWrapper(this);
119             try {
120                 mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
121                         ActivityThread.currentPackageName(), mAppOpsCallback);
122             } catch (RemoteException e) {
123                 Log.e(TAG, "Error registering appOps callback", e);
124                 mHasAppOpsPlayAudio = false;
125             }
126         }
127         try {
128             mPlayerIId = getService().trackPlayer(
129                     new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this)));
130         } catch (RemoteException e) {
131             Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
132         }
133     }
134 
135     /**
136      * To be called whenever the audio attributes of the player change
137      * @param attr non-null audio attributes
138      */
baseUpdateAudioAttributes(@onNull AudioAttributes attr)139     void baseUpdateAudioAttributes(@NonNull AudioAttributes attr) {
140         if (attr == null) {
141             throw new IllegalArgumentException("Illegal null AudioAttributes");
142         }
143         try {
144             getService().playerAttributes(mPlayerIId, attr);
145         } catch (RemoteException e) {
146             Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
147         }
148         synchronized (mLock) {
149             boolean attributesChanged = (mAttributes != attr);
150             mAttributes = attr;
151             updateAppOpsPlayAudio_sync(attributesChanged);
152         }
153     }
154 
updateState(int state)155     private void updateState(int state) {
156         final int piid;
157         synchronized (mLock) {
158             mState = state;
159             piid = mPlayerIId;
160         }
161         try {
162             getService().playerEvent(piid, state);
163         } catch (RemoteException e) {
164             Log.e(TAG, "Error talking to audio service, "
165                     + AudioPlaybackConfiguration.toLogFriendlyPlayerState(state)
166                     + " state will not be tracked for piid=" + piid, e);
167         }
168     }
169 
baseStart()170     void baseStart() {
171         if (DEBUG) { Log.v(TAG, "baseStart() piid=" + mPlayerIId); }
172         updateState(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
173         synchronized (mLock) {
174             if (isRestricted_sync()) {
175                 playerSetVolume(true/*muting*/,0, 0);
176             }
177         }
178     }
179 
baseSetStartDelayMs(int delayMs)180     void baseSetStartDelayMs(int delayMs) {
181         synchronized(mLock) {
182             mStartDelayMs = Math.max(delayMs, 0);
183         }
184     }
185 
getStartDelayMs()186     protected int getStartDelayMs() {
187         synchronized(mLock) {
188             return mStartDelayMs;
189         }
190     }
191 
basePause()192     void basePause() {
193         if (DEBUG) { Log.v(TAG, "basePause() piid=" + mPlayerIId); }
194         updateState(AudioPlaybackConfiguration.PLAYER_STATE_PAUSED);
195     }
196 
baseStop()197     void baseStop() {
198         if (DEBUG) { Log.v(TAG, "baseStop() piid=" + mPlayerIId); }
199         updateState(AudioPlaybackConfiguration.PLAYER_STATE_STOPPED);
200     }
201 
baseSetPan(float pan)202     void baseSetPan(float pan) {
203         final float p = Math.min(Math.max(-1.0f, pan), 1.0f);
204         synchronized (mLock) {
205             if (p >= 0.0f) {
206                 mPanMultiplierL = 1.0f - p;
207                 mPanMultiplierR = 1.0f;
208             } else {
209                 mPanMultiplierL = 1.0f;
210                 mPanMultiplierR = 1.0f + p;
211             }
212         }
213         updatePlayerVolume();
214     }
215 
updatePlayerVolume()216     private void updatePlayerVolume() {
217         final float finalLeftVol, finalRightVol;
218         final boolean isRestricted;
219         synchronized (mLock) {
220             finalLeftVol = mVolMultiplier * mLeftVolume * mPanMultiplierL;
221             finalRightVol = mVolMultiplier * mRightVolume * mPanMultiplierR;
222             isRestricted = isRestricted_sync();
223         }
224         playerSetVolume(isRestricted /*muting*/, finalLeftVol, finalRightVol);
225     }
226 
setVolumeMultiplier(float vol)227     void setVolumeMultiplier(float vol) {
228         synchronized (mLock) {
229             this.mVolMultiplier = vol;
230         }
231         updatePlayerVolume();
232     }
233 
baseSetVolume(float leftVolume, float rightVolume)234     void baseSetVolume(float leftVolume, float rightVolume) {
235         synchronized (mLock) {
236             mLeftVolume = leftVolume;
237             mRightVolume = rightVolume;
238         }
239         updatePlayerVolume();
240     }
241 
baseSetAuxEffectSendLevel(float level)242     int baseSetAuxEffectSendLevel(float level) {
243         synchronized (mLock) {
244             mAuxEffectSendLevel = level;
245             if (isRestricted_sync()) {
246                 return AudioSystem.SUCCESS;
247             }
248         }
249         return playerSetAuxEffectSendLevel(false/*muting*/, level);
250     }
251 
252     /**
253      * To be called from a subclass release or finalize method.
254      * Releases AppOps related resources.
255      */
baseRelease()256     void baseRelease() {
257         if (DEBUG) { Log.v(TAG, "baseRelease() piid=" + mPlayerIId + " state=" + mState); }
258         boolean releasePlayer = false;
259         synchronized (mLock) {
260             if (mState != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
261                 releasePlayer = true;
262                 mState = AudioPlaybackConfiguration.PLAYER_STATE_RELEASED;
263             }
264         }
265         try {
266             if (releasePlayer) {
267                 getService().releasePlayer(mPlayerIId);
268             }
269         } catch (RemoteException e) {
270             Log.e(TAG, "Error talking to audio service, the player will still be tracked", e);
271         }
272         try {
273             if (mAppOps != null) {
274                 mAppOps.stopWatchingMode(mAppOpsCallback);
275             }
276         } catch (Exception e) {
277             // nothing to do here, the object is supposed to be released anyway
278         }
279     }
280 
updateAppOpsPlayAudio()281     private void updateAppOpsPlayAudio() {
282         synchronized (mLock) {
283             updateAppOpsPlayAudio_sync(false);
284         }
285     }
286 
287     /**
288      * To be called whenever a condition that might affect audibility of this player is updated.
289      * Must be called synchronized on mLock.
290      */
updateAppOpsPlayAudio_sync(boolean attributesChanged)291     void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
292         if (USE_AUDIOFLINGER_MUTING_FOR_OP) {
293             return;
294         }
295         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
296         try {
297             int mode = AppOpsManager.MODE_IGNORED;
298             if (mAppOps != null) {
299                 mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
300                     mAttributes.getUsage(),
301                     Process.myUid(), ActivityThread.currentPackageName());
302             }
303             mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
304         } catch (RemoteException e) {
305             mHasAppOpsPlayAudio = false;
306         }
307 
308         // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
309         // volume used by the player
310         try {
311             if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio ||
312                     attributesChanged) {
313                 getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio);
314                 if (!isRestricted_sync()) {
315                     if (DEBUG_APP_OPS) {
316                         Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
317                                 + "/" + mRightVolume);
318                     }
319                     playerSetVolume(false/*muting*/,
320                             mLeftVolume * mPanMultiplierL, mRightVolume * mPanMultiplierR);
321                     playerSetAuxEffectSendLevel(false/*muting*/, mAuxEffectSendLevel);
322                 } else {
323                     if (DEBUG_APP_OPS) {
324                         Log.v(TAG, "updateAppOpsPlayAudio: muting player");
325                     }
326                     playerSetVolume(true/*muting*/, 0.0f, 0.0f);
327                     playerSetAuxEffectSendLevel(true/*muting*/, 0.0f);
328                 }
329             }
330         } catch (Exception e) {
331             // failing silently, player might not be in right state
332         }
333     }
334 
335     /**
336      * To be called by the subclass whenever an operation is potentially restricted.
337      * As the media player-common behavior are incorporated into this class, the subclass's need
338      * to call this method should be removed, and this method could become private.
339      * FIXME can this method be private so subclasses don't have to worry about when to check
340      *    the restrictions.
341      * @return
342      */
isRestricted_sync()343     boolean isRestricted_sync() {
344         if (USE_AUDIOFLINGER_MUTING_FOR_OP) {
345             return false;
346         }
347         // check app ops
348         if (mHasAppOpsPlayAudio) {
349             return false;
350         }
351         // check bypass flag
352         if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
353             return false;
354         }
355         // check force audibility flag and camera restriction
356         if (((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0)
357                 && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)) {
358             boolean cameraSoundForced = false;
359             try {
360                 cameraSoundForced = getService().isCameraSoundForced();
361             } catch (RemoteException e) {
362                 Log.e(TAG, "Cannot access AudioService in isRestricted_sync()");
363             } catch (NullPointerException e) {
364                 Log.e(TAG, "Null AudioService in isRestricted_sync()");
365             }
366             if (cameraSoundForced) {
367                 return false;
368             }
369         }
370         return true;
371     }
372 
getService()373     private static IAudioService getService()
374     {
375         if (sService != null) {
376             return sService;
377         }
378         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
379         sService = IAudioService.Stub.asInterface(b);
380         return sService;
381     }
382 
383     /**
384      * @hide
385      * @param delayMs
386      */
setStartDelayMs(int delayMs)387     public void setStartDelayMs(int delayMs) {
388         baseSetStartDelayMs(delayMs);
389     }
390 
391     //=====================================================================
392     // Abstract methods a subclass needs to implement
393     /**
394      * Abstract method for the subclass behavior's for volume and muting commands
395      * @param muting if true, the player is to be muted, and the volume values can be ignored
396      * @param leftVolume the left volume to use if muting is false
397      * @param rightVolume the right volume to use if muting is false
398      */
playerSetVolume(boolean muting, float leftVolume, float rightVolume)399     abstract void playerSetVolume(boolean muting, float leftVolume, float rightVolume);
400 
401     /**
402      * Abstract method to apply a {@link VolumeShaper.Configuration}
403      * and a {@link VolumeShaper.Operation} to the Player.
404      * This should be overridden by the Player to call into the native
405      * VolumeShaper implementation. Multiple {@code VolumeShapers} may be
406      * concurrently active for a given Player, each accessible by the
407      * {@code VolumeShaper} id.
408      *
409      * The {@code VolumeShaper} implementation caches the id returned
410      * when applying a fully specified configuration
411      * from {VolumeShaper.Configuration.Builder} to track later
412      * operation changes requested on it.
413      *
414      * @param configuration a {@code VolumeShaper.Configuration} object
415      *        created by {@link VolumeShaper.Configuration.Builder} or
416      *        an created from a {@code VolumeShaper} id
417      *        by the {@link VolumeShaper.Configuration} constructor.
418      * @param operation a {@code VolumeShaper.Operation}.
419      * @return a negative error status or a
420      *         non-negative {@code VolumeShaper} id on success.
421      */
playerApplyVolumeShaper( @onNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation)422     /* package */ abstract int playerApplyVolumeShaper(
423             @NonNull VolumeShaper.Configuration configuration,
424             @NonNull VolumeShaper.Operation operation);
425 
426     /**
427      * Abstract method to get the current VolumeShaper state.
428      * @param id the {@code VolumeShaper} id returned from
429      *           sending a fully specified {@code VolumeShaper.Configuration}
430      *           through {@link #playerApplyVolumeShaper}
431      * @return a {@code VolumeShaper.State} object or null if
432      *         there is no {@code VolumeShaper} for the id.
433      */
playerGetVolumeShaperState(int id)434     /* package */ abstract @Nullable VolumeShaper.State playerGetVolumeShaperState(int id);
435 
playerSetAuxEffectSendLevel(boolean muting, float level)436     abstract int playerSetAuxEffectSendLevel(boolean muting, float level);
playerStart()437     abstract void playerStart();
playerPause()438     abstract void playerPause();
playerStop()439     abstract void playerStop();
440 
441     //=====================================================================
442     private static class IAppOpsCallbackWrapper extends IAppOpsCallback.Stub {
443         private final WeakReference<PlayerBase> mWeakPB;
444 
IAppOpsCallbackWrapper(PlayerBase pb)445         public IAppOpsCallbackWrapper(PlayerBase pb) {
446             mWeakPB = new WeakReference<PlayerBase>(pb);
447         }
448 
449         @Override
opChanged(int op, int uid, String packageName)450         public void opChanged(int op, int uid, String packageName) {
451             if (op == AppOpsManager.OP_PLAY_AUDIO) {
452                 if (DEBUG_APP_OPS) { Log.v(TAG, "opChanged: op=PLAY_AUDIO pack=" + packageName); }
453                 final PlayerBase pb = mWeakPB.get();
454                 if (pb != null) {
455                     pb.updateAppOpsPlayAudio();
456                 }
457             }
458         }
459     }
460 
461     //=====================================================================
462     /**
463      * Wrapper around an implementation of IPlayer for all subclasses of PlayerBase
464      * that doesn't keep a strong reference on PlayerBase
465      */
466     private static class IPlayerWrapper extends IPlayer.Stub {
467         private final WeakReference<PlayerBase> mWeakPB;
468 
IPlayerWrapper(PlayerBase pb)469         public IPlayerWrapper(PlayerBase pb) {
470             mWeakPB = new WeakReference<PlayerBase>(pb);
471         }
472 
473         @Override
start()474         public void start() {
475             final PlayerBase pb = mWeakPB.get();
476             if (pb != null) {
477                 pb.playerStart();
478             }
479         }
480 
481         @Override
pause()482         public void pause() {
483             final PlayerBase pb = mWeakPB.get();
484             if (pb != null) {
485                 pb.playerPause();
486             }
487         }
488 
489         @Override
stop()490         public void stop() {
491             final PlayerBase pb = mWeakPB.get();
492             if (pb != null) {
493                 pb.playerStop();
494             }
495         }
496 
497         @Override
setVolume(float vol)498         public void setVolume(float vol) {
499             final PlayerBase pb = mWeakPB.get();
500             if (pb != null) {
501                 pb.setVolumeMultiplier(vol);
502             }
503         }
504 
505         @Override
setPan(float pan)506         public void setPan(float pan) {
507             final PlayerBase pb = mWeakPB.get();
508             if (pb != null) {
509                 pb.baseSetPan(pan);
510             }
511         }
512 
513         @Override
setStartDelayMs(int delayMs)514         public void setStartDelayMs(int delayMs) {
515             final PlayerBase pb = mWeakPB.get();
516             if (pb != null) {
517                 pb.baseSetStartDelayMs(delayMs);
518             }
519         }
520 
521         @Override
applyVolumeShaper( @onNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation)522         public void applyVolumeShaper(
523                 @NonNull VolumeShaper.Configuration configuration,
524                 @NonNull VolumeShaper.Operation operation) {
525             final PlayerBase pb = mWeakPB.get();
526             if (pb != null) {
527                 pb.playerApplyVolumeShaper(configuration, operation);
528             }
529         }
530     }
531 
532     //=====================================================================
533     /**
534      * Class holding all the information about a player that needs to be known at registration time
535      */
536     public static class PlayerIdCard implements Parcelable {
537         public final int mPlayerType;
538 
539         public static final int AUDIO_ATTRIBUTES_NONE = 0;
540         public static final int AUDIO_ATTRIBUTES_DEFINED = 1;
541         public final AudioAttributes mAttributes;
542         public final IPlayer mIPlayer;
543 
PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer)544         PlayerIdCard(int type, @NonNull AudioAttributes attr, @NonNull IPlayer iplayer) {
545             mPlayerType = type;
546             mAttributes = attr;
547             mIPlayer = iplayer;
548         }
549 
550         @Override
hashCode()551         public int hashCode() {
552             return Objects.hash(mPlayerType);
553         }
554 
555         @Override
describeContents()556         public int describeContents() {
557             return 0;
558         }
559 
560         @Override
writeToParcel(Parcel dest, int flags)561         public void writeToParcel(Parcel dest, int flags) {
562             dest.writeInt(mPlayerType);
563             mAttributes.writeToParcel(dest, 0);
564             dest.writeStrongBinder(mIPlayer == null ? null : mIPlayer.asBinder());
565         }
566 
567         public static final @android.annotation.NonNull Parcelable.Creator<PlayerIdCard> CREATOR
568         = new Parcelable.Creator<PlayerIdCard>() {
569             /**
570              * Rebuilds an PlayerIdCard previously stored with writeToParcel().
571              * @param p Parcel object to read the PlayerIdCard from
572              * @return a new PlayerIdCard created from the data in the parcel
573              */
574             public PlayerIdCard createFromParcel(Parcel p) {
575                 return new PlayerIdCard(p);
576             }
577             public PlayerIdCard[] newArray(int size) {
578                 return new PlayerIdCard[size];
579             }
580         };
581 
PlayerIdCard(Parcel in)582         private PlayerIdCard(Parcel in) {
583             mPlayerType = in.readInt();
584             mAttributes = AudioAttributes.CREATOR.createFromParcel(in);
585             // IPlayer can be null if unmarshalling a Parcel coming from who knows where
586             final IBinder b = in.readStrongBinder();
587             mIPlayer = (b == null ? null : IPlayer.Stub.asInterface(b));
588         }
589 
590         @Override
equals(Object o)591         public boolean equals(Object o) {
592             if (this == o) return true;
593             if (o == null || !(o instanceof PlayerIdCard)) return false;
594 
595             PlayerIdCard that = (PlayerIdCard) o;
596 
597             // FIXME change to the binder player interface once supported as a member
598             return ((mPlayerType == that.mPlayerType) && mAttributes.equals(that.mAttributes));
599         }
600     }
601 
602     //=====================================================================
603     // Utilities
604 
605     /**
606      * @hide
607      * Use to generate warning or exception in legacy code paths that allowed passing stream types
608      * to qualify audio playback.
609      * @param streamType the stream type to check
610      * @throws IllegalArgumentException
611      */
deprecateStreamTypeForPlayback(int streamType, @NonNull String className, @NonNull String opName)612     public static void deprecateStreamTypeForPlayback(int streamType, @NonNull String className,
613             @NonNull String opName) throws IllegalArgumentException {
614         // STREAM_ACCESSIBILITY was introduced at the same time the use of stream types
615         // for audio playback was deprecated, so it is not allowed at all to qualify a playback
616         // use case
617         if (streamType == AudioManager.STREAM_ACCESSIBILITY) {
618             throw new IllegalArgumentException("Use of STREAM_ACCESSIBILITY is reserved for "
619                     + "volume control");
620         }
621         Log.w(className, "Use of stream types is deprecated for operations other than " +
622                 "volume control");
623         Log.w(className, "See the documentation of " + opName + " for what to use instead with " +
624                 "android.media.AudioAttributes to qualify your playback use case");
625     }
626 
getCurrentOpPackageName()627     protected String getCurrentOpPackageName() {
628         return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
629     }
630 }
631