diff --git a/core/api/system-current.txt b/core/api/system-current.txt index dbd6cac45e9e7..5e4834a04a83f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5274,11 +5274,12 @@ package android.hardware.radio { method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1 + field public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; // 0xe field public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; // 0x6 field public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; // 0x8 field public static final int IDENTIFIER_TYPE_DAB_SCID = 7; // 0x7 - field public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5 - field public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5 + field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5 + field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5 field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 @@ -5315,7 +5316,7 @@ package android.hardware.radio { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } - @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { + @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType { } @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType { @@ -5530,30 +5531,31 @@ package android.hardware.radio { public abstract class RadioTuner { ctor public RadioTuner(); - method public abstract int cancel(); - method public abstract void cancelAnnouncement(); - method public abstract void close(); - method @Deprecated public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); - method @Nullable public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter); - method public abstract boolean getMute(); - method @NonNull public java.util.Map getParameters(@NonNull java.util.List); - method @Deprecated public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); - method @Deprecated @NonNull public abstract java.util.List getProgramList(@Nullable java.util.Map); - method public abstract boolean hasControl(); - method @Deprecated public abstract boolean isAnalogForced(); - method @Deprecated public abstract boolean isAntennaConnected(); - method public boolean isConfigFlagSet(int); - method public boolean isConfigFlagSupported(int); - method public abstract int scan(int, boolean); - method @Deprecated public abstract void setAnalogForced(boolean); - method public void setConfigFlag(int, boolean); - method @Deprecated public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig); - method public abstract int setMute(boolean); - method @NonNull public java.util.Map setParameters(@NonNull java.util.Map); - method public abstract boolean startBackgroundScan(); - method public abstract int step(int, boolean); - method @Deprecated public abstract int tune(int, int); - method public abstract void tune(@NonNull android.hardware.radio.ProgramSelector); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int cancel(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void cancelAnnouncement(); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void close(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean getMute(); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map getParameters(@NonNull java.util.List); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract java.util.List getProgramList(@Nullable java.util.Map); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean hasControl(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAnalogForced(); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAntennaConnected(); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSet(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSupported(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int scan(int, boolean); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public int seek(int, boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void setAnalogForced(boolean); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void setConfigFlag(int, boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setMute(boolean); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map setParameters(@NonNull java.util.Map); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean startBackgroundScan(); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int step(int, boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int tune(int, int); + method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void tune(@NonNull android.hardware.radio.ProgramSelector); field public static final int DIRECTION_DOWN = 1; // 0x1 field public static final int DIRECTION_UP = 0; // 0x0 field @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6 @@ -5563,6 +5565,14 @@ package android.hardware.radio { field @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0 field @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3 field @Deprecated public static final int ERROR_SERVER_DIED = 1; // 0x1 + field public static final int TUNER_RESULT_CANCELED = 6; // 0x6 + field public static final int TUNER_RESULT_INTERNAL_ERROR = 1; // 0x1 + field public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2; // 0x2 + field public static final int TUNER_RESULT_INVALID_STATE = 3; // 0x3 + field public static final int TUNER_RESULT_NOT_SUPPORTED = 4; // 0x4 + field public static final int TUNER_RESULT_OK = 0; // 0x0 + field public static final int TUNER_RESULT_TIMEOUT = 5; // 0x5 + field public static final int TUNER_RESULT_UNKNOWN_ERROR = 7; // 0x7 } public abstract static class RadioTuner.Callback { @@ -5570,6 +5580,7 @@ package android.hardware.radio { method public void onAntennaState(boolean); method public void onBackgroundScanAvailabilityChange(boolean); method public void onBackgroundScanComplete(); + method public void onConfigFlagUpdated(int, boolean); method @Deprecated public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig); method public void onControlChanged(boolean); method public void onEmergencyAnnouncement(boolean); diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl index 9349cf7d0c327..c7131a7fe9c95 100644 --- a/core/java/android/hardware/radio/IRadioService.aidl +++ b/core/java/android/hardware/radio/IRadioService.aidl @@ -31,7 +31,7 @@ interface IRadioService { List listModules(); ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio, - in ITunerCallback callback); + in ITunerCallback callback, int targetSdkVersion); ICloseHandle addAnnouncementListener(in int[] enabledTypes, in IAnnouncementListener listener); diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index 7bf234b9d5a4b..e68c3cccc074c 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -49,7 +49,7 @@ interface ITuner { /** * @throws IllegalStateException if called out of sequence */ - void scan(boolean directionDown, boolean skipSubChannel); + void seek(boolean directionDown, boolean skipSubChannel); /** * @throws IllegalArgumentException if invalid arguments are passed diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl index f98947b50e345..13092cc66ccd1 100644 --- a/core/java/android/hardware/radio/ITunerCallback.aidl +++ b/core/java/android/hardware/radio/ITunerCallback.aidl @@ -24,6 +24,13 @@ import android.hardware.radio.RadioMetadata; /** {@hide} */ oneway interface ITunerCallback { void onError(int status); + + /** + * Callback called when tuning operations, such as tune, step, seek, failed. + * + * @param result Tuning result of {@link RadioTuner#TunerResultType} type. + * @param selector Program selector used for the tuning operation. + */ void onTuneFailed(int result, in ProgramSelector selector); void onConfigurationChanged(in RadioManager.BandConfig config); void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info); @@ -36,6 +43,18 @@ oneway interface ITunerCallback { void onProgramListUpdated(in ProgramList.Chunk chunk); /** + * Callback for passing updates to config flags from {@link IRadioService} to + * {@link RadioTuner}. + * + * @param flag Config flag (defined in {@link RadioManager.ConfigFlag}) updated + * @param value Updated value for the config flag + */ + void onConfigFlagUpdated(int flag, boolean value); + + /** + * Callback for passing updates to vendor-specific parameter values from + * {@link IRadioService} to {@link RadioTuner}. + * * @param parameters Vendor-specific key-value pairs */ void onParametersUpdated(in Map parameters); diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 8a92135151229..7faa285a8fee9 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -167,7 +167,10 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004; /** * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT} + * + * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead */ + @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; /** * 28bit compound primary identifier for Digital Audio Broadcasting. @@ -183,7 +186,10 @@ public final class ProgramSelector implements Parcelable { * * The remaining bits should be set to zeros when writing on the chip side * and ignored when read. + * + * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead */ + @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC; /** 16bit */ public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; @@ -197,7 +203,7 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; /** * 1: AM, 2:FM - * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead + * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead */ @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; @@ -205,6 +211,23 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; /** 0-999 range */ public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; + /** + * 44bit compound primary identifier for Digital Audio Broadcasting and + * Digital Multimedia Broadcasting. + * + *

Consists of (from the LSB): + * - 32bit: SId; + * - 8bit: ECC code; + * - 4bit: SCIdS. + * + *

SCIdS (Service Component Identifier within the Service) value + * of 0 represents the main service, while 1 and above represents + * secondary services. + * + * The remaining bits should be set to zeros when writing on the chip side + * and ignored when read. + */ + public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; /** * Primary identifier for vendor-specific radio technology. * The value format is determined by a vendor. @@ -219,12 +242,12 @@ public final class ProgramSelector implements Parcelable { */ public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END; /** - * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead + * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead */ @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START; /** - * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead + * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead */ @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END; @@ -245,6 +268,7 @@ public final class ProgramSelector implements Parcelable { IDENTIFIER_TYPE_DRMO_MODULATION, IDENTIFIER_TYPE_SXM_SERVICE_ID, IDENTIFIER_TYPE_SXM_CHANNEL, + IDENTIFIER_TYPE_DAB_DMB_SID_EXT, }) @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END) @Retention(RetentionPolicy.SOURCE) @@ -285,7 +309,7 @@ public final class ProgramSelector implements Parcelable { * Type of a radio technology. * * @return program type. - * @deprecated use {@link getPrimaryId} instead + * @deprecated use {@link #getPrimaryId} instead */ @Deprecated public @ProgramType int getProgramType() { diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 9a217f9b5119a..f072e3b43b4ad 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -82,6 +82,24 @@ public class RadioManager { /** Method return status: time out before operation completion */ public static final int STATUS_TIMED_OUT = -110; + /** + * Radio operation status types + * + * @hide + */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_OK, + STATUS_ERROR, + STATUS_PERMISSION_DENIED, + STATUS_NO_INIT, + STATUS_BAD_VALUE, + STATUS_DEAD_OBJECT, + STATUS_INVALID_OPERATION, + STATUS_TIMED_OUT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RadioStatusType{} + // keep in sync with radio_class_t in /system/core/incluse/system/radio.h /** Radio module class supporting FM (including HD radio) and AM */ @@ -330,6 +348,7 @@ public class RadioManager { * program list. * @return the number of audio sources available. */ + @RadioStatusType public int getNumAudioSources() { return mNumAudioSources; } @@ -1724,6 +1743,7 @@ public class RadioManager { * */ @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioStatusType public int listModules(List modules) { if (modules == null) { Log.e(TAG, "the output list must not be empty"); @@ -1776,7 +1796,7 @@ public class RadioManager { ITuner tuner; TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler); try { - tuner = mService.openTuner(moduleId, config, withAudio, halCallback); + tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion); } catch (RemoteException | IllegalArgumentException | IllegalStateException ex) { Log.e(TAG, "Failed to open tuner", ex); return null; @@ -1853,6 +1873,7 @@ public class RadioManager { @NonNull private final Context mContext; @NonNull private final IRadioService mService; + private final int mTargetSdkVersion; /** * @hide @@ -1869,5 +1890,6 @@ public class RadioManager { public RadioManager(Context context, IRadioService service) { mContext = context; mService = service; + mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; } } diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java index 969db96561d78..9b2bcdea5f302 100644 --- a/core/java/android/hardware/radio/RadioTuner.java +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -16,14 +16,20 @@ package android.hardware.radio; +import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.graphics.Bitmap; import android.os.Handler; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * RadioTuner interface provides methods to control a radio tuner on the device: selecting and @@ -43,16 +49,21 @@ public abstract class RadioTuner { public static final int DIRECTION_DOWN = 1; /** - * Close the tuner interface. The {@link Callback} callback will not be called - * anymore and associated resources will be released. - * Must be called when the tuner is not needed to make hardware resources available to others. + * Close the tuner interface. + * + *

The {@link Callback} callback will not be called anymore and associated resources will be + * released. Must be called when the tuner is not needed to make hardware resources available + * to others. * */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void close(); /** * Set the active band configuration for this module. - * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed - * in the ModuleProperties of the module with the specified ID. + * + *

Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor + * listed in the ModuleProperties of the module with the specified ID. + * * @param config The desired band configuration (FmBandConfig or AmBandConfig). * @return *

    @@ -67,10 +78,13 @@ public abstract class RadioTuner { * @deprecated Only applicable for HAL 1.x. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int setConfiguration(RadioManager.BandConfig config); /** * Get current configuration. + * * @param config a BandConfig array of lengh 1 where the configuration is returned. * @return *
      @@ -86,11 +100,15 @@ public abstract class RadioTuner { * @deprecated Only applicable for HAL 1.x. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int getConfiguration(RadioManager.BandConfig[] config); /** - * Set mute state. When muted, the radio tuner audio source is not available for playback on + * Set mute state. + * + *

      When muted, the radio tuner audio source is not available for playback on * any audio device. when unmuted, the radio tuner audio source is output as a media source * and renderd over the audio device selected for media use case. * The radio tuner audio source is muted by default when the tuner is first attached. @@ -107,6 +125,8 @@ public abstract class RadioTuner { * service fails, *

    */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int setMute(boolean mute); /** @@ -115,13 +135,19 @@ public abstract class RadioTuner { * @return {@code true} if the radio tuner audio source is muted or a problem occured * retrieving the mute state, {@code false} otherwise. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean getMute(); /** * Step up or down by one channel spacing. - * The operation is asynchronous and {@link Callback} - * onProgramInfoChanged() will be called when step completes or - * onError() when cancelled or timeout. + * + *

    The operation is asynchronous and {@link Callback#onProgramInfoChanged} + * will be called when step completes or {@link Callback#onTuneFailed} + * when timeout or canceled. + * + *

    When this operation is called by users other than current user or system user, it is + * ignored silently. + * * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}. * @param skipSubChannel indicates to skip sub channels when the configuration currently * selected supports sub channel (e.g HD Radio). N/A otherwise. @@ -136,13 +162,50 @@ public abstract class RadioTuner { * service fails, *

*/ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int step(int direction, boolean skipSubChannel); /** * Scan up or down to next valid station. - * The operation is asynchronous and {@link Callback} - * onProgramInfoChanged() will be called when scan completes or - * onError() when cancelled or timeout. + * + *

The operation is asynchronous and {@link Callback#onProgramInfoChanged} + * will be called when scan completes or {@link Callback#onTuneFailed} + * when timeout or canceled. + * + *

When this operation is called by users other than current user or system user, it is + * ignored silently. + * + * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}. + * @param skipSubChannel indicates to skip sub channels when the configuration currently + * selected supports sub channel (e.g HD Radio). N/A otherwise. + * @return + *

    + *
  • {@link RadioManager#STATUS_OK} in case of success,
  • + *
  • {@link RadioManager#STATUS_ERROR} in case of unspecified error,
  • + *
  • {@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached,
  • + *
  • {@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid,
  • + *
  • {@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence,
  • + *
  • {@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails,
  • + *
+ * @deprecated Use {@link #seek(int, boolean)} instead. + */ + @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType + public abstract int scan(int direction, boolean skipSubChannel); + + /** + * Seek up or down to next valid station. + * + *

The operation is asynchronous and {@link Callback#onProgramInfoChanged} + * will be called when seek completes or {@link Callback#onTuneFailed} + * when timeout or canceled. + * + *

When this operation is called by users other than current user or system user, it is + * ignore silently. + * * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}. * @param skipSubChannel indicates to skip sub channels when the configuration currently * selected supports sub channel (e.g HD Radio). N/A otherwise. @@ -157,13 +220,22 @@ public abstract class RadioTuner { * service fails, * */ - public abstract int scan(int direction, boolean skipSubChannel); + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType + public int seek(int direction, boolean skipSubChannel) { + throw new UnsupportedOperationException("Seeking is not supported"); + } /** * Tune to a specific frequency. - * The operation is asynchronous and {@link Callback} - * onProgramInfoChanged() will be called when tune completes or - * onError() when cancelled or timeout. + * + *

The operation is asynchronous and {@link Callback#onProgramInfoChanged} + * will be called when tune completes or {@link Callback#onTuneFailed} + * when timeout or canceled. + * + *

When this operation is called by users other than current user or system user, it is + * ignored silently. + * * @param channel the specific channel or frequency to tune to. * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration * does not support cub channels. @@ -177,25 +249,37 @@ public abstract class RadioTuner { *

  • {@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails,
  • * - * @deprecated Use {@link tune(ProgramSelector)} instead. + * @deprecated Use {@link #tune(ProgramSelector)} instead. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int tune(int channel, int subChannel); /** * Tune to a program. * - * The operation is asynchronous and {@link Callback} onProgramInfoChanged() will be called - * when tune completes or onError() when cancelled or on timeout. + *

    The operation is asynchronous and {@link Callback#onProgramInfoChanged} + * will be called when tune completes or {@link Callback#onTuneFailed} + * when timeout or canceled. + * + *

    When this operation is called by users other than current user or system user, it is + * ignored silently. * * @throws IllegalArgumentException if the provided selector is invalid */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void tune(@NonNull ProgramSelector selector); /** * Cancel a pending scan or tune operation. - * If an operation is pending, {@link Callback} onError() will be called with + * + *

    If an operation is pending, {@link Callback#onTuneFailed} will be called with * {@link #ERROR_CANCELLED}. + * + *

    When this operation is called by users other than current user or system + * user, it is ignored silently. + * * @return *

      *
    • {@link RadioManager#STATUS_OK} in case of success,
    • @@ -207,21 +291,27 @@ public abstract class RadioTuner { * service fails, *
    */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int cancel(); /** * Cancels traffic or emergency announcement. * - * If there was no announcement to cancel, no action is taken. + *

    If there was no announcement to cancel, no action is taken. * - * There is a race condition between calling cancelAnnouncement and the actual announcement + *

    There is a race condition between calling cancelAnnouncement and the actual announcement * being finished, so onTrafficAnnouncement / onEmergencyAnnouncement callback should be * tracked with proper locking. + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void cancelAnnouncement(); /** * Get current station information. + * * @param info a ProgramInfo array of lengh 1 where the information is returned. * @return *

      @@ -233,23 +323,27 @@ public abstract class RadioTuner { *
    • {@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native * service fails,
    • *
    - * @deprecated Use {@link onProgramInfoChanged} callback instead. + * @deprecated Use {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)} callback + * instead. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) + @RadioManager.RadioStatusType public abstract int getProgramInformation(RadioManager.ProgramInfo[] info); /** * Retrieves a {@link Bitmap} for the given image ID or null, * if the image was missing from the tuner. * - * This involves doing a call to the tuner, so the bitmap should be cached + *

    This involves doing a call to the tuner, so the bitmap should be cached * on the application side. * - * If the method returns null for non-zero ID, it means the image was + *

    If the method returns null for non-zero ID, it means the image was * updated on the tuner side. There is a race conditon between fetching * image for an old ID and tuner updating the image (and cleaning up the * old image). In such case, a new ProgramInfo with updated image id will - * be sent with a {@link onProgramInfoChanged} callback. + * be sent with a {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)} + * callback. * * @param id The image identifier, retrieved with * {@link RadioMetadata#getBitmapId(String)}. @@ -258,14 +352,16 @@ public abstract class RadioTuner { * @hide This API is not thoroughly elaborated yet */ @SuppressWarnings("HiddenAbstractMethod") + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract @Nullable Bitmap getMetadataImage(int id); /** * Initiates a background scan to update internally cached program list. * - * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot. + *

    It may not be necessary to initiate the scan explicitly - the scan MAY be performed on + * boot. * - * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will + *

    The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will * be called if the return value of this call was {@code true}. As result of this call * programListChanged may be triggered (if the scanned list differs). * @@ -273,13 +369,16 @@ public abstract class RadioTuner { * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device * or permanently if the feature is not supported * (see ModuleProperties#isBackgroundScanningSupported()). + * @deprecated Only applicable for HAL 1.x. */ + @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean startBackgroundScan(); /** * Get the list of discovered radio stations. * - * To get the full list, set filter to null or empty map. + *

    To get the full list, set filter to null or empty map. * Keys must be prefixed with unique vendor Java-style namespace, * eg. 'com.somecompany.parameter1'. * @@ -288,24 +387,27 @@ public abstract class RadioTuner { * @throws IllegalStateException if the scan is in progress or has not been started, * startBackgroundScan() call may fix it. * @throws IllegalArgumentException if the vendorFilter argument is not valid. - * @deprecated Use {@link getDynamicProgramList} instead. + * @deprecated Use {@link #getDynamicProgramList(ProgramList.Filter)} instead. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract @NonNull List getProgramList(@Nullable Map vendorFilter); /** * Get the dynamic list of discovered radio stations. * - * The list object is updated asynchronously; to get the updates register - * with {@link ProgramList#addListCallback}. + *

    The list object is updated asynchronously; to get the updates register + * with {@link ProgramList#registerListCallback(ProgramList.ListCallback)} + * or {@link ProgramList#registerListCallback(Executor, ProgramList.ListCallback)}. * - * When the returned object is no longer used, it must be closed. + *

    When the returned object is no longer used, it must be closed. * * @param filter filter for the list, or null to get the full list. * @return the dynamic program list object, close it after use * or {@code null} if program list is not supported by the tuner */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { return null; } @@ -316,26 +418,28 @@ public abstract class RadioTuner { * @throws IllegalStateException if the switch is not supported at current * configuration. * @return {@code true} if analog is forced, {@code false} otherwise. - * @deprecated Use {@link isConfigFlagSet(int)} instead. + * @deprecated Use {@link #isConfigFlagSet(int)} instead. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAnalogForced(); /** * Forces the analog playback for the supporting radio technology. * - * User may disable digital playback for FM HD Radio or hybrid FM/DAB with + *

    User may disable digital playback for FM HD Radio or hybrid FM/DAB with * this option. This is purely user choice, ie. does not reflect digital- * analog handover managed from the HAL implementation side. * - * Some radio technologies may not support this, ie. DAB. + *

    Some radio technologies may not support this, ie. DAB. * * @param isForced {@code true} to force analog, {@code false} for a default behaviour. * @throws IllegalStateException if the switch is not supported at current * configuration. - * @deprecated Use {@link setConfigFlag(int, boolean)} instead. + * @deprecated Use {@link #setConfigFlag(int, boolean)} instead. */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void setAnalogForced(boolean isForced); /** @@ -344,6 +448,7 @@ public abstract class RadioTuner { * @param flag Flag to check. * @return True, if the flag is supported. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) { return false; } @@ -351,29 +456,34 @@ public abstract class RadioTuner { /** * Fetches the current setting of a given config flag. * - * The success/failure result is consistent with isConfigFlagSupported. + *

    The success/failure result is consistent with isConfigFlagSupported. * * @param flag Flag to fetch. * @return The current value of the flag. * @throws IllegalStateException if the flag is not applicable right now. * @throws UnsupportedOperationException if the flag is not supported at all. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("isConfigFlagSet is not supported"); } /** * Sets the config flag. * - * The success/failure result is consistent with isConfigFlagSupported. + *

    The success/failure result is consistent with isConfigFlagSupported. + * + *

    When this operation is called by users other than current user or system user, + * it is ignored silently. * * @param flag Flag to set. * @param value The new value of a given flag. * @throws IllegalStateException if the flag is not applicable right now. * @throws UnsupportedOperationException if the flag is not supported at all. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Setting config flag is not supported"); } /** @@ -381,30 +491,37 @@ public abstract class RadioTuner { * The framework does not interpret the parameters, they are passed * in an opaque manner between a vendor application and HAL. * - * Framework does not make any assumptions on the keys or values, other than + *

    Framework does not make any assumptions on the keys or values, other than * ones stated in VendorKeyValue documentation (a requirement of key * prefixes). - * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal. + * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal for + * HIDL 2.0 HAL or + * hardware/interfaces/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl + * for AIDL HAL. * - * For each pair in the result map, the key will be one of the keys + *

    For each pair in the result map, the key will be one of the keys * contained in the input (possibly with wildcards expanded), and the value * will be a vendor-specific result status (such as "OK" or an error code). * The implementation may choose to return an empty map, or only return * a status for a subset of the provided inputs, at its discretion. * - * Application and HAL must not use keys with unknown prefix. In particular, + *

    Application and HAL must not use keys with unknown prefix. In particular, * it must not place a key-value pair in results vector for unknown key from * parameters vector - instead, an unknown key should simply be ignored. * In other words, results vector may contain a subset of parameter keys * (however, the framework doesn't enforce a strict subset - the only * formal requirement is vendor domain prefix for keys). * + *

    When this operation is called by users other than current user or system user, + * it is ignored silently. + * * @param parameters Vendor-specific key-value pairs. * @return Operation completion status for parameters being set. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public @NonNull Map setParameters(@NonNull Map parameters) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Setting parameters is not supported"); } /** @@ -412,23 +529,24 @@ public abstract class RadioTuner { * The framework does not interpret the parameters, they are passed * in an opaque manner between a vendor application and HAL. * - * Framework does not cache set/get requests, so it's possible for + *

    Framework does not cache set/get requests, so it's possible for * getParameter to return a different value than previous setParameter call. * - * The syntax and semantics of keys are up to the vendor (as long as prefix + *

    The syntax and semantics of keys are up to the vendor (as long as prefix * rules are obeyed). For instance, vendors may include some form of * wildcard support. In such case, result vector may be of different size * than requested keys vector. However, wildcards are not recognized by * framework and they are passed as-is to the HAL implementation. * - * Unknown keys must be ignored and not placed into results vector. + *

    Unknown keys must be ignored and not placed into results vector. * * @param keys Parameter keys to fetch. * @return Vendor-specific key-value pairs. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public @NonNull Map getParameters(@NonNull List keys) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Getting parameters is not supported"); } /** @@ -436,14 +554,16 @@ public abstract class RadioTuner { * Only valid if a configuration has been applied. * @return {@code true} if the antenna is connected, {@code false} otherwise. * - * @deprecated Use {@link onAntennaState} callback instead + * @deprecated Use {@link Callback#onAntennaState(boolean)} callback instead */ @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAntennaConnected(); /** * Indicates if this client actually controls the tuner. - * Control is always granted after + * + *

    Control is always granted after * {@link RadioManager#openTuner(int, * RadioManager.BandConfig, boolean, Callback, Handler)} * returns a non null tuner interface. @@ -451,48 +571,102 @@ public abstract class RadioTuner { * When this happens, {@link Callback#onControlChanged(boolean)} is received. * The client can either wait for control to be returned (which is indicated by the same * callback) or close and reopen the tuner interface. + * * @return {@code true} if this interface controls the tuner, * {@code false} otherwise or if a problem occured retrieving the state. */ + @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean hasControl(); /** Indicates a failure of radio IC or driver. - * The application must close and re open the tuner - * @deprecated See {@link onError} callback. + * + *

    The application must close and re open the tuner + * + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; /** Indicates a failure of the radio service. - * The application must close and re open the tuner - * @deprecated See {@link onError} callback. + * + *

    The application must close and re open the tuner + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_SERVER_DIED = 1; /** A pending seek or tune operation was cancelled - * @deprecated See {@link onError} callback. + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_CANCELLED = 2; /** A pending seek or tune operation timed out - * @deprecated See {@link onError} callback. + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; /** The requested configuration could not be applied - * @deprecated See {@link onError} callback. + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_CONFIG = 4; /** Background scan was interrupted due to hardware becoming temporarily unavailable. - * @deprecated See {@link onError} callback. + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; /** Background scan failed due to other error, ie. HW failure. - * @deprecated See {@link onError} callback. + * @deprecated See {@link RadioTuner.Callback#onError(int)} callback. */ @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; + /** Result when a tune, seek, or step operation runs without error. + */ + public static final int TUNER_RESULT_OK = 0; + /** Result when internal error occurs in HAL. + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_INTERNAL_ERROR = 1; + /** Result used when the input argument for the method is invalid. + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2; + /** Result when HAL is of invalid state. + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_INVALID_STATE = 3; + /** Result when the operation is not supported. + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_NOT_SUPPORTED = 4; + /** Result when a tune, seek, or step operation is timeout + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_TIMEOUT = 5; + /** Result when a tune, seek, or step operation is canceled before processed. + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_CANCELED = 6; + /** Result when a tune, seek, or step operation fails due to unknown error. + * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback. + */ + public static final int TUNER_RESULT_UNKNOWN_ERROR = 7; + + /** + * Tuning operation result types + * + * @hide + */ + @IntDef(prefix = { "TUNER_RESULT_" }, value = { + TUNER_RESULT_OK, + TUNER_RESULT_INTERNAL_ERROR, + TUNER_RESULT_INVALID_ARGUMENTS, + TUNER_RESULT_INVALID_STATE, + TUNER_RESULT_NOT_SUPPORTED, + TUNER_RESULT_TIMEOUT, + TUNER_RESULT_CANCELED, + TUNER_RESULT_UNKNOWN_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TunerResultType{} /** * Callback provided by the client application when opening a {@link RadioTuner} @@ -506,8 +680,9 @@ public abstract class RadioTuner { * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT}, * {@link #ERROR_CONFIG} * - * @deprecated Use {@link onTuneFailed} for tune, scan and step; - * other use cases (configuration, background scan) are already deprecated. + * @deprecated Use {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} for + * tune, scan and step; other use cases (configuration, background scan) + * are already deprecated. */ public void onError(int status) {} @@ -518,7 +693,7 @@ public abstract class RadioTuner { * @param selector ProgramSelector argument of tune that failed; * null for scan and step. */ - public void onTuneFailed(int result, @Nullable ProgramSelector selector) {} + public void onTuneFailed(@TunerResultType int result, @Nullable ProgramSelector selector) {} /** * onConfigurationChanged() is called upon successful completion of @@ -533,7 +708,7 @@ public abstract class RadioTuner { /** * Called when program info (including metadata) for the current program has changed. * - * It happens either upon successful completion of {@link RadioTuner#step(int, boolean)}, + *

    It happens either upon successful completion of {@link RadioTuner#step(int, boolean)}, * {@link RadioTuner#scan(int, boolean)}, {@link RadioTuner#tune(int, int)}; when * a switching to alternate frequency occurs; or when metadata is updated. */ @@ -589,16 +764,29 @@ public abstract class RadioTuner { /** * Called when available program list changed. * - * Use {@link RadioTuner#getProgramList(String)} to get an actual list. + * Use {@link RadioTuner#getProgramList(Map)} to get an actual list. */ public void onProgramListChanged() {} + /** + * Called when config flags are updated asynchronously due to internal events + * in broadcast radio HAL. + * + * {@link RadioTuner#setConfigFlag(int, boolean)} must not trigger this + * callback. + * + * @param flag Config flag updated + * @param value Value of the updated config flag + */ + public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {} + /** * Generic callback for passing updates to vendor-specific parameter values. - * The framework does not interpret the parameters, they are passed + * + *

    The framework does not interpret the parameters, they are passed * in an opaque manner between a vendor application and HAL. * - * It's up to the HAL implementation if and how to implement this callback, + *

    It's up to the HAL implementation if and how to implement this callback, * as long as it obeys the prefix rule. In particular, only selected keys * may be notified this way. However, setParameters must not trigger * this callback, while an internal event can change parameters diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 4a18333aee9f7..bdbca91a715a9 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -154,7 +154,7 @@ final class TunerAdapter extends RadioTuner { @Override public int scan(int direction, boolean skipSubChannel) { try { - mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, + mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, skipSubChannel); } catch (IllegalStateException e) { Log.e(TAG, "Can't scan", e); @@ -166,6 +166,21 @@ final class TunerAdapter extends RadioTuner { return RadioManager.STATUS_OK; } + @Override + public int seek(int direction, boolean skipSubChannel) { + try { + mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN, + skipSubChannel); + } catch (IllegalStateException e) { + Log.e(TAG, "Can't seek", e); + return RadioManager.STATUS_INVALID_OPERATION; + } catch (RemoteException e) { + Log.e(TAG, "Service died", e); + return RadioManager.STATUS_DEAD_OBJECT; + } + return RadioManager.STATUS_OK; + } + @Override public int tune(int channel, int subChannel) { try { diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index b9782a87735db..22f59021bca17 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -245,6 +245,11 @@ final class TunerCallbackAdapter extends ITunerCallback.Stub { }); } + @Override + public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) { + mHandler.post(() -> mCallback.onConfigFlagUpdated(flag, value)); + } + @Override public void onParametersUpdated(Map parameters) { mHandler.post(() -> mCallback.onParametersUpdated(parameters)); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java index 3f35e998e0253..cabeb13b21240 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java @@ -17,6 +17,7 @@ package android.hardware.radio.tests.functional; import static org.junit.Assert.*; import static org.junit.Assume.*; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -335,8 +336,10 @@ public class RadioTunerTest { assertEquals(RadioManager.STATUS_OK, scanRet); assertEquals(RadioManager.STATUS_OK, cancelRet); - verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED); + verify(mCallback, after(kCancelTimeoutMs).atMost(1)) + .onTuneFailed(eq(RadioTuner.TUNER_RESULT_CANCELED), any()); verify(mCallback, atMost(1)).onProgramInfoChanged(any()); + Mockito.reset(mCallback); } @Test diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java index 2fa3f87668ca0..65e55a2c753bc 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java @@ -129,6 +129,18 @@ public final class DefaultRadioTunerTest { } }; + @Test + public void seek_forRadioTuner_throwsException() { + UnsupportedOperationException thrown = assertThrows( + UnsupportedOperationException.class, () -> { + DEFAULT_RADIO_TUNER.seek(RadioTuner.DIRECTION_DOWN, + /* skipSubChannel= */ false); + }); + + assertWithMessage("Exception for seeking on default radio tuner") + .that(thrown).hasMessageThat().contains("Seeking is not supported"); + } + @Test public void getDynamicProgramList_forRadioTuner_returnsNull() { assertWithMessage("Dynamic program list obtained from default radio tuner") @@ -143,29 +155,45 @@ public final class DefaultRadioTunerTest { @Test public void isConfigFlagSet_forRadioTuner_throwsException() { - assertThrows(UnsupportedOperationException.class, () -> { - DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1); - }); + UnsupportedOperationException thrown = assertThrows( + UnsupportedOperationException.class, () -> { + DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1); + }); + + assertWithMessage("Exception for isConfigFlagSet on default radio tuner") + .that(thrown).hasMessageThat().contains("isConfigFlagSet is not supported"); } @Test public void setConfigFlag_forRadioTuner_throwsException() { - assertThrows(UnsupportedOperationException.class, () -> { + UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, + () -> { DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false); }); + + assertWithMessage("Exception for setting config flag on default radio tuner") + .that(thrown).hasMessageThat().contains("Setting config flag is not supported"); } @Test public void setParameters_forRadioTuner_throwsException() { - assertThrows(UnsupportedOperationException.class, () -> { + UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, + () -> { DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue")); }); + + assertWithMessage("Exception for setting parameters from default radio tuner") + .that(thrown).hasMessageThat().contains("Setting parameters is not supported"); } @Test public void getParameters_forRadioTuner_throwsException() { - assertThrows(UnsupportedOperationException.class, () -> { + UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class, + () -> { DEFAULT_RADIO_TUNER.getParameters(List.of("testKey")); }); + + assertWithMessage("Exception for getting parameters from default radio tuner") + .that(thrown).hasMessageThat().contains("Getting parameters is not supported"); } } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java index 2b9de18701e2e..87f91fa436ed3 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java @@ -41,6 +41,8 @@ import android.os.Parcel; import android.os.RemoteException; import android.util.ArraySet; +import androidx.test.InstrumentationRegistry; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -54,6 +56,8 @@ import java.util.concurrent.Executor; @RunWith(MockitoJUnitRunner.class) public final class ProgramListTest { + public final Context mContext = InstrumentationRegistry.getContext(); + private static final int CREATOR_ARRAY_SIZE = 3; private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500); @@ -109,8 +113,6 @@ public final class ProgramListTest { @Mock private IRadioService mRadioServiceMock; @Mock - private Context mContextMock; - @Mock private ITuner mTunerMock; @Mock private RadioTuner.Callback mTunerCallbackMock; @@ -477,7 +479,7 @@ public final class ProgramListTest { } private void createRadioTuner() throws Exception { - RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock); + RadioManager radioManager = new RadioManager(mContext, mRadioServiceMock); RadioManager.BandConfig band = new RadioManager.FmBandConfig( new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM, /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200, @@ -487,7 +489,7 @@ public final class ProgramListTest { doAnswer(invocation -> { mTunerCallback = (ITunerCallback) invocation.getArguments()[3]; return mTunerMock; - }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any()); + }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()); mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band, /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java index 44aa6d19cb0a2..03742eb93a3bb 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java @@ -18,14 +18,16 @@ package android.hardware.radio.tests.unittests; import static com.google.common.truth.Truth.assertWithMessage; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.hardware.radio.Announcement; import android.hardware.radio.IAnnouncementListener; import android.hardware.radio.ICloseHandle; @@ -34,6 +36,7 @@ import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.RadioTuner; +import android.os.Build; import android.os.Parcel; import android.os.RemoteException; import android.util.ArrayMap; @@ -53,6 +56,8 @@ import java.util.Set; @RunWith(MockitoJUnitRunner.class) public final class RadioManagerTest { + private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; + private static final int REGION = RadioManager.REGION_ITU_2; private static final int FM_LOWER_LIMIT = 87500; private static final int FM_UPPER_LIMIT = 108000; @@ -126,6 +131,7 @@ public final class RadioManagerTest { /* vendorInfo= */ new ArrayMap<>())); private RadioManager mRadioManager; + private final ApplicationInfo mApplicationInfo = new ApplicationInfo(); @Mock private IRadioService mRadioServiceMock; @@ -1008,7 +1014,8 @@ public final class RadioManagerTest { mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock, /* handler= */ null); - verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any()); + verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(), + anyInt()); } @Test @@ -1103,6 +1110,8 @@ public final class RadioManagerTest { } private void createRadioManager() throws RemoteException { + mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION; + when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo); when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES)); when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java index bdba6a1c5402a..d851a7724e8ee 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; import android.hardware.radio.IRadioService; import android.hardware.radio.ITuner; @@ -35,6 +36,7 @@ import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.RadioTuner; +import android.os.Build; import org.junit.After; import org.junit.Before; @@ -51,11 +53,12 @@ import java.util.Map; @RunWith(MockitoJUnitRunner.class) public final class TunerAdapterTest { + private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; + private static final int CALLBACK_TIMEOUT_MS = 30_000; private static final int AM_LOWER_LIMIT_KHZ = 150; private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig(); - private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 94300); @@ -66,6 +69,7 @@ public final class TunerAdapterTest { private RadioTuner mRadioTuner; private ITunerCallback mTunerCallback; + private final ApplicationInfo mApplicationInfo = new ApplicationInfo(); @Mock private IRadioService mRadioServiceMock; @@ -78,12 +82,14 @@ public final class TunerAdapterTest { @Before public void setUp() throws Exception { + mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION; + when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo); RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock); doAnswer(invocation -> { mTunerCallback = (ITunerCallback) invocation.getArguments()[3]; return mTunerMock; - }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any()); + }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()); doAnswer(invocation -> { ProgramSelector program = (ProgramSelector) invocation.getArguments()[0]; @@ -92,7 +98,7 @@ public final class TunerAdapterTest { throw new IllegalArgumentException(); } if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) { - mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program); + mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, program); } else { mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO); } @@ -169,16 +175,31 @@ public final class TunerAdapterTest { verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); } + @Test + public void scan_forTunerAdapter_succeeds() throws Exception { + doAnswer(invocation -> { + mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO); + return RadioManager.STATUS_OK; + }).when(mTunerMock).seek(anyBoolean(), anyBoolean()); + + int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false); + + verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false); + assertWithMessage("Status for scaning") + .that(scanStatus).isEqualTo(RadioManager.STATUS_OK); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); + } + @Test public void seek_forTunerAdapter_succeeds() throws Exception { doAnswer(invocation -> { mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO); return RadioManager.STATUS_OK; - }).when(mTunerMock).scan(anyBoolean(), anyBoolean()); + }).when(mTunerMock).seek(anyBoolean(), anyBoolean()); int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false); - verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false); + verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false); assertWithMessage("Status for seeking") .that(scanStatus).isEqualTo(RadioManager.STATUS_OK); verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO); @@ -187,13 +208,14 @@ public final class TunerAdapterTest { @Test public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception { doAnswer(invocation -> { - mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT); + mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR); return RadioManager.STATUS_OK; - }).when(mTunerMock).scan(anyBoolean(), anyBoolean()); + }).when(mTunerMock).seek(anyBoolean(), anyBoolean()); mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true); - verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT); + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed( + RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR); } @Test @@ -224,7 +246,7 @@ public final class TunerAdapterTest { mRadioTuner.tune(invalidSelector); verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) - .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector); + .onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, invalidSelector); } @Test @@ -414,6 +436,17 @@ public final class TunerAdapterTest { verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramListChanged(); } + @Test + public void onConfigFlagUpdated_forTunerCallbackAdapter() throws Exception { + int configFlag = RadioManager.CONFIG_RDS_AF; + boolean configFlagValue = true; + + mTunerCallback.onConfigFlagUpdated(configFlag, configFlagValue); + + verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)) + .onConfigFlagUpdated(configFlag, configFlagValue); + } + @Test public void onParametersUpdated_forTunerCallbackAdapter() throws Exception { Map parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock"); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java index a2df426c315c8..9803474566fbf 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,6 +34,7 @@ import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; +import android.os.Build; import android.os.IBinder; import android.os.ServiceManager; @@ -55,6 +57,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas "android.hardware.broadcastradio.IBroadcastRadio/amfm"; private static final String DAB_SERVICE_NAME = "android.hardware.broadcastradio.IBroadcastRadio/dab"; + private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private IRadioServiceAidlImpl mAidlImpl; @@ -82,7 +85,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas doNothing().when(mServiceMock).enforcePolicyAccess(); when(mHalMock.listModules()).thenReturn(List.of(mModuleMock)); - when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any())) + when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION))) .thenReturn(mTunerMock); when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle); @@ -114,7 +117,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas @Test public void openTuner_forAidlImpl() throws Exception { ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock, - /* withAudio= */ true, mTunerCallbackMock); + /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION); assertWithMessage("Tuner opened in AIDL HAL") .that(tuner).isEqualTo(mTunerMock); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java index 5ab943542f81d..cfff477362400 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java @@ -32,6 +32,7 @@ import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; +import android.os.Build; import org.junit.Before; import org.junit.Test; @@ -49,6 +50,7 @@ public final class IRadioServiceHidlImplTest { private static final int HAL1_MODULE_ID = 0; private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; + private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private IRadioServiceHidlImpl mHidlImpl; @@ -103,7 +105,7 @@ public final class IRadioServiceHidlImplTest { @Test public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception { ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock, - /* withAudio= */ true, mTunerCallbackMock); + /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION); assertWithMessage("Tuner opened in HAL 1") .that(tuner).isEqualTo(mHal1TunerMock); @@ -112,7 +114,7 @@ public final class IRadioServiceHidlImplTest { @Test public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception { ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock, - /* withAudio= */ true, mTunerCallbackMock); + /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION); assertWithMessage("Tuner opened in HAL 2") .that(tuner).isEqualTo(mHal2TunerMock); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java index 1cc0a98512bf5..f4040825e9a74 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java @@ -36,6 +36,7 @@ import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioTuner; +import android.os.Build; import android.os.IBinder; import android.os.IServiceCallback; import android.os.RemoteException; @@ -54,6 +55,8 @@ import java.util.Arrays; public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase { + private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; + private static final int FM_RADIO_MODULE_ID = 0; private static final int DAB_RADIO_MODULE_ID = 1; private static final ArrayList SERVICE_LIST = @@ -137,7 +140,8 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes createBroadcastRadioService(); ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, - /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock, + TARGET_SDK_VERSION); assertWithMessage("Session opened in FM radio module") .that(session).isEqualTo(mFmTunerSessionMock); @@ -148,7 +152,8 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes createBroadcastRadioService(); ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1, - /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock, + TARGET_SDK_VERSION); assertWithMessage("Session opened with id not found").that(session).isNull(); } @@ -160,7 +165,8 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, - /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock)); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock, + TARGET_SDK_VERSION)); assertWithMessage("Exception for opening session by non-current user") .that(thrown).hasMessageThat().contains("Cannot open session for non-current user"); @@ -228,7 +234,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes return null; }).when(mFmBinderMock).linkToDeath(any(), anyInt()); - when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock))) + when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION))) .thenReturn(mFmTunerSessionMock); } } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java new file mode 100644 index 0000000000000..df3ddfd6bb717 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio.aidl; + +import android.hardware.broadcastradio.Result; +import android.hardware.radio.RadioTuner; + +import com.google.common.truth.Expect; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; + +@RunWith(Parameterized.class) +public final class ConversionUtilsResultTest { + + private final int mHalResult; + private final int mTunerResult; + + @Rule + public final Expect expect = Expect.create(); + + public ConversionUtilsResultTest(int halResult, int tunerResult) { + this.mHalResult = halResult; + this.mTunerResult = tunerResult; + } + + @Parameterized.Parameters + public static List inputParameters() { + return Arrays.asList(new Object[][]{ + {Result.OK, RadioTuner.TUNER_RESULT_OK}, + {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR}, + {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS}, + {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE}, + {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED}, + {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT}, + {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR} + }); + } + + @Test + public void halResultToTunerResult() { + expect.withMessage("Tuner result converted from AIDL HAL result %s", mHalResult) + .that(ConversionUtils.halResultToTunerResult(mHalResult)) + .isEqualTo(mTunerResult); + } +} diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 31195548c9685..a1cebb630aac3 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -25,6 +25,7 @@ import android.hardware.broadcastradio.VendorKeyValue; import android.hardware.radio.Announcement; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; +import android.os.Build; import com.google.common.truth.Expect; @@ -68,6 +69,18 @@ public final class ConversionUtilsTest { @Rule public final Expect expect = Expect.create(); + @Test + public void isAtLeastU_withTSdkVersion_returnsFalse() { + expect.withMessage("Target SDK version of T") + .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse(); + } + + @Test + public void isAtLeastU_withCurrentSdkVersion_returnsTrue() { + expect.withMessage("Target SDK version of U") + .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue(); + } + @Test public void propertiesFromHalProperties_idsMatch() { expect.withMessage("Properties id") diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 993ca7728374b..c5c6349df07e4 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -42,6 +42,7 @@ import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioTuner; +import android.os.Build; import android.os.ServiceSpecificException; import android.util.ArrayMap; import android.util.ArraySet; @@ -65,6 +66,7 @@ import java.util.Map; */ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { + private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 200); private static final int SIGNAL_QUALITY = 1; @@ -298,6 +300,18 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo); } + @Test + public void tune_withLowerSdkVersion() throws Exception { + openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU); + ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); + RadioManager.ProgramInfo tuneInfo = + AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY); + + mTunerSessions[0].tune(initialSel); + + verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo); + } + @Test public void tune_withMultipleSessions() throws Exception { int numSessions = 3; @@ -377,49 +391,49 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - public void scan_withDirectionUp() throws Exception { + public void seek_withDirectionUp() throws Exception { long initFreq = AM_FM_FREQUENCY_LIST[2]; ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq); - RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo( + RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo( AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)), SIGNAL_QUALITY); openAidlClients(/* numClients= */ 1); mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo( ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY); - mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false); + mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) - .onCurrentProgramInfoChanged(scanUpInfo); + .onCurrentProgramInfoChanged(seekUpInfo); } @Test - public void scan_callsOnTuneFailedWhenTimeout() throws Exception { + public void seek_callsOnTuneFailedWhenTimeout() throws Exception { int numSessions = 2; openAidlClients(numSessions); - mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false); + mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false); for (int index = 0; index < numSessions; index++) { verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) - .onTuneFailed(eq(Result.TIMEOUT), any()); + .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any()); } } @Test - public void scan_withDirectionDown() throws Exception { + public void seek_withDirectionDown() throws Exception { long initFreq = AM_FM_FREQUENCY_LIST[2]; ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq); - RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo( + RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo( AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)), SIGNAL_QUALITY); openAidlClients(/* numClients= */ 1); mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo( ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY); - mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false); + mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) - .onCurrentProgramInfoChanged(scanUpInfo); + .onCurrentProgramInfoChanged(seekUpInfo); } @Test @@ -585,7 +599,7 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } @Test - public void onConfigFlagUpdated_forTunerCallback() throws Exception { + public void onAntennaStateChange_forTunerCallback() throws Exception { int numSessions = 3; openAidlClients(numSessions); @@ -597,6 +611,21 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { } } + @Test + public void onConfigFlagUpdated_forTunerCallback() throws Exception { + int numSessions = 3; + openAidlClients(numSessions); + int flag = UNSUPPORTED_CONFIG_FLAG + 1; + boolean configFlagValue = true; + + mHalTunerCallback.onConfigFlagUpdated(flag, configFlagValue); + + for (int index = 0; index < numSessions; index++) { + verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) + .onConfigFlagUpdated(flag, configFlagValue); + } + } + @Test public void onParametersUpdated_forTunerCallback() throws Exception { int numSessions = 3; @@ -612,13 +641,17 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { .onParametersUpdated(parametersExpected); } } - private void openAidlClients(int numClients) throws Exception { + openAidlClients(numClients, TARGET_SDK_VERSION); + } + + private void openAidlClients(int numClients, int targetSdkVersion) throws Exception { mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; mTunerSessions = new TunerSession[numClients]; for (int index = 0; index < numClients; index++) { mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class); - mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]); + mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index], + targetSdkVersion); } } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java new file mode 100644 index 0000000000000..d106de9be06f2 --- /dev/null +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.broadcastradio.hal2; + +import android.hardware.broadcastradio.V2_0.Result; +import android.hardware.radio.RadioTuner; + +import com.google.common.truth.Expect; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.List; + +@RunWith(Parameterized.class) +public final class ConvertResultTest { + + private final int mHalResult; + private final int mTunerResult; + + @Rule + public final Expect expect = Expect.create(); + + public ConvertResultTest(int halResult, int tunerResult) { + this.mHalResult = halResult; + this.mTunerResult = tunerResult; + } + + @Parameterized.Parameters + public static List inputParameters() { + return Arrays.asList(new Object[][]{ + {Result.OK, RadioTuner.TUNER_RESULT_OK}, + {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR}, + {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS}, + {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE}, + {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED}, + {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT}, + {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR} + }); + } + + @Test + public void halResultToTunerResult() { + expect.withMessage("Tuner result converted from HAL 2.0 result %s", + Result.toString(mHalResult)) + .that(Convert.halResultToTunerResult(mHalResult)) + .isEqualTo(mTunerResult); + } +} diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index ff988a21473ad..db16c0362e7c9 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -384,49 +384,49 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { } @Test - public void scan_withDirectionUp() throws Exception { + public void seek_withDirectionUp() throws Exception { long initFreq = AM_FM_FREQUENCY_LIST[2]; ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq); - RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo( + RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo( TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)), SIGNAL_QUALITY); openAidlClients(/* numClients= */ 1); mHalCurrentInfo = TestUtils.makeHalProgramInfo( Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY); - mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false); + mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) - .onCurrentProgramInfoChanged(scanUpInfo); + .onCurrentProgramInfoChanged(seekUpInfo); } @Test - public void scan_callsOnTuneFailedWhenTimeout() throws Exception { + public void seek_callsOnTuneFailedWhenTimeout() throws Exception { int numSessions = 2; openAidlClients(numSessions); - mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false); + mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false); for (int index = 0; index < numSessions; index++) { verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT) - .onTuneFailed(eq(Result.TIMEOUT), any()); + .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any()); } } @Test - public void scan_withDirectionDown() throws Exception { + public void seek_withDirectionDown() throws Exception { long initFreq = AM_FM_FREQUENCY_LIST[2]; ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq); - RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo( + RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo( TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)), SIGNAL_QUALITY); openAidlClients(/* numClients= */ 1); mHalCurrentInfo = TestUtils.makeHalProgramInfo( Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY); - mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false); + mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false); verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT) - .onCurrentProgramInfoChanged(scanUpInfo); + .onCurrentProgramInfoChanged(seekUpInfo); } @Test diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java index 6a010424db139..b66120d428c3f 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java @@ -86,7 +86,8 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, - boolean withAudio, ITunerCallback callback) throws RemoteException { + boolean withAudio, ITunerCallback callback, int targetSdkVersion) + throws RemoteException { if (isDebugEnabled()) { Slogf.d(TAG, "Opening module %d", moduleId); } @@ -94,7 +95,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } - return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback); + return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java index 408fba1bff3b1..8a1ba19e24ab7 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java @@ -92,7 +92,8 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, - boolean withAudio, ITunerCallback callback) throws RemoteException { + boolean withAudio, ITunerCallback callback, int targetSdkVersion) + throws RemoteException { if (isDebugEnabled()) { Slog.d(TAG, "Opening module " + moduleId); } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java index 03acf72725e7d..772cd4144f11b 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java @@ -199,7 +199,8 @@ public final class BroadcastRadioServiceImpl { */ @Nullable public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, - boolean withAudio, ITunerCallback callback) throws RemoteException { + boolean withAudio, ITunerCallback callback, int targetSdkVersion) + throws RemoteException { if (DEBUG) { Slogf.d(TAG, "Open AIDL radio session"); } @@ -222,7 +223,7 @@ public final class BroadcastRadioServiceImpl { } } - TunerSession tunerSession = radioModule.openSession(callback); + TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion); if (legacyConfig != null) { tunerSession.setConfiguration(legacyConfig); } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java index d90f9c47f10ac..3c0fda863cdc2 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java @@ -33,6 +33,8 @@ import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; +import android.hardware.radio.RadioTuner; +import android.os.Build; import android.os.ParcelableException; import android.os.ServiceSpecificException; import android.util.ArrayMap; @@ -62,6 +64,11 @@ final class ConversionUtils { throw new UnsupportedOperationException("ConversionUtils class is noninstantiable"); } + static boolean isAtLeastU(int targetSdkVersion) { + // TODO(b/261770108): Use version code for U. + return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT; + } + static RuntimeException throwOnError(RuntimeException halException, String action) { if (!(halException instanceof ServiceSpecificException)) { return new ParcelableException(new RuntimeException( @@ -89,6 +96,27 @@ final class ConversionUtils { } } + @RadioTuner.TunerResultType + static int halResultToTunerResult(int result) { + switch (result) { + case Result.OK: + return RadioTuner.TUNER_RESULT_OK; + case Result.INTERNAL_ERROR: + return RadioTuner.TUNER_RESULT_INTERNAL_ERROR; + case Result.INVALID_ARGUMENTS: + return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS; + case Result.INVALID_STATE: + return RadioTuner.TUNER_RESULT_INVALID_STATE; + case Result.NOT_SUPPORTED: + return RadioTuner.TUNER_RESULT_NOT_SUPPORTED; + case Result.TIMEOUT: + return RadioTuner.TUNER_RESULT_TIMEOUT; + case Result.UNKNOWN_ERROR: + default: + return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR; + } + } + static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map info) { if (info == null) { return new VendorKeyValue[]{}; @@ -143,6 +171,7 @@ final class ConversionUtils { case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: + case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT: return ProgramSelector.PROGRAM_TYPE_DAB; case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: @@ -471,6 +500,60 @@ final class ConversionUtils { return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed); } + private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) { + return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT; + } + + static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, + int targetSdkVersion) { + if (isAtLeastU(targetSdkVersion)) { + return true; + } + if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) { + return false; + } + ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds(); + for (int i = 0; i < secondaryIds.length; i++) { + if (isNewIdentifierInU(secondaryIds[i])) { + return false; + } + } + return true; + } + + static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, + int targetSdkVersion) { + if (isAtLeastU(targetSdkVersion)) { + return true; + } + if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) { + return false; + } + if (isNewIdentifierInU(info.getLogicallyTunedTo()) + || isNewIdentifierInU(info.getPhysicallyTunedTo())) { + return false; + } + Iterator relatedContentIt = info.getRelatedContent().iterator(); + while (relatedContentIt.hasNext()) { + if (isNewIdentifierInU(relatedContentIt.next())) { + return false; + } + } + return true; + } + + static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, + int targetSdkVersion) { + if (isAtLeastU(targetSdkVersion)) { + return chunk; + } + Set modified = chunk.getModified(); + modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)); + Set removed = chunk.getRemoved(); + removed.removeIf(id -> isNewIdentifierInU(id)); + return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed); + } + public static android.hardware.radio.Announcement announcementFromHalAnnouncement( Announcement hwAnnouncement) { return new android.hardware.radio.Announcement( diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java index e956a9c2038cc..6193f234730dd 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java @@ -100,7 +100,16 @@ final class RadioModule { synchronized (mLock) { android.hardware.radio.ProgramSelector csel = ConversionUtils.programSelectorFromHalProgramSelector(programSelector); - fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel)); + int tunerResult = ConversionUtils.halResultToTunerResult(result); + fanoutAidlCallbackLocked((cb, sdkVersion) -> { + if (csel != null && !ConversionUtils + .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) { + Slogf.e(TAG, "onTuneFailed: cannot send program selector " + + "requiring higher target SDK version"); + return; + } + cb.onTuneFailed(tunerResult, csel); + }); } }); } @@ -112,7 +121,13 @@ final class RadioModule { mCurrentProgramInfo = ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo); RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo; - fanoutAidlCallbackLocked(cb -> { + fanoutAidlCallbackLocked((cb, sdkVersion) -> { + if (!ConversionUtils.programInfoMeetsSdkVersionRequirement( + currentProgramInfo, sdkVersion)) { + Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send " + + "program info requiring higher target SDK version"); + return; + } cb.onCurrentProgramInfoChanged(currentProgramInfo); }); } @@ -139,7 +154,7 @@ final class RadioModule { fireLater(() -> { synchronized (mLock) { mAntennaConnected = connected; - fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected)); + fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected)); } }); } @@ -147,8 +162,11 @@ final class RadioModule { @Override public void onConfigFlagUpdated(int flag, boolean value) { fireLater(() -> { - // TODO(b/243853343): implement config flag update method in - // android.hardware.radio.ITunerCallback + synchronized (mLock) { + fanoutAidlCallbackLocked((cb, sdkVersion) -> { + cb.onConfigFlagUpdated(flag, value); + }); + } }); } @@ -158,7 +176,9 @@ final class RadioModule { synchronized (mLock) { Map cparam = ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters); - fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam)); + fanoutAidlCallbackLocked((cb, sdkVersion) -> { + cb.onParametersUpdated(cparam); + }); } }); } @@ -222,14 +242,14 @@ final class RadioModule { mService.setTunerCallback(mHalTunerCallback); } - TunerSession openSession(android.hardware.radio.ITunerCallback userCb) + TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion) throws RemoteException { mLogger.logRadioEvent("Open TunerSession"); TunerSession tunerSession; Boolean antennaConnected; RadioManager.ProgramInfo currentProgramInfo; synchronized (mLock) { - tunerSession = new TunerSession(this, mService, userCb); + tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion); mAidlTunerSessions.add(tunerSession); antennaConnected = mAntennaConnected; currentProgramInfo = mCurrentProgramInfo; @@ -382,7 +402,8 @@ final class RadioModule { } interface AidlCallbackRunnable { - void run(android.hardware.radio.ITunerCallback callback) throws RemoteException; + void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion) + throws RemoteException; } // Invokes runnable with each TunerSession currently open. @@ -399,7 +420,8 @@ final class RadioModule { List deadSessions = null; for (int i = 0; i < mAidlTunerSessions.size(); i++) { try { - runnable.run(mAidlTunerSessions.valueAt(i).mCallback); + runnable.run(mAidlTunerSessions.valueAt(i).mCallback, + mAidlTunerSessions.valueAt(i).getTargetSdkVersion()); } catch (DeadObjectException ex) { // The other side died without calling close(), so just purge it from our records. Slogf.e(TAG, "Removing dead TunerSession"); diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java index 1ce4044d4c51c..d700ed01b3c35 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java @@ -21,7 +21,6 @@ import android.graphics.Bitmap; import android.hardware.broadcastradio.ConfigFlag; import android.hardware.broadcastradio.IBroadcastRadio; import android.hardware.radio.ITuner; -import android.hardware.radio.ITunerCallback; import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; @@ -47,6 +46,7 @@ final class TunerSession extends ITuner.Stub { private final RadioLogger mLogger; private final RadioModule mModule; final android.hardware.radio.ITunerCallback mCallback; + private final int mTargetSdkVersion; private final IBroadcastRadio mService; @GuardedBy("mLock") @@ -61,10 +61,11 @@ final class TunerSession extends ITuner.Stub { private RadioManager.BandConfig mPlaceHolderConfig; TunerSession(RadioModule radioModule, IBroadcastRadio service, - android.hardware.radio.ITunerCallback callback) { + android.hardware.radio.ITunerCallback callback, int targetSdkVersion) { mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null"); mService = Objects.requireNonNull(service, "service cannot be null"); mCallback = Objects.requireNonNull(callback, "callback cannot be null"); + mTargetSdkVersion = targetSdkVersion; mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE); } @@ -129,7 +130,7 @@ final class TunerSession extends ITuner.Stub { mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null"); } Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL"); - mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config)); + mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config)); } @Override @@ -178,8 +179,8 @@ final class TunerSession extends ITuner.Stub { } @Override - public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException { - mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s", + public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException { + mLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s", directionDown ? "down" : "up", skipSubChannel ? "yes" : "no"); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, "Cannot scan on AIDL HAL client from non-current user"); @@ -232,8 +233,7 @@ final class TunerSession extends ITuner.Stub { @Override public void cancelAnnouncement() { - // TODO(b/244485175): deperacte cancelAnnouncement - Slogf.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL"); + Slogf.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL"); } @Override @@ -244,12 +244,14 @@ final class TunerSession extends ITuner.Stub { @Override public boolean startBackgroundScan() { - Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL"); + Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL AIDL"); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user"); return false; } - mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete); + mModule.fanoutAidlCallback((cb, sdkVersion) -> { + cb.onBackgroundScanComplete(); + }); return true; } @@ -277,6 +279,10 @@ final class TunerSession extends ITuner.Stub { mModule.onTunerSessionProgramListFilterChanged(this); } + int getTargetSdkVersion() { + return mTargetSdkVersion; + } + ProgramList.Filter getProgramListFilter() { synchronized (mLock) { return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter(); @@ -312,7 +318,14 @@ final class TunerSession extends ITuner.Stub { } for (int i = 0; i < chunks.size(); i++) { try { - mCallback.onProgramListUpdated(chunks.get(i)); + if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) { + ProgramList.Chunk downgradedChunk = + ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i), + getTargetSdkVersion()); + mCallback.onProgramListUpdated(downgradedChunk); + } else { + mCallback.onProgramListUpdated(chunks.get(i)); + } } catch (RemoteException ex) { Slogf.w(TAG, ex, "mCallback.onProgramListUpdated() failed"); } diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java index ed8a37aab14a3..8e5f6b5b86240 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java @@ -189,9 +189,9 @@ class Tuner extends ITuner.Stub { } @Override - public void scan(boolean directionDown, boolean skipSubChannel) { + public void seek(boolean directionDown, boolean skipSubChannel) { if (!RadioServiceUserController.isCurrentOrSystemUser()) { - Slogf.w(TAG, "Cannot scan on HAL 1.x client from non-current user"); + Slogf.w(TAG, "Cannot seek on HAL 1.x client from non-current user"); return; } synchronized (mLock) { diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java index 0cc3833896bec..aa43b7581fe7e 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java @@ -173,9 +173,14 @@ class TunerCallback implements ITunerCallback { dispatch(() -> mClientCallback.onProgramListUpdated(chunk)); } + @Override + public void onConfigFlagUpdated(int flag, boolean value) { + Slog.w(TAG, "Not applicable for HAL 1.x"); + } + @Override public void onParametersUpdated(Map parameters) { - Slog.e(TAG, "Not applicable for HAL 1.x"); + Slog.w(TAG, "Not applicable for HAL 1.x"); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 984bf5125582e..1e31f200fd47d 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -169,7 +169,7 @@ public final class BroadcastRadioService { } public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, - boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException { + boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException { Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user"); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index 3daf1dbd933ae..98a450fa45e47 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -36,6 +36,7 @@ import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; +import android.hardware.radio.RadioTuner; import android.os.ParcelableException; import android.util.Slog; @@ -81,6 +82,27 @@ class Convert { } } + @RadioTuner.TunerResultType + static int halResultToTunerResult(int result) { + switch (result) { + case Result.OK: + return RadioTuner.TUNER_RESULT_OK; + case Result.INTERNAL_ERROR: + return RadioTuner.TUNER_RESULT_INTERNAL_ERROR; + case Result.INVALID_ARGUMENTS: + return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS; + case Result.INVALID_STATE: + return RadioTuner.TUNER_RESULT_INVALID_STATE; + case Result.NOT_SUPPORTED: + return RadioTuner.TUNER_RESULT_NOT_SUPPORTED; + case Result.TIMEOUT: + return RadioTuner.TUNER_RESULT_TIMEOUT; + case Result.UNKNOWN_ERROR: + default: + return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR; + } + } + static @NonNull ArrayList vendorInfoToHal(@Nullable Map info) { if (info == null) return new ArrayList<>(); @@ -130,6 +152,7 @@ class Convert { case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: + case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT: return ProgramSelector.PROGRAM_TYPE_DAB; case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 0ea5f0fc1d6a0..59a81544d8ef5 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -87,8 +87,9 @@ final class RadioModule { fireLater(() -> { android.hardware.radio.ProgramSelector csel = Convert.programSelectorFromHal(programSelector); + int tunerResult = Convert.halResultToTunerResult(result); synchronized (mLock) { - fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel)); + fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(tunerResult, csel)); } }); } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 7afee277fe1c6..204b9649135e4 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -171,8 +171,8 @@ class TunerSession extends ITuner.Stub { } @Override - public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException { - mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s", + public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException { + mEventLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s", directionDown ? "down" : "up", skipSubChannel ? "yes" : "no"); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, "Cannot scan on HAL 2.0 client from non-current user"); @@ -214,7 +214,7 @@ class TunerSession extends ITuner.Stub { @Override public void cancelAnnouncement() { - Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0"); + Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0"); } @Override @@ -225,7 +225,7 @@ class TunerSession extends ITuner.Stub { @Override public boolean startBackgroundScan() { - Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0"); + Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0"); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, "Cannot start background scan on HAL 2.0 client from non-current user");