• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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 package android.media;
17 
18 import android.annotation.IntDef;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.os.Build;
24 import android.os.BadParcelableException;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.lang.ref.WeakReference;
31 import java.util.Arrays;
32 import java.util.Objects;
33 
34 /**
35  * The {@code VolumeShaper} class is used to automatically control audio volume during media
36  * playback, allowing simple implementation of transition effects and ducking.
37  * It is created from implementations of {@code VolumeAutomation},
38  * such as {@code MediaPlayer} and {@code AudioTrack} (referred to as "players" below),
39  * by {@link MediaPlayer#createVolumeShaper} or {@link AudioTrack#createVolumeShaper}.
40  *
41  * A {@code VolumeShaper} is intended for short volume changes.
42  * If the audio output sink changes during
43  * a {@code VolumeShaper} transition, the precise curve position may be lost, and the
44  * {@code VolumeShaper} may advance to the end of the curve for the new audio output sink.
45  *
46  * The {@code VolumeShaper} appears as an additional scaling on the audio output,
47  * and adjusts independently of track or stream volume controls.
48  */
49 public final class VolumeShaper implements AutoCloseable {
50     /* member variables */
51     private int mId;
52     private final WeakReference<PlayerBase> mWeakPlayerBase;
53 
VolumeShaper( @onNull Configuration configuration, @NonNull PlayerBase playerBase)54     /* package */ VolumeShaper(
55             @NonNull Configuration configuration, @NonNull PlayerBase playerBase) {
56         mWeakPlayerBase = new WeakReference<PlayerBase>(playerBase);
57         mId = applyPlayer(configuration, new Operation.Builder().defer().build());
58     }
59 
getId()60     /* package */ int getId() {
61         return mId;
62     }
63 
64     /**
65      * Applies the {@link VolumeShaper.Operation} to the {@code VolumeShaper}.
66      *
67      * Applying {@link VolumeShaper.Operation#PLAY} after {@code PLAY}
68      * or {@link VolumeShaper.Operation#REVERSE} after
69      * {@code REVERSE} has no effect.
70      *
71      * Applying {@link VolumeShaper.Operation#PLAY} when the player
72      * hasn't started will synchronously start the {@code VolumeShaper} when
73      * playback begins.
74      *
75      * @param operation the {@code operation} to apply.
76      * @throws IllegalStateException if the player is uninitialized or if there
77      *         is a critical failure. In that case, the {@code VolumeShaper} should be
78      *         recreated.
79      */
apply(@onNull Operation operation)80     public void apply(@NonNull Operation operation) {
81         /* void */ applyPlayer(new VolumeShaper.Configuration(mId), operation);
82     }
83 
84     /**
85      * Replaces the current {@code VolumeShaper}
86      * {@code configuration} with a new {@code configuration}.
87      *
88      * This allows the user to change the volume shape
89      * while the existing {@code VolumeShaper} is in effect.
90      *
91      * The effect of {@code replace()} is similar to an atomic close of
92      * the existing {@code VolumeShaper} and creation of a new {@code VolumeShaper}.
93      *
94      * If the {@code operation} is {@link VolumeShaper.Operation#PLAY} then the
95      * new curve starts immediately.
96      *
97      * If the {@code operation} is
98      * {@link VolumeShaper.Operation#REVERSE}, then the new curve will
99      * be delayed until {@code PLAY} is applied.
100      *
101      * @param configuration the new {@code configuration} to use.
102      * @param operation the {@code operation} to apply to the {@code VolumeShaper}
103      * @param join if true, match the start volume of the
104      *             new {@code configuration} to the current volume of the existing
105      *             {@code VolumeShaper}, to avoid discontinuity.
106      * @throws IllegalStateException if the player is uninitialized or if there
107      *         is a critical failure. In that case, the {@code VolumeShaper} should be
108      *         recreated.
109      */
replace( @onNull Configuration configuration, @NonNull Operation operation, boolean join)110     public void replace(
111             @NonNull Configuration configuration, @NonNull Operation operation, boolean join) {
112         mId = applyPlayer(
113                 configuration,
114                 new Operation.Builder(operation).replace(mId, join).build());
115     }
116 
117     /**
118      * Returns the current volume scale attributable to the {@code VolumeShaper}.
119      *
120      * This is the last volume from the {@code VolumeShaper} used for the player,
121      * or the initial volume if the {@code VolumeShaper} hasn't been started with
122      * {@link VolumeShaper.Operation#PLAY}.
123      *
124      * @return the volume, linearly represented as a value between 0.f and 1.f.
125      * @throws IllegalStateException if the player is uninitialized or if there
126      *         is a critical failure.  In that case, the {@code VolumeShaper} should be
127      *         recreated.
128      */
getVolume()129     public float getVolume() {
130         return getStatePlayer(mId).getVolume();
131     }
132 
133     /**
134      * Releases the {@code VolumeShaper} object; any volume scale due to the
135      * {@code VolumeShaper} is removed after closing.
136      *
137      * If the volume does not reach 1.f when the {@code VolumeShaper} is closed
138      * (or finalized), there may be an abrupt change of volume.
139      *
140      * {@code close()} may be safely called after a prior {@code close()}.
141      * This class implements the Java {@code AutoClosable} interface and
142      * may be used with try-with-resources.
143      */
144     @Override
close()145     public void close() {
146         try {
147             /* void */ applyPlayer(
148                     new VolumeShaper.Configuration(mId),
149                     new Operation.Builder().terminate().build());
150         } catch (IllegalStateException ise) {
151             ; // ok
152         }
153         if (mWeakPlayerBase != null) {
154             mWeakPlayerBase.clear();
155         }
156     }
157 
158     @Override
finalize()159     protected void finalize() {
160         close(); // ensure we remove the native VolumeShaper
161     }
162 
163     /**
164      * Internal call to apply the {@code configuration} and {@code operation} to the player.
165      * Returns a valid shaper id or throws the appropriate exception.
166      * @param configuration
167      * @param operation
168      * @return id a non-negative shaper id.
169      * @throws IllegalStateException if the player has been deallocated or is uninitialized.
170      */
applyPlayer( @onNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation)171     private int applyPlayer(
172             @NonNull VolumeShaper.Configuration configuration,
173             @NonNull VolumeShaper.Operation operation) {
174         final int id;
175         if (mWeakPlayerBase != null) {
176             PlayerBase player = mWeakPlayerBase.get();
177             if (player == null) {
178                 throw new IllegalStateException("player deallocated");
179             }
180             id = player.playerApplyVolumeShaper(configuration, operation);
181         } else {
182             throw new IllegalStateException("uninitialized shaper");
183         }
184         if (id < 0) {
185             // TODO - get INVALID_OPERATION from platform.
186             final int VOLUME_SHAPER_INVALID_OPERATION = -38; // must match with platform
187             // Due to RPC handling, we translate integer codes to exceptions right before
188             // delivering to the user.
189             if (id == VOLUME_SHAPER_INVALID_OPERATION) {
190                 throw new IllegalStateException("player or VolumeShaper deallocated");
191             } else {
192                 throw new IllegalArgumentException("invalid configuration or operation: " + id);
193             }
194         }
195         return id;
196     }
197 
198     /**
199      * Internal call to retrieve the current {@code VolumeShaper} state.
200      * @param id
201      * @return the current {@code VolumeShaper.State}
202      * @throws IllegalStateException if the player has been deallocated or is uninitialized.
203      */
getStatePlayer(int id)204     private @NonNull VolumeShaper.State getStatePlayer(int id) {
205         final VolumeShaper.State state;
206         if (mWeakPlayerBase != null) {
207             PlayerBase player = mWeakPlayerBase.get();
208             if (player == null) {
209                 throw new IllegalStateException("player deallocated");
210             }
211             state = player.playerGetVolumeShaperState(id);
212         } else {
213             throw new IllegalStateException("uninitialized shaper");
214         }
215         if (state == null) {
216             throw new IllegalStateException("shaper cannot be found");
217         }
218         return state;
219     }
220 
221     /**
222      * The {@code VolumeShaper.Configuration} class contains curve
223      * and duration information.
224      * It is constructed by the {@link VolumeShaper.Configuration.Builder}.
225      * <p>
226      * A {@code VolumeShaper.Configuration} is used by
227      * {@link VolumeAutomation#createVolumeShaper(Configuration)
228      * VolumeAutomation.createVolumeShaper(Configuration)} to create
229      * a {@code VolumeShaper} and
230      * by {@link VolumeShaper#replace(Configuration, Operation, boolean)
231      * VolumeShaper.replace(Configuration, Operation, boolean)}
232      * to replace an existing {@code configuration}.
233      * <p>
234      * The {@link AudioTrack} and {@link MediaPlayer} classes implement
235      * the {@link VolumeAutomation} interface.
236      */
237     public static final class Configuration implements Parcelable {
238         private static final int MAXIMUM_CURVE_POINTS = 16;
239 
240         /**
241          * Returns the maximum number of curve points allowed for
242          * {@link VolumeShaper.Builder#setCurve(float[], float[])}.
243          */
getMaximumCurvePoints()244         public static int getMaximumCurvePoints() {
245             return MAXIMUM_CURVE_POINTS;
246         }
247 
248         // These values must match the native VolumeShaper::Configuration::Type
249         /** @hide */
250         @IntDef({
251             TYPE_ID,
252             TYPE_SCALE,
253             })
254         @Retention(RetentionPolicy.SOURCE)
255         public @interface Type {}
256 
257         /**
258          * Specifies a {@link VolumeShaper} handle created by {@link #VolumeShaper(int)}
259          * from an id returned by {@code setVolumeShaper()}.
260          * The type, curve, etc. may not be queried from
261          * a {@code VolumeShaper} object of this type;
262          * the handle is used to identify and change the operation of
263          * an existing {@code VolumeShaper} sent to the player.
264          */
265         /* package */ static final int TYPE_ID = 0;
266 
267         /**
268          * Specifies a {@link VolumeShaper} to be used
269          * as an additional scale to the current volume.
270          * This is created by the {@link VolumeShaper.Builder}.
271          */
272         /* package */ static final int TYPE_SCALE = 1;
273 
274         // These values must match the native InterpolatorType enumeration.
275         /** @hide */
276         @IntDef({
277             INTERPOLATOR_TYPE_STEP,
278             INTERPOLATOR_TYPE_LINEAR,
279             INTERPOLATOR_TYPE_CUBIC,
280             INTERPOLATOR_TYPE_CUBIC_MONOTONIC,
281             })
282         @Retention(RetentionPolicy.SOURCE)
283         public @interface InterpolatorType {}
284 
285         /**
286          * Stepwise volume curve.
287          */
288         public static final int INTERPOLATOR_TYPE_STEP = 0;
289 
290         /**
291          * Linear interpolated volume curve.
292          */
293         public static final int INTERPOLATOR_TYPE_LINEAR = 1;
294 
295         /**
296          * Cubic interpolated volume curve.
297          * This is default if unspecified.
298          */
299         public static final int INTERPOLATOR_TYPE_CUBIC = 2;
300 
301         /**
302          * Cubic interpolated volume curve
303          * that preserves local monotonicity.
304          * So long as the control points are locally monotonic,
305          * the curve interpolation between those points are monotonic.
306          * This is useful for cubic spline interpolated
307          * volume ramps and ducks.
308          */
309         public static final int INTERPOLATOR_TYPE_CUBIC_MONOTONIC = 3;
310 
311         // These values must match the native VolumeShaper::Configuration::InterpolatorType
312         /** @hide */
313         @IntDef({
314             OPTION_FLAG_VOLUME_IN_DBFS,
315             OPTION_FLAG_CLOCK_TIME,
316             })
317         @Retention(RetentionPolicy.SOURCE)
318         public @interface OptionFlag {}
319 
320         /**
321          * @hide
322          * Use a dB full scale volume range for the volume curve.
323          *<p>
324          * The volume scale is typically from 0.f to 1.f on a linear scale;
325          * this option changes to -inf to 0.f on a db full scale,
326          * where 0.f is equivalent to a scale of 1.f.
327          */
328         public static final int OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0);
329 
330         /**
331          * @hide
332          * Use clock time instead of media time.
333          *<p>
334          * The default implementation of {@code VolumeShaper} is to apply
335          * volume changes by the media time of the player.
336          * Hence, the {@code VolumeShaper} will speed or slow down to
337          * match player changes of playback rate, pause, or resume.
338          *<p>
339          * The {@code OPTION_FLAG_CLOCK_TIME} option allows the {@code VolumeShaper}
340          * progress to be determined by clock time instead of media time.
341          */
342         public static final int OPTION_FLAG_CLOCK_TIME = (1 << 1);
343 
344         private static final int OPTION_FLAG_PUBLIC_ALL =
345                 OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME;
346 
347         /**
348          * A one second linear ramp from silence to full volume.
349          * Use {@link VolumeShaper.Builder#reflectTimes()}
350          * or {@link VolumeShaper.Builder#invertVolumes()} to generate
351          * the matching linear duck.
352          */
353         public static final Configuration LINEAR_RAMP = new VolumeShaper.Configuration.Builder()
354                 .setInterpolatorType(INTERPOLATOR_TYPE_LINEAR)
355                 .setCurve(new float[] {0.f, 1.f} /* times */,
356                         new float[] {0.f, 1.f} /* volumes */)
357                 .setDuration(1000)
358                 .build();
359 
360         /**
361          * A one second cubic ramp from silence to full volume.
362          * Use {@link VolumeShaper.Builder#reflectTimes()}
363          * or {@link VolumeShaper.Builder#invertVolumes()} to generate
364          * the matching cubic duck.
365          */
366         public static final Configuration CUBIC_RAMP = new VolumeShaper.Configuration.Builder()
367                 .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
368                 .setCurve(new float[] {0.f, 1.f} /* times */,
369                         new float[] {0.f, 1.f}  /* volumes */)
370                 .setDuration(1000)
371                 .build();
372 
373         /**
374          * A one second sine curve
375          * from silence to full volume for energy preserving cross fades.
376          * Use {@link VolumeShaper.Builder#reflectTimes()} to generate
377          * the matching cosine duck.
378          */
379         public static final Configuration SINE_RAMP;
380 
381         /**
382          * A one second sine-squared s-curve ramp
383          * from silence to full volume.
384          * Use {@link VolumeShaper.Builder#reflectTimes()}
385          * or {@link VolumeShaper.Builder#invertVolumes()} to generate
386          * the matching sine-squared s-curve duck.
387          */
388         public static final Configuration SCURVE_RAMP;
389 
390         static {
391             final int POINTS = MAXIMUM_CURVE_POINTS;
392             final float times[] = new float[POINTS];
393             final float sines[] = new float[POINTS];
394             final float scurve[] = new float[POINTS];
395             for (int i = 0; i < POINTS; ++i) {
396                 times[i] = (float)i / (POINTS - 1);
397                 final float sine = (float)Math.sin(times[i] * Math.PI / 2.);
398                 sines[i] = sine;
399                 scurve[i] = sine * sine;
400             }
401             SINE_RAMP = new VolumeShaper.Configuration.Builder()
402                 .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
403                 .setCurve(times, sines)
404                 .setDuration(1000)
405                 .build();
406             SCURVE_RAMP = new VolumeShaper.Configuration.Builder()
407                 .setInterpolatorType(INTERPOLATOR_TYPE_CUBIC)
408                 .setCurve(times, scurve)
409                 .setDuration(1000)
410                 .build();
411         }
412 
413         /*
414          * member variables - these are all final
415          */
416 
417         // type of VolumeShaper
418         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
419         private final int mType;
420 
421         // valid when mType is TYPE_ID
422         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
423         private final int mId;
424 
425         // valid when mType is TYPE_SCALE
426         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
427         private final int mOptionFlags;
428         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
429         private final double mDurationMs;
430         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
431         private final int mInterpolatorType;
432         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
433         private final float[] mTimes;
434         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
435         private final float[] mVolumes;
436 
437         @Override
toString()438         public String toString() {
439             return "VolumeShaper.Configuration{"
440                     + "mType = " + mType
441                     + ", mId = " + mId
442                     + (mType == TYPE_ID
443                         ? "}"
444                         : ", mOptionFlags = 0x" + Integer.toHexString(mOptionFlags).toUpperCase()
445                         + ", mDurationMs = " + mDurationMs
446                         + ", mInterpolatorType = " + mInterpolatorType
447                         + ", mTimes[] = " + Arrays.toString(mTimes)
448                         + ", mVolumes[] = " + Arrays.toString(mVolumes)
449                         + "}");
450         }
451 
452         @Override
hashCode()453         public int hashCode() {
454             return mType == TYPE_ID
455                     ? Objects.hash(mType, mId)
456                     : Objects.hash(mType, mId,
457                             mOptionFlags, mDurationMs, mInterpolatorType,
458                             Arrays.hashCode(mTimes), Arrays.hashCode(mVolumes));
459         }
460 
461         @Override
equals(Object o)462         public boolean equals(Object o) {
463             if (!(o instanceof Configuration)) return false;
464             if (o == this) return true;
465             final Configuration other = (Configuration) o;
466             // Note that exact floating point equality may not be guaranteed
467             // for a theoretically idempotent operation; for example,
468             // there are many cases where a + b - b != a.
469             return mType == other.mType
470                     && mId == other.mId
471                     && (mType == TYPE_ID
472                         ||  (mOptionFlags == other.mOptionFlags
473                             && mDurationMs == other.mDurationMs
474                             && mInterpolatorType == other.mInterpolatorType
475                             && Arrays.equals(mTimes, other.mTimes)
476                             && Arrays.equals(mVolumes, other.mVolumes)));
477         }
478 
479         @Override
describeContents()480         public int describeContents() {
481             return 0;
482         }
483 
484         @Override
writeToParcel(Parcel dest, int flags)485         public void writeToParcel(Parcel dest, int flags) {
486             VolumeShaperConfiguration parcelable = toParcelable();
487             parcelable.writeToParcel(dest, flags);
488         }
489 
490         /** @hide */
toParcelable()491         public VolumeShaperConfiguration toParcelable() {
492             VolumeShaperConfiguration parcelable = new VolumeShaperConfiguration();
493             parcelable.type = typeToAidl(mType);
494             parcelable.id = mId;
495             if (mType != TYPE_ID) {
496                 parcelable.optionFlags = optionFlagsToAidl(mOptionFlags);
497                 parcelable.durationMs = mDurationMs;
498                 parcelable.interpolatorConfig = toInterpolatorParcelable();
499             }
500             return parcelable;
501         }
502 
toInterpolatorParcelable()503         private InterpolatorConfig toInterpolatorParcelable() {
504             InterpolatorConfig parcelable = new InterpolatorConfig();
505             parcelable.type = interpolatorTypeToAidl(mInterpolatorType);
506             parcelable.firstSlope = 0.f; // first slope (specifying for native side)
507             parcelable.lastSlope = 0.f; // last slope (specifying for native side)
508             parcelable.xy = new float[mTimes.length * 2];
509             for (int i = 0; i < mTimes.length; ++i) {
510                 parcelable.xy[i * 2] = mTimes[i];
511                 parcelable.xy[i * 2 + 1] = mVolumes[i];
512             }
513             return parcelable;
514         }
515 
516         /** @hide */
fromParcelable(VolumeShaperConfiguration parcelable)517         public static Configuration fromParcelable(VolumeShaperConfiguration parcelable) {
518             // this needs to match the native VolumeShaper.Configuration parceling
519             final int type = typeFromAidl(parcelable.type);
520             final int id = parcelable.id;
521             if (type == TYPE_ID) {
522                 return new VolumeShaper.Configuration(id);
523             } else {
524                 final int optionFlags = optionFlagsFromAidl(parcelable.optionFlags);
525                 final double durationMs = parcelable.durationMs;
526                 final int interpolatorType = interpolatorTypeFromAidl(
527                         parcelable.interpolatorConfig.type);
528                 // parcelable.interpolatorConfig.firstSlope is ignored on the Java side
529                 // parcelable.interpolatorConfig.lastSlope is ignored on the Java side
530                 final int length = parcelable.interpolatorConfig.xy.length;
531                 if (length % 2 != 0) {
532                     throw new android.os.BadParcelableException("xy length must be even");
533                 }
534                 final float[] times = new float[length / 2];
535                 final float[] volumes = new float[length / 2];
536                 for (int i = 0; i < length / 2; ++i) {
537                     times[i] = parcelable.interpolatorConfig.xy[i * 2];
538                     volumes[i] = parcelable.interpolatorConfig.xy[i * 2 + 1];
539                 }
540 
541                 return new VolumeShaper.Configuration(
542                         type,
543                         id,
544                         optionFlags,
545                         durationMs,
546                         interpolatorType,
547                         times,
548                         volumes);
549             }
550         }
551 
552         public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.Configuration> CREATOR
553                 = new Parcelable.Creator<VolumeShaper.Configuration>() {
554             @Override
555             public VolumeShaper.Configuration createFromParcel(Parcel p) {
556                 return fromParcelable(VolumeShaperConfiguration.CREATOR.createFromParcel(p));
557             }
558 
559             @Override
560             public VolumeShaper.Configuration[] newArray(int size) {
561                 return new VolumeShaper.Configuration[size];
562             }
563         };
564 
565         private static @InterpolatorType
interpolatorTypeFromAidl(@ndroid.media.InterpolatorType int aidl)566         int interpolatorTypeFromAidl(@android.media.InterpolatorType int aidl) {
567             switch (aidl) {
568                 case android.media.InterpolatorType.STEP:
569                     return INTERPOLATOR_TYPE_STEP;
570                 case android.media.InterpolatorType.LINEAR:
571                     return INTERPOLATOR_TYPE_LINEAR;
572                 case android.media.InterpolatorType.CUBIC:
573                     return INTERPOLATOR_TYPE_CUBIC;
574                 case android.media.InterpolatorType.CUBIC_MONOTONIC:
575                     return INTERPOLATOR_TYPE_CUBIC_MONOTONIC;
576                 default:
577                     throw new BadParcelableException("Unknown interpolator type");
578             }
579         }
580 
581         private static @android.media.InterpolatorType
interpolatorTypeToAidl(@nterpolatorType int type)582         int interpolatorTypeToAidl(@InterpolatorType int type) {
583             switch (type) {
584                 case INTERPOLATOR_TYPE_STEP:
585                     return android.media.InterpolatorType.STEP;
586                 case INTERPOLATOR_TYPE_LINEAR:
587                     return android.media.InterpolatorType.LINEAR;
588                 case INTERPOLATOR_TYPE_CUBIC:
589                     return android.media.InterpolatorType.CUBIC;
590                 case INTERPOLATOR_TYPE_CUBIC_MONOTONIC:
591                     return android.media.InterpolatorType.CUBIC_MONOTONIC;
592                 default:
593                     throw new RuntimeException("Unknown interpolator type");
594             }
595         }
596 
597         private static @Type
typeFromAidl(@ndroid.media.VolumeShaperConfigurationType int aidl)598         int typeFromAidl(@android.media.VolumeShaperConfigurationType int aidl) {
599             switch (aidl) {
600                 case VolumeShaperConfigurationType.ID:
601                     return TYPE_ID;
602                 case VolumeShaperConfigurationType.SCALE:
603                     return TYPE_SCALE;
604                 default:
605                     throw new BadParcelableException("Unknown type");
606             }
607         }
608 
609         private static @android.media.VolumeShaperConfigurationType
typeToAidl(@ype int type)610         int typeToAidl(@Type int type) {
611             switch (type) {
612                 case TYPE_ID:
613                     return VolumeShaperConfigurationType.ID;
614                 case TYPE_SCALE:
615                     return VolumeShaperConfigurationType.SCALE;
616                 default:
617                     throw new RuntimeException("Unknown type");
618             }
619         }
620 
optionFlagsFromAidl(int aidl)621         private static int optionFlagsFromAidl(int aidl) {
622             int result = 0;
623             if ((aidl & (1 << VolumeShaperConfigurationOptionFlag.VOLUME_IN_DBFS)) != 0) {
624                 result |= OPTION_FLAG_VOLUME_IN_DBFS;
625             }
626             if ((aidl & (1 << VolumeShaperConfigurationOptionFlag.CLOCK_TIME)) != 0) {
627                 result |= OPTION_FLAG_CLOCK_TIME;
628             }
629             return result;
630         }
631 
optionFlagsToAidl(int flags)632         private static int optionFlagsToAidl(int flags) {
633             int result = 0;
634             if ((flags & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
635                 result |= (1 << VolumeShaperConfigurationOptionFlag.VOLUME_IN_DBFS);
636             }
637             if ((flags & OPTION_FLAG_CLOCK_TIME) != 0) {
638                 result |= (1 << VolumeShaperConfigurationOptionFlag.CLOCK_TIME);
639             }
640             return result;
641         }
642 
643         /**
644          * @hide
645          * Constructs a {@code VolumeShaper} from an id.
646          *
647          * This is an opaque handle for controlling a {@code VolumeShaper} that has
648          * already been sent to a player.  The {@code id} is returned from the
649          * initial {@code setVolumeShaper()} call on success.
650          *
651          * These configurations are for native use only,
652          * they are never returned directly to the user.
653          *
654          * @param id
655          * @throws IllegalArgumentException if id is negative.
656          */
Configuration(int id)657         public Configuration(int id) {
658             if (id < 0) {
659                 throw new IllegalArgumentException("negative id " + id);
660             }
661             mType = TYPE_ID;
662             mId = id;
663             mInterpolatorType = 0;
664             mOptionFlags = 0;
665             mDurationMs = 0;
666             mTimes = null;
667             mVolumes = null;
668         }
669 
670         /**
671          * Direct constructor for VolumeShaper.
672          * Use the Builder instead.
673          */
674         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Configuration(@ype int type, int id, @OptionFlag int optionFlags, double durationMs, @InterpolatorType int interpolatorType, @NonNull float[] times, @NonNull float[] volumes)675         private Configuration(@Type int type,
676                 int id,
677                 @OptionFlag int optionFlags,
678                 double durationMs,
679                 @InterpolatorType int interpolatorType,
680                 @NonNull float[] times,
681                 @NonNull float[] volumes) {
682             mType = type;
683             mId = id;
684             mOptionFlags = optionFlags;
685             mDurationMs = durationMs;
686             mInterpolatorType = interpolatorType;
687             // Builder should have cloned these arrays already.
688             mTimes = times;
689             mVolumes = volumes;
690         }
691 
692         /**
693          * @hide
694          * Returns the {@code VolumeShaper} type.
695          */
getType()696         public @Type int getType() {
697             return mType;
698         }
699 
700         /**
701          * @hide
702          * Returns the {@code VolumeShaper} id.
703          */
getId()704         public int getId() {
705             return mId;
706         }
707 
708         /**
709          * Returns the interpolator type.
710          */
getInterpolatorType()711         public @InterpolatorType int getInterpolatorType() {
712             return mInterpolatorType;
713         }
714 
715         /**
716          * @hide
717          * Returns the option flags
718          */
getOptionFlags()719         public @OptionFlag int getOptionFlags() {
720             return mOptionFlags & OPTION_FLAG_PUBLIC_ALL;
721         }
722 
getAllOptionFlags()723         /* package */ @OptionFlag int getAllOptionFlags() {
724             return mOptionFlags;
725         }
726 
727         /**
728          * Returns the duration of the volume shape in milliseconds.
729          */
getDuration()730         public long getDuration() {
731             // casting is safe here as the duration was set as a long in the Builder
732             return (long) mDurationMs;
733         }
734 
735         /**
736          * Returns the times (x) coordinate array of the volume curve points.
737          */
getTimes()738         public float[] getTimes() {
739             return mTimes;
740         }
741 
742         /**
743          * Returns the volumes (y) coordinate array of the volume curve points.
744          */
getVolumes()745         public float[] getVolumes() {
746             return mVolumes;
747         }
748 
749         /**
750          * Checks the validity of times and volumes point representation.
751          *
752          * {@code times[]} and {@code volumes[]} are two arrays representing points
753          * for the volume curve.
754          *
755          * Note that {@code times[]} and {@code volumes[]} are explicitly checked against
756          * null here to provide the proper error string - those are legitimate
757          * arguments to this method.
758          *
759          * @param times the x coordinates for the points,
760          *        must be between 0.f and 1.f and be monotonic.
761          * @param volumes the y coordinates for the points,
762          *        must be between 0.f and 1.f for linear and
763          *        must be no greater than 0.f for log (dBFS).
764          * @param log set to true if the scale is logarithmic.
765          * @return null if no error, or the reason in a {@code String} for an error.
766          */
checkCurveForErrors( @ullable float[] times, @Nullable float[] volumes, boolean log)767         private static @Nullable String checkCurveForErrors(
768                 @Nullable float[] times, @Nullable float[] volumes, boolean log) {
769             if (times == null) {
770                 return "times array must be non-null";
771             } else if (volumes == null) {
772                 return "volumes array must be non-null";
773             } else if (times.length != volumes.length) {
774                 return "array length must match";
775             } else if (times.length < 2) {
776                 return "array length must be at least 2";
777             } else if (times.length > MAXIMUM_CURVE_POINTS) {
778                 return "array length must be no larger than " + MAXIMUM_CURVE_POINTS;
779             } else if (times[0] != 0.f) {
780                 return "times must start at 0.f";
781             } else if (times[times.length - 1] != 1.f) {
782                 return "times must end at 1.f";
783             }
784 
785             // validate points along the curve
786             for (int i = 1; i < times.length; ++i) {
787                 if (!(times[i] > times[i - 1]) /* handle nan */) {
788                     return "times not monotonic increasing, check index " + i;
789                 }
790             }
791             if (log) {
792                 for (int i = 0; i < volumes.length; ++i) {
793                     if (!(volumes[i] <= 0.f) /* handle nan */) {
794                         return "volumes for log scale cannot be positive, "
795                                 + "check index " + i;
796                     }
797                 }
798             } else {
799                 for (int i = 0; i < volumes.length; ++i) {
800                     if (!(volumes[i] >= 0.f) || !(volumes[i] <= 1.f) /* handle nan */) {
801                         return "volumes for linear scale must be between 0.f and 1.f, "
802                                 + "check index " + i;
803                     }
804                 }
805             }
806             return null; // no errors
807         }
808 
checkCurveForErrorsAndThrowException( @ullable float[] times, @Nullable float[] volumes, boolean log, boolean ise)809         private static void checkCurveForErrorsAndThrowException(
810                 @Nullable float[] times, @Nullable float[] volumes, boolean log, boolean ise) {
811             final String error = checkCurveForErrors(times, volumes, log);
812             if (error != null) {
813                 if (ise) {
814                     throw new IllegalStateException(error);
815                 } else {
816                     throw new IllegalArgumentException(error);
817                 }
818             }
819         }
820 
checkValidVolumeAndThrowException(float volume, boolean log)821         private static void checkValidVolumeAndThrowException(float volume, boolean log) {
822             if (log) {
823                 if (!(volume <= 0.f) /* handle nan */) {
824                     throw new IllegalArgumentException("dbfs volume must be 0.f or less");
825                 }
826             } else {
827                 if (!(volume >= 0.f) || !(volume <= 1.f) /* handle nan */) {
828                     throw new IllegalArgumentException("volume must be >= 0.f and <= 1.f");
829                 }
830             }
831         }
832 
clampVolume(float[] volumes, boolean log)833         private static void clampVolume(float[] volumes, boolean log) {
834             if (log) {
835                 for (int i = 0; i < volumes.length; ++i) {
836                     if (!(volumes[i] <= 0.f) /* handle nan */) {
837                         volumes[i] = 0.f;
838                     }
839                 }
840             } else {
841                 for (int i = 0; i < volumes.length; ++i) {
842                     if (!(volumes[i] >= 0.f) /* handle nan */) {
843                         volumes[i] = 0.f;
844                     } else if (!(volumes[i] <= 1.f)) {
845                         volumes[i] = 1.f;
846                     }
847                 }
848             }
849         }
850 
851         /**
852          * Builder class for a {@link VolumeShaper.Configuration} object.
853          * <p> Here is an example where {@code Builder} is used to define the
854          * {@link VolumeShaper.Configuration}.
855          *
856          * <pre class="prettyprint">
857          * VolumeShaper.Configuration LINEAR_RAMP =
858          *         new VolumeShaper.Configuration.Builder()
859          *             .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
860          *             .setCurve(new float[] { 0.f, 1.f }, // times
861          *                       new float[] { 0.f, 1.f }) // volumes
862          *             .setDuration(1000)
863          *             .build();
864          * </pre>
865          * <p>
866          */
867         public static final class Builder {
868             private int mType = TYPE_SCALE;
869             private int mId = -1; // invalid
870             private int mInterpolatorType = INTERPOLATOR_TYPE_CUBIC;
871             private int mOptionFlags = OPTION_FLAG_CLOCK_TIME;
872             private double mDurationMs = 1000.;
873             private float[] mTimes = null;
874             private float[] mVolumes = null;
875 
876             /**
877              * Constructs a new {@code Builder} with the defaults.
878              */
Builder()879             public Builder() {
880             }
881 
882             /**
883              * Constructs a new {@code Builder} with settings
884              * copied from a given {@code VolumeShaper.Configuration}.
885              * @param configuration prototypical configuration
886              *        which will be reused in the new {@code Builder}.
887              */
Builder(@onNull Configuration configuration)888             public Builder(@NonNull Configuration configuration) {
889                 mType = configuration.getType();
890                 mId = configuration.getId();
891                 mOptionFlags = configuration.getAllOptionFlags();
892                 mInterpolatorType = configuration.getInterpolatorType();
893                 mDurationMs = configuration.getDuration();
894                 mTimes = configuration.getTimes().clone();
895                 mVolumes = configuration.getVolumes().clone();
896             }
897 
898             /**
899              * @hide
900              * Set the {@code id} for system defined shapers.
901              * @param id the {@code id} to set. If non-negative, then it is used.
902              *        If -1, then the system is expected to assign one.
903              * @return the same {@code Builder} instance.
904              * @throws IllegalArgumentException if {@code id} < -1.
905              */
setId(int id)906             public @NonNull Builder setId(int id) {
907                 if (id < -1) {
908                     throw new IllegalArgumentException("invalid id: " + id);
909                 }
910                 mId = id;
911                 return this;
912             }
913 
914             /**
915              * Sets the interpolator type.
916              *
917              * If omitted the default interpolator type is {@link #INTERPOLATOR_TYPE_CUBIC}.
918              *
919              * @param interpolatorType method of interpolation used for the volume curve.
920              *        One of {@link #INTERPOLATOR_TYPE_STEP},
921              *        {@link #INTERPOLATOR_TYPE_LINEAR},
922              *        {@link #INTERPOLATOR_TYPE_CUBIC},
923              *        {@link #INTERPOLATOR_TYPE_CUBIC_MONOTONIC}.
924              * @return the same {@code Builder} instance.
925              * @throws IllegalArgumentException if {@code interpolatorType} is not valid.
926              */
setInterpolatorType(@nterpolatorType int interpolatorType)927             public @NonNull Builder setInterpolatorType(@InterpolatorType int interpolatorType) {
928                 switch (interpolatorType) {
929                     case INTERPOLATOR_TYPE_STEP:
930                     case INTERPOLATOR_TYPE_LINEAR:
931                     case INTERPOLATOR_TYPE_CUBIC:
932                     case INTERPOLATOR_TYPE_CUBIC_MONOTONIC:
933                         mInterpolatorType = interpolatorType;
934                         break;
935                     default:
936                         throw new IllegalArgumentException("invalid interpolatorType: "
937                                 + interpolatorType);
938                 }
939                 return this;
940             }
941 
942             /**
943              * @hide
944              * Sets the optional flags
945              *
946              * If omitted, flags are 0. If {@link #OPTION_FLAG_VOLUME_IN_DBFS} has
947              * changed the volume curve needs to be set again as the acceptable
948              * volume domain has changed.
949              *
950              * @param optionFlags new value to replace the old {@code optionFlags}.
951              * @return the same {@code Builder} instance.
952              * @throws IllegalArgumentException if flag is not recognized.
953              */
954             @TestApi
setOptionFlags(@ptionFlag int optionFlags)955             public @NonNull Builder setOptionFlags(@OptionFlag int optionFlags) {
956                 if ((optionFlags & ~OPTION_FLAG_PUBLIC_ALL) != 0) {
957                     throw new IllegalArgumentException("invalid bits in flag: " + optionFlags);
958                 }
959                 mOptionFlags = mOptionFlags & ~OPTION_FLAG_PUBLIC_ALL | optionFlags;
960                 return this;
961             }
962 
963             /**
964              * Sets the {@code VolumeShaper} duration in milliseconds.
965              *
966              * If omitted, the default duration is 1 second.
967              *
968              * @param durationMillis
969              * @return the same {@code Builder} instance.
970              * @throws IllegalArgumentException if {@code durationMillis}
971              *         is not strictly positive.
972              */
setDuration(long durationMillis)973             public @NonNull Builder setDuration(long durationMillis) {
974                 if (durationMillis <= 0) {
975                     throw new IllegalArgumentException(
976                             "duration: " + durationMillis + " not positive");
977                 }
978                 mDurationMs = (double) durationMillis;
979                 return this;
980             }
981 
982             /**
983              * Sets the volume curve.
984              *
985              * The volume curve is represented by a set of control points given by
986              * two float arrays of equal length,
987              * one representing the time (x) coordinates
988              * and one corresponding to the volume (y) coordinates.
989              * The length must be at least 2
990              * and no greater than {@link VolumeShaper.Configuration#getMaximumCurvePoints()}.
991              * <p>
992              * The volume curve is normalized as follows:
993              * time (x) coordinates should be monotonically increasing, from 0.f to 1.f;
994              * volume (y) coordinates must be within 0.f to 1.f.
995              * <p>
996              * The time scale is set by {@link #setDuration}.
997              * <p>
998              * @param times an array of float values representing
999              *        the time line of the volume curve.
1000              * @param volumes an array of float values representing
1001              *        the amplitude of the volume curve.
1002              * @return the same {@code Builder} instance.
1003              * @throws IllegalArgumentException if {@code times} or {@code volumes} is invalid.
1004              */
1005 
1006             /* Note: volume (y) coordinates must be non-positive for log scaling,
1007              * if {@link VolumeShaper.Configuration#OPTION_FLAG_VOLUME_IN_DBFS} is set.
1008              */
1009 
setCurve(@onNull float[] times, @NonNull float[] volumes)1010             public @NonNull Builder setCurve(@NonNull float[] times, @NonNull float[] volumes) {
1011                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
1012                 checkCurveForErrorsAndThrowException(times, volumes, log, false /* ise */);
1013                 mTimes = times.clone();
1014                 mVolumes = volumes.clone();
1015                 return this;
1016             }
1017 
1018             /**
1019              * Reflects the volume curve so that
1020              * the shaper changes volume from the end
1021              * to the start.
1022              *
1023              * @return the same {@code Builder} instance.
1024              * @throws IllegalStateException if curve has not been set.
1025              */
reflectTimes()1026             public @NonNull Builder reflectTimes() {
1027                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
1028                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
1029                 int i;
1030                 for (i = 0; i < mTimes.length / 2; ++i) {
1031                     float temp = mTimes[i];
1032                     mTimes[i] = 1.f - mTimes[mTimes.length - 1 - i];
1033                     mTimes[mTimes.length - 1 - i] = 1.f - temp;
1034                     temp = mVolumes[i];
1035                     mVolumes[i] = mVolumes[mVolumes.length - 1 - i];
1036                     mVolumes[mVolumes.length - 1 - i] = temp;
1037                 }
1038                 if ((mTimes.length & 1) != 0) {
1039                     mTimes[i] = 1.f - mTimes[i];
1040                 }
1041                 return this;
1042             }
1043 
1044             /**
1045              * Inverts the volume curve so that the max volume
1046              * becomes the min volume and vice versa.
1047              *
1048              * @return the same {@code Builder} instance.
1049              * @throws IllegalStateException if curve has not been set.
1050              */
invertVolumes()1051             public @NonNull Builder invertVolumes() {
1052                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
1053                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
1054                 float min = mVolumes[0];
1055                 float max = mVolumes[0];
1056                 for (int i = 1; i < mVolumes.length; ++i) {
1057                     if (mVolumes[i] < min) {
1058                         min = mVolumes[i];
1059                     } else if (mVolumes[i] > max) {
1060                         max = mVolumes[i];
1061                     }
1062                 }
1063 
1064                 final float maxmin = max + min;
1065                 for (int i = 0; i < mVolumes.length; ++i) {
1066                     mVolumes[i] = maxmin - mVolumes[i];
1067                 }
1068                 return this;
1069             }
1070 
1071             /**
1072              * Scale the curve end volume to a target value.
1073              *
1074              * Keeps the start volume the same.
1075              * This works best if the volume curve is monotonic.
1076              *
1077              * @param volume the target end volume to use.
1078              * @return the same {@code Builder} instance.
1079              * @throws IllegalArgumentException if {@code volume} is not valid.
1080              * @throws IllegalStateException if curve has not been set.
1081              */
scaleToEndVolume(float volume)1082             public @NonNull Builder scaleToEndVolume(float volume) {
1083                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
1084                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
1085                 checkValidVolumeAndThrowException(volume, log);
1086                 final float startVolume = mVolumes[0];
1087                 final float endVolume = mVolumes[mVolumes.length - 1];
1088                 if (endVolume == startVolume) {
1089                     // match with linear ramp
1090                     final float offset = volume - startVolume;
1091                     for (int i = 0; i < mVolumes.length; ++i) {
1092                         mVolumes[i] = mVolumes[i] + offset * mTimes[i];
1093                     }
1094                 } else {
1095                     // scale
1096                     final float scale = (volume - startVolume) / (endVolume - startVolume);
1097                     for (int i = 0; i < mVolumes.length; ++i) {
1098                         mVolumes[i] = scale * (mVolumes[i] - startVolume) + startVolume;
1099                     }
1100                 }
1101                 clampVolume(mVolumes, log);
1102                 return this;
1103             }
1104 
1105             /**
1106              * Scale the curve start volume to a target value.
1107              *
1108              * Keeps the end volume the same.
1109              * This works best if the volume curve is monotonic.
1110              *
1111              * @param volume the target start volume to use.
1112              * @return the same {@code Builder} instance.
1113              * @throws IllegalArgumentException if {@code volume} is not valid.
1114              * @throws IllegalStateException if curve has not been set.
1115              */
scaleToStartVolume(float volume)1116             public @NonNull Builder scaleToStartVolume(float volume) {
1117                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
1118                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
1119                 checkValidVolumeAndThrowException(volume, log);
1120                 final float startVolume = mVolumes[0];
1121                 final float endVolume = mVolumes[mVolumes.length - 1];
1122                 if (endVolume == startVolume) {
1123                     // match with linear ramp
1124                     final float offset = volume - startVolume;
1125                     for (int i = 0; i < mVolumes.length; ++i) {
1126                         mVolumes[i] = mVolumes[i] + offset * (1.f - mTimes[i]);
1127                     }
1128                 } else {
1129                     final float scale = (volume - endVolume) / (startVolume - endVolume);
1130                     for (int i = 0; i < mVolumes.length; ++i) {
1131                         mVolumes[i] = scale * (mVolumes[i] - endVolume) + endVolume;
1132                     }
1133                 }
1134                 clampVolume(mVolumes, log);
1135                 return this;
1136             }
1137 
1138             /**
1139              * Builds a new {@link VolumeShaper} object.
1140              *
1141              * @return a new {@link VolumeShaper} object.
1142              * @throws IllegalStateException if curve is not properly set.
1143              */
build()1144             public @NonNull Configuration build() {
1145                 final boolean log = (mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0;
1146                 checkCurveForErrorsAndThrowException(mTimes, mVolumes, log, true /* ise */);
1147                 return new Configuration(mType, mId, mOptionFlags, mDurationMs,
1148                         mInterpolatorType, mTimes, mVolumes);
1149             }
1150         } // Configuration.Builder
1151     } // Configuration
1152 
1153     /**
1154      * The {@code VolumeShaper.Operation} class is used to specify operations
1155      * to the {@code VolumeShaper} that affect the volume change.
1156      */
1157     public static final class Operation implements Parcelable {
1158         /**
1159          * Forward playback from current volume time position.
1160          * At the end of the {@code VolumeShaper} curve,
1161          * the last volume value persists.
1162          */
1163         public static final Operation PLAY =
1164                 new VolumeShaper.Operation.Builder()
1165                     .build();
1166 
1167         /**
1168          * Reverse playback from current volume time position.
1169          * When the position reaches the start of the {@code VolumeShaper} curve,
1170          * the first volume value persists.
1171          */
1172         public static final Operation REVERSE =
1173                 new VolumeShaper.Operation.Builder()
1174                     .reverse()
1175                     .build();
1176 
1177         // No user serviceable parts below.
1178 
1179         // These flags must match the native VolumeShaper::Operation::Flag
1180         /** @hide */
1181         @IntDef({
1182             FLAG_NONE,
1183             FLAG_REVERSE,
1184             FLAG_TERMINATE,
1185             FLAG_JOIN,
1186             FLAG_DEFER,
1187             })
1188         @Retention(RetentionPolicy.SOURCE)
1189         public @interface Flag {}
1190 
1191         /**
1192          * No special {@code VolumeShaper} operation.
1193          */
1194         private static final int FLAG_NONE = 0;
1195 
1196         /**
1197          * Reverse the {@code VolumeShaper} progress.
1198          *
1199          * Reverses the {@code VolumeShaper} curve from its current
1200          * position. If the {@code VolumeShaper} curve has not started,
1201          * it automatically is considered finished.
1202          */
1203         private static final int FLAG_REVERSE = 1 << 0;
1204 
1205         /**
1206          * Terminate the existing {@code VolumeShaper}.
1207          * This flag is generally used by itself;
1208          * it takes precedence over all other flags.
1209          */
1210         private static final int FLAG_TERMINATE = 1 << 1;
1211 
1212         /**
1213          * Attempt to join as best as possible to the previous {@code VolumeShaper}.
1214          * This requires the previous {@code VolumeShaper} to be active and
1215          * {@link #setReplaceId} to be set.
1216          */
1217         private static final int FLAG_JOIN = 1 << 2;
1218 
1219         /**
1220          * Defer playback until next operation is sent. This is used
1221          * when starting a {@code VolumeShaper} effect.
1222          */
1223         private static final int FLAG_DEFER = 1 << 3;
1224 
1225         /**
1226          * Use the id specified in the configuration, creating
1227          * {@code VolumeShaper} as needed; the configuration should be
1228          * TYPE_SCALE.
1229          */
1230         private static final int FLAG_CREATE_IF_NEEDED = 1 << 4;
1231 
1232         private static final int FLAG_PUBLIC_ALL = FLAG_REVERSE | FLAG_TERMINATE;
1233 
1234         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1235         private final int mFlags;
1236         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1237         private final int mReplaceId;
1238         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1239         private final float mXOffset;
1240 
1241         @Override
toString()1242         public String toString() {
1243             return "VolumeShaper.Operation{"
1244                     + "mFlags = 0x" + Integer.toHexString(mFlags).toUpperCase()
1245                     + ", mReplaceId = " + mReplaceId
1246                     + ", mXOffset = " + mXOffset
1247                     + "}";
1248         }
1249 
1250         @Override
hashCode()1251         public int hashCode() {
1252             return Objects.hash(mFlags, mReplaceId, mXOffset);
1253         }
1254 
1255         @Override
equals(Object o)1256         public boolean equals(Object o) {
1257             if (!(o instanceof Operation)) return false;
1258             if (o == this) return true;
1259             final Operation other = (Operation) o;
1260 
1261             return mFlags == other.mFlags
1262                     && mReplaceId == other.mReplaceId
1263                     && Float.compare(mXOffset, other.mXOffset) == 0;
1264         }
1265 
1266         @Override
describeContents()1267         public int describeContents() {
1268             return 0;
1269         }
1270 
1271         @Override
writeToParcel(Parcel dest, int flags)1272         public void writeToParcel(Parcel dest, int flags) {
1273             toParcelable().writeToParcel(dest, flags);
1274         }
1275 
1276         /** @hide */
toParcelable()1277         public VolumeShaperOperation toParcelable() {
1278             VolumeShaperOperation result = new VolumeShaperOperation();
1279             result.flags = flagsToAidl(mFlags);
1280             result.replaceId = mReplaceId;
1281             result.xOffset = mXOffset;
1282             return result;
1283         }
1284 
1285         /** @hide */
fromParcelable(VolumeShaperOperation parcelable)1286         public static Operation fromParcelable(VolumeShaperOperation parcelable) {
1287             return new VolumeShaper.Operation(
1288                     flagsFromAidl(parcelable.flags),
1289                     parcelable.replaceId,
1290                     parcelable.xOffset);
1291         }
1292 
1293         public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.Operation> CREATOR
1294                 = new Parcelable.Creator<VolumeShaper.Operation>() {
1295             @Override
1296             public VolumeShaper.Operation createFromParcel(Parcel p) {
1297                 return fromParcelable(VolumeShaperOperation.CREATOR.createFromParcel(p));
1298             }
1299 
1300             @Override
1301             public VolumeShaper.Operation[] newArray(int size) {
1302                 return new VolumeShaper.Operation[size];
1303             }
1304         };
1305 
flagsFromAidl(int aidl)1306         private static int flagsFromAidl(int aidl) {
1307             int result = 0;
1308             if ((aidl & (1 << VolumeShaperOperationFlag.REVERSE)) != 0) {
1309                 result |= FLAG_REVERSE;
1310             }
1311             if ((aidl & (1 << VolumeShaperOperationFlag.TERMINATE)) != 0) {
1312                 result |= FLAG_TERMINATE;
1313             }
1314             if ((aidl & (1 << VolumeShaperOperationFlag.JOIN)) != 0) {
1315                 result |= FLAG_JOIN;
1316             }
1317             if ((aidl & (1 << VolumeShaperOperationFlag.DELAY)) != 0) {
1318                 result |= FLAG_DEFER;
1319             }
1320             if ((aidl & (1 << VolumeShaperOperationFlag.CREATE_IF_NECESSARY)) != 0) {
1321                 result |= FLAG_CREATE_IF_NEEDED;
1322             }
1323             return result;
1324         }
1325 
flagsToAidl(int flags)1326         private static int flagsToAidl(int flags) {
1327             int result = 0;
1328             if ((flags & FLAG_REVERSE) != 0) {
1329                 result |= (1 << VolumeShaperOperationFlag.REVERSE);
1330             }
1331             if ((flags & FLAG_TERMINATE) != 0) {
1332                 result |= (1 << VolumeShaperOperationFlag.TERMINATE);
1333             }
1334             if ((flags & FLAG_JOIN) != 0) {
1335                 result |= (1 << VolumeShaperOperationFlag.JOIN);
1336             }
1337             if ((flags & FLAG_DEFER) != 0) {
1338                 result |= (1 << VolumeShaperOperationFlag.DELAY);
1339             }
1340             if ((flags & FLAG_CREATE_IF_NEEDED) != 0) {
1341                 result |= (1 << VolumeShaperOperationFlag.CREATE_IF_NECESSARY);
1342             }
1343             return result;
1344         }
1345 
1346         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Operation(@lag int flags, int replaceId, float xOffset)1347         private Operation(@Flag int flags, int replaceId, float xOffset) {
1348             mFlags = flags;
1349             mReplaceId = replaceId;
1350             mXOffset = xOffset;
1351         }
1352 
1353         /**
1354          * @hide
1355          * {@code Builder} class for {@link VolumeShaper.Operation} object.
1356          *
1357          * Not for public use.
1358          */
1359         public static final class Builder {
1360             int mFlags;
1361             int mReplaceId;
1362             float mXOffset;
1363 
1364             /**
1365              * Constructs a new {@code Builder} with the defaults.
1366              */
Builder()1367             public Builder() {
1368                 mFlags = 0;
1369                 mReplaceId = -1;
1370                 mXOffset = Float.NaN;
1371             }
1372 
1373             /**
1374              * Constructs a new {@code Builder} from a given {@code VolumeShaper.Operation}
1375              * @param operation the {@code VolumeShaper.operation} whose data will be
1376              *        reused in the new {@code Builder}.
1377              */
Builder(@onNull VolumeShaper.Operation operation)1378             public Builder(@NonNull VolumeShaper.Operation operation) {
1379                 mReplaceId = operation.mReplaceId;
1380                 mFlags = operation.mFlags;
1381                 mXOffset = operation.mXOffset;
1382             }
1383 
1384             /**
1385              * Replaces the previous {@code VolumeShaper} specified by {@code id}.
1386              *
1387              * The {@code VolumeShaper} specified by the {@code id} is removed
1388              * if it exists. The configuration should be TYPE_SCALE.
1389              *
1390              * @param id the {@code id} of the previous {@code VolumeShaper}.
1391              * @param join if true, match the volume of the previous
1392              * shaper to the start volume of the new {@code VolumeShaper}.
1393              * @return the same {@code Builder} instance.
1394              */
replace(int id, boolean join)1395             public @NonNull Builder replace(int id, boolean join) {
1396                 mReplaceId = id;
1397                 if (join) {
1398                     mFlags |= FLAG_JOIN;
1399                 } else {
1400                     mFlags &= ~FLAG_JOIN;
1401                 }
1402                 return this;
1403             }
1404 
1405             /**
1406              * Defers all operations.
1407              * @return the same {@code Builder} instance.
1408              */
defer()1409             public @NonNull Builder defer() {
1410                 mFlags |= FLAG_DEFER;
1411                 return this;
1412             }
1413 
1414             /**
1415              * Terminates the {@code VolumeShaper}.
1416              *
1417              * Do not call directly, use {@link VolumeShaper#close()}.
1418              * @return the same {@code Builder} instance.
1419              */
terminate()1420             public @NonNull Builder terminate() {
1421                 mFlags |= FLAG_TERMINATE;
1422                 return this;
1423             }
1424 
1425             /**
1426              * Reverses direction.
1427              * @return the same {@code Builder} instance.
1428              */
reverse()1429             public @NonNull Builder reverse() {
1430                 mFlags ^= FLAG_REVERSE;
1431                 return this;
1432             }
1433 
1434             /**
1435              * Use the id specified in the configuration, creating
1436              * {@code VolumeShaper} only as needed; the configuration should be
1437              * TYPE_SCALE.
1438              *
1439              * If the {@code VolumeShaper} with the same id already exists
1440              * then the operation has no effect.
1441              *
1442              * @return the same {@code Builder} instance.
1443              */
createIfNeeded()1444             public @NonNull Builder createIfNeeded() {
1445                 mFlags |= FLAG_CREATE_IF_NEEDED;
1446                 return this;
1447             }
1448 
1449             /**
1450              * Sets the {@code xOffset} to use for the {@code VolumeShaper}.
1451              *
1452              * The {@code xOffset} is the position on the volume curve,
1453              * and setting takes effect when the {@code VolumeShaper} is used next.
1454              *
1455              * @param xOffset a value between (or equal to) 0.f and 1.f, or Float.NaN to ignore.
1456              * @return the same {@code Builder} instance.
1457              * @throws IllegalArgumentException if {@code xOffset} is not between 0.f and 1.f,
1458              *         or a Float.NaN.
1459              */
setXOffset(float xOffset)1460             public @NonNull Builder setXOffset(float xOffset) {
1461                 if (xOffset < -0.f) {
1462                     throw new IllegalArgumentException("Negative xOffset not allowed");
1463                 } else if (xOffset > 1.f) {
1464                     throw new IllegalArgumentException("xOffset > 1.f not allowed");
1465                 }
1466                 // Float.NaN passes through
1467                 mXOffset = xOffset;
1468                 return this;
1469             }
1470 
1471             /**
1472              * Sets the operation flag.  Do not call this directly but one of the
1473              * other builder methods.
1474              *
1475              * @param flags new value for {@code flags}, consisting of ORed flags.
1476              * @return the same {@code Builder} instance.
1477              * @throws IllegalArgumentException if {@code flags} contains invalid set bits.
1478              */
setFlags(@lag int flags)1479             private @NonNull Builder setFlags(@Flag int flags) {
1480                 if ((flags & ~FLAG_PUBLIC_ALL) != 0) {
1481                     throw new IllegalArgumentException("flag has unknown bits set: " + flags);
1482                 }
1483                 mFlags = mFlags & ~FLAG_PUBLIC_ALL | flags;
1484                 return this;
1485             }
1486 
1487             /**
1488              * Builds a new {@link VolumeShaper.Operation} object.
1489              *
1490              * @return a new {@code VolumeShaper.Operation} object
1491              */
build()1492             public @NonNull Operation build() {
1493                 return new Operation(mFlags, mReplaceId, mXOffset);
1494             }
1495         } // Operation.Builder
1496     } // Operation
1497 
1498     /**
1499      * @hide
1500      * {@code VolumeShaper.State} represents the current progress
1501      * of the {@code VolumeShaper}.
1502      *
1503      *  Not for public use.
1504      */
1505     public static final class State implements Parcelable {
1506         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1507         private float mVolume;
1508         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1509         private float mXOffset;
1510 
1511         @Override
toString()1512         public String toString() {
1513             return "VolumeShaper.State{"
1514                     + "mVolume = " + mVolume
1515                     + ", mXOffset = " + mXOffset
1516                     + "}";
1517         }
1518 
1519         @Override
hashCode()1520         public int hashCode() {
1521             return Objects.hash(mVolume, mXOffset);
1522         }
1523 
1524         @Override
equals(Object o)1525         public boolean equals(Object o) {
1526             if (!(o instanceof State)) return false;
1527             if (o == this) return true;
1528             final State other = (State) o;
1529             return mVolume == other.mVolume
1530                     && mXOffset == other.mXOffset;
1531         }
1532 
1533         @Override
describeContents()1534         public int describeContents() {
1535             return 0;
1536         }
1537 
1538         @Override
writeToParcel(Parcel dest, int flags)1539         public void writeToParcel(Parcel dest, int flags) {
1540             toParcelable().writeToParcel(dest, flags);
1541         }
1542 
1543         /** @hide */
toParcelable()1544         public VolumeShaperState toParcelable() {
1545             VolumeShaperState result = new VolumeShaperState();
1546             result.volume = mVolume;
1547             result.xOffset = mXOffset;
1548             return result;
1549         }
1550 
1551         /** @hide */
fromParcelable(VolumeShaperState p)1552         public static State fromParcelable(VolumeShaperState p) {
1553             return new VolumeShaper.State(p.volume, p.xOffset);
1554         }
1555 
1556         public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.State> CREATOR
1557                 = new Parcelable.Creator<VolumeShaper.State>() {
1558             @Override
1559             public VolumeShaper.State createFromParcel(Parcel p) {
1560                 return fromParcelable(VolumeShaperState.CREATOR.createFromParcel(p));
1561             }
1562 
1563             @Override
1564             public VolumeShaper.State[] newArray(int size) {
1565                 return new VolumeShaper.State[size];
1566             }
1567         };
1568 
1569         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
State(float volume, float xOffset)1570         /* package */ State(float volume, float xOffset) {
1571             mVolume = volume;
1572             mXOffset = xOffset;
1573         }
1574 
1575         /**
1576          * Gets the volume of the {@link VolumeShaper.State}.
1577          * @return linear volume between 0.f and 1.f.
1578          */
getVolume()1579         public float getVolume() {
1580             return mVolume;
1581         }
1582 
1583         /**
1584          * Gets the {@code xOffset} position on the normalized curve
1585          * of the {@link VolumeShaper.State}.
1586          * @return the curve x position between 0.f and 1.f.
1587          */
getXOffset()1588         public float getXOffset() {
1589             return mXOffset;
1590         }
1591     } // State
1592 }
1593