Merge "Implement ProgramSelector for font-end Java APIs."
This commit is contained in:
committed by
Android (Google) Code Review
commit
38d9ee4cd6
@@ -712,6 +712,7 @@ aidl_files := \
|
||||
frameworks/base/core/java/android/print/PrinterInfo.aidl \
|
||||
frameworks/base/core/java/android/print/PrintJobId.aidl \
|
||||
frameworks/base/core/java/android/printservice/recommendation/RecommendationInfo.aidl \
|
||||
frameworks/base/core/java/android/hardware/radio/ProgramSelector.aidl \
|
||||
frameworks/base/core/java/android/hardware/radio/RadioManager.aidl \
|
||||
frameworks/base/core/java/android/hardware/radio/RadioMetadata.aidl \
|
||||
frameworks/base/core/java/android/hardware/usb/UsbDevice.aidl \
|
||||
|
||||
@@ -17215,6 +17215,63 @@ package android.hardware.location {
|
||||
|
||||
package android.hardware.radio {
|
||||
|
||||
public final class ProgramSelector implements android.os.Parcelable {
|
||||
ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]);
|
||||
method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int);
|
||||
method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int, int);
|
||||
method public int describeContents();
|
||||
method public android.hardware.radio.ProgramSelector.Identifier[] getAllIds(int);
|
||||
method public long getFirstId(int);
|
||||
method public android.hardware.radio.ProgramSelector.Identifier getPrimaryId();
|
||||
method public int getProgramType();
|
||||
method public android.hardware.radio.ProgramSelector.Identifier[] getSecondaryIds();
|
||||
method public long[] getVendorIds();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
|
||||
field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
|
||||
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_DRMO_FREQUENCY = 10; // 0xa
|
||||
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
|
||||
field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
|
||||
field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
|
||||
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
|
||||
field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 12; // 0xc
|
||||
field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 11; // 0xb
|
||||
field public static final int IDENTIFIER_TYPE_VENDOR1_PRIMARY = 13; // 0xd
|
||||
field public static final int IDENTIFIER_TYPE_VENDOR2_PRIMARY = 14; // 0xe
|
||||
field public static final int IDENTIFIER_TYPE_VENDOR3_PRIMARY = 15; // 0xf
|
||||
field public static final int IDENTIFIER_TYPE_VENDOR4_PRIMARY = 16; // 0x10
|
||||
field public static final int PROGRAM_TYPE_AM = 1; // 0x1
|
||||
field public static final int PROGRAM_TYPE_AM_HD = 3; // 0x3
|
||||
field public static final int PROGRAM_TYPE_DAB = 5; // 0x5
|
||||
field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6
|
||||
field public static final int PROGRAM_TYPE_FM = 2; // 0x2
|
||||
field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4
|
||||
field public static final int PROGRAM_TYPE_SXM = 7; // 0x7
|
||||
field public static final int PROGRAM_TYPE_VENDOR1 = 8; // 0x8
|
||||
field public static final int PROGRAM_TYPE_VENDOR2 = 9; // 0x9
|
||||
field public static final int PROGRAM_TYPE_VENDOR3 = 10; // 0xa
|
||||
field public static final int PROGRAM_TYPE_VENDOR4 = 11; // 0xb
|
||||
}
|
||||
|
||||
public static final class ProgramSelector.Identifier implements android.os.Parcelable {
|
||||
ctor public ProgramSelector.Identifier(int, long);
|
||||
method public int describeContents();
|
||||
method public int getType();
|
||||
method public long getValue();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
|
||||
}
|
||||
|
||||
public static abstract class ProgramSelector.IdentifierType implements java.lang.annotation.Annotation {
|
||||
}
|
||||
|
||||
public static abstract class ProgramSelector.ProgramType implements java.lang.annotation.Annotation {
|
||||
}
|
||||
|
||||
public class RadioManager {
|
||||
method public int listModules(java.util.List<android.hardware.radio.RadioManager.ModuleProperties>);
|
||||
method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler);
|
||||
@@ -17222,6 +17279,7 @@ package android.hardware.radio {
|
||||
field public static final int BAND_AM_HD = 3; // 0x3
|
||||
field public static final int BAND_FM = 1; // 0x1
|
||||
field public static final int BAND_FM_HD = 2; // 0x2
|
||||
field public static final int BAND_INVALID = -1; // 0xffffffff
|
||||
field public static final int CLASS_AM_FM = 0; // 0x0
|
||||
field public static final int CLASS_DT = 2; // 0x2
|
||||
field public static final int CLASS_SAT = 1; // 0x1
|
||||
@@ -17257,6 +17315,9 @@ package android.hardware.radio {
|
||||
field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.AmBandDescriptor> CREATOR;
|
||||
}
|
||||
|
||||
public static abstract class RadioManager.Band implements java.lang.annotation.Annotation {
|
||||
}
|
||||
|
||||
public static class RadioManager.BandConfig implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public int getLowerLimit();
|
||||
@@ -17331,10 +17392,11 @@ package android.hardware.radio {
|
||||
|
||||
public static class RadioManager.ProgramInfo implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public int getChannel();
|
||||
method public deprecated int getChannel();
|
||||
method public android.hardware.radio.RadioMetadata getMetadata();
|
||||
method public android.hardware.radio.ProgramSelector getSelector();
|
||||
method public int getSignalStrength();
|
||||
method public int getSubChannel();
|
||||
method public deprecated int getSubChannel();
|
||||
method public java.lang.String getVendorExension();
|
||||
method public boolean isDigital();
|
||||
method public boolean isLive();
|
||||
@@ -17406,7 +17468,8 @@ package android.hardware.radio {
|
||||
method public abstract int setMute(boolean);
|
||||
method public abstract boolean startBackgroundScan();
|
||||
method public abstract int step(int, boolean);
|
||||
method public abstract int tune(int, int);
|
||||
method public abstract deprecated int tune(int, int);
|
||||
method public abstract void tune(android.hardware.radio.ProgramSelector);
|
||||
field public static final int DIRECTION_DOWN = 1; // 0x1
|
||||
field public static final int DIRECTION_UP = 0; // 0x0
|
||||
field public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.hardware.radio;
|
||||
|
||||
import android.hardware.radio.ProgramSelector;
|
||||
import android.hardware.radio.RadioManager;
|
||||
|
||||
/** {@hide} */
|
||||
@@ -52,7 +53,7 @@ interface ITuner {
|
||||
* @throws IllegalArgumentException if invalid arguments are passed
|
||||
* @throws IllegalStateException if called out of sequence
|
||||
*/
|
||||
void tune(int channel, int subChannel);
|
||||
void tune(in ProgramSelector selector);
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if called out of sequence
|
||||
|
||||
20
core/java/android/hardware/radio/ProgramSelector.aidl
Normal file
20
core/java/android/hardware/radio/ProgramSelector.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.radio;
|
||||
|
||||
/** @hide */
|
||||
parcelable ProgramSelector;
|
||||
506
core/java/android/hardware/radio/ProgramSelector.java
Normal file
506
core/java/android/hardware/radio/ProgramSelector.java
Normal file
@@ -0,0 +1,506 @@
|
||||
/**
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.radio;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A set of identifiers necessary to tune to a given station.
|
||||
*
|
||||
* This can hold various identifiers, like
|
||||
* - AM/FM frequency
|
||||
* - HD Radio subchannel
|
||||
* - DAB channel info
|
||||
*
|
||||
* The primary ID uniquely identifies a station and can be used for equality
|
||||
* check. The secondary IDs are supplementary and can speed up tuning process,
|
||||
* but the primary ID is sufficient (ie. after a full band scan).
|
||||
*
|
||||
* Two selectors with different secondary IDs, but the same primary ID are
|
||||
* considered equal. In particular, secondary IDs vector may get updated for
|
||||
* an entry on the program list (ie. when a better frequency for a given
|
||||
* station is found).
|
||||
*
|
||||
* The primaryId of a given programType MUST be of a specific type:
|
||||
* - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;
|
||||
* - AM_HD, FM_HD: HD_STATION_ID_EXT;
|
||||
* - DAB: DAB_SIDECC;
|
||||
* - DRMO: DRMO_SERVICE_ID;
|
||||
* - SXM: SXM_SERVICE_ID;
|
||||
* - VENDOR: VENDOR_PRIMARY.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class ProgramSelector implements Parcelable {
|
||||
/** Analogue AM radio (with or without RDS). */
|
||||
public static final int PROGRAM_TYPE_AM = 1;
|
||||
/** analogue FM radio (with or without RDS). */
|
||||
public static final int PROGRAM_TYPE_FM = 2;
|
||||
/** AM HD Radio. */
|
||||
public static final int PROGRAM_TYPE_AM_HD = 3;
|
||||
/** FM HD Radio. */
|
||||
public static final int PROGRAM_TYPE_FM_HD = 4;
|
||||
/** Digital audio broadcasting. */
|
||||
public static final int PROGRAM_TYPE_DAB = 5;
|
||||
/** Digital Radio Mondiale. */
|
||||
public static final int PROGRAM_TYPE_DRMO = 6;
|
||||
/** SiriusXM Satellite Radio. */
|
||||
public static final int PROGRAM_TYPE_SXM = 7;
|
||||
/** Vendor-specific, not synced across devices. */
|
||||
public static final int PROGRAM_TYPE_VENDOR1 = 8;
|
||||
public static final int PROGRAM_TYPE_VENDOR2 = 9;
|
||||
public static final int PROGRAM_TYPE_VENDOR3 = 10;
|
||||
public static final int PROGRAM_TYPE_VENDOR4 = 11;
|
||||
@IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
|
||||
PROGRAM_TYPE_AM,
|
||||
PROGRAM_TYPE_FM,
|
||||
PROGRAM_TYPE_AM_HD,
|
||||
PROGRAM_TYPE_FM_HD,
|
||||
PROGRAM_TYPE_DAB,
|
||||
PROGRAM_TYPE_DRMO,
|
||||
PROGRAM_TYPE_SXM,
|
||||
PROGRAM_TYPE_VENDOR1,
|
||||
PROGRAM_TYPE_VENDOR2,
|
||||
PROGRAM_TYPE_VENDOR3,
|
||||
PROGRAM_TYPE_VENDOR4,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ProgramType {}
|
||||
|
||||
/** kHz */
|
||||
public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
|
||||
/** 16bit */
|
||||
public static final int IDENTIFIER_TYPE_RDS_PI = 2;
|
||||
/**
|
||||
* 64bit compound primary identifier for HD Radio.
|
||||
*
|
||||
* Consists of (from the LSB):
|
||||
* - 32bit: Station ID number;
|
||||
* - 4bit: HD_SUBCHANNEL;
|
||||
* - 18bit: AMFM_FREQUENCY.
|
||||
* The remaining bits should be set to zeros when writing on the chip side
|
||||
* and ignored when read.
|
||||
*/
|
||||
public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
|
||||
/**
|
||||
* HD Radio subchannel - a value of range 0-7.
|
||||
*
|
||||
* The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
|
||||
* as opposed to HD Radio standard (where it's 1-based).
|
||||
*/
|
||||
public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4;
|
||||
/**
|
||||
* 24bit compound primary identifier for DAB.
|
||||
*
|
||||
* Consists of (from the LSB):
|
||||
* - 16bit: SId;
|
||||
* - 8bit: ECC code.
|
||||
* 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_SIDECC = 5;
|
||||
/** 16bit */
|
||||
public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
|
||||
/** 12bit */
|
||||
public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
|
||||
/** kHz */
|
||||
public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
|
||||
/** 24bit */
|
||||
public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
|
||||
/** kHz */
|
||||
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
|
||||
/** 32bit */
|
||||
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 11;
|
||||
/** 0-999 range */
|
||||
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 12;
|
||||
/**
|
||||
* Primary identifier for vendor-specific radio technology.
|
||||
* The value format is determined by a vendor.
|
||||
*
|
||||
* It must not be used in any other programType than VENDORx.
|
||||
*/
|
||||
public static final int IDENTIFIER_TYPE_VENDOR1_PRIMARY = 13;
|
||||
public static final int IDENTIFIER_TYPE_VENDOR2_PRIMARY = 14;
|
||||
public static final int IDENTIFIER_TYPE_VENDOR3_PRIMARY = 15;
|
||||
public static final int IDENTIFIER_TYPE_VENDOR4_PRIMARY = 16;
|
||||
@IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
|
||||
IDENTIFIER_TYPE_AMFM_FREQUENCY,
|
||||
IDENTIFIER_TYPE_RDS_PI,
|
||||
IDENTIFIER_TYPE_HD_STATION_ID_EXT,
|
||||
IDENTIFIER_TYPE_HD_SUBCHANNEL,
|
||||
IDENTIFIER_TYPE_DAB_SIDECC,
|
||||
IDENTIFIER_TYPE_DAB_ENSEMBLE,
|
||||
IDENTIFIER_TYPE_DAB_SCID,
|
||||
IDENTIFIER_TYPE_DAB_FREQUENCY,
|
||||
IDENTIFIER_TYPE_DRMO_SERVICE_ID,
|
||||
IDENTIFIER_TYPE_DRMO_FREQUENCY,
|
||||
IDENTIFIER_TYPE_SXM_SERVICE_ID,
|
||||
IDENTIFIER_TYPE_SXM_CHANNEL,
|
||||
IDENTIFIER_TYPE_VENDOR1_PRIMARY,
|
||||
IDENTIFIER_TYPE_VENDOR2_PRIMARY,
|
||||
IDENTIFIER_TYPE_VENDOR3_PRIMARY,
|
||||
IDENTIFIER_TYPE_VENDOR4_PRIMARY,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface IdentifierType {}
|
||||
|
||||
private final @ProgramType int mProgramType;
|
||||
private final @NonNull Identifier mPrimaryId;
|
||||
private final @NonNull Identifier[] mSecondaryIds;
|
||||
private final @NonNull long[] mVendorIds;
|
||||
|
||||
/**
|
||||
* Constructor for ProgramSelector.
|
||||
*
|
||||
* It's not desired to modify selector objects, so all its fields are initialized at creation.
|
||||
*
|
||||
* Identifier lists must not contain any nulls, but can itself be null to be interpreted
|
||||
* as empty list at object creation.
|
||||
*
|
||||
* @param programType type of a radio technology.
|
||||
* @param primaryId primary program identifier.
|
||||
* @param secondaryIds list of secondary program identifiers.
|
||||
* @param vendorIds list of vendor-specific program identifiers.
|
||||
*/
|
||||
public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId,
|
||||
@Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) {
|
||||
if (secondaryIds == null) secondaryIds = new Identifier[0];
|
||||
if (vendorIds == null) vendorIds = new long[0];
|
||||
if (Stream.of(secondaryIds).anyMatch(id -> id == null)) {
|
||||
throw new IllegalArgumentException("secondaryIds list must not contain nulls");
|
||||
}
|
||||
mProgramType = programType;
|
||||
mPrimaryId = Objects.requireNonNull(primaryId);
|
||||
mSecondaryIds = secondaryIds;
|
||||
mVendorIds = vendorIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of a radio technology.
|
||||
*
|
||||
* @returns program type.
|
||||
*/
|
||||
public @ProgramType int getProgramType() {
|
||||
return mProgramType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary program identifier uniquely identifies a station and is used to
|
||||
* determine equality between two ProgramSelectors.
|
||||
*
|
||||
* @returns primary identifier.
|
||||
*/
|
||||
public @NonNull Identifier getPrimaryId() {
|
||||
return mPrimaryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secondary program identifier is not required for tuning, but may make it
|
||||
* faster or more reliable.
|
||||
*
|
||||
* @returns secondary identifier list, must not be modified.
|
||||
*/
|
||||
public @NonNull Identifier[] getSecondaryIds() {
|
||||
return mSecondaryIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up an identifier of a given type (either primary or secondary).
|
||||
*
|
||||
* If there are multiple identifiers if a given type, then first in order (where primary id is
|
||||
* before any secondary) is selected.
|
||||
*
|
||||
* @param type type of identifier.
|
||||
* @return identifier value, if found.
|
||||
* @throws IllegalArgumentException, if not found.
|
||||
*/
|
||||
public long getFirstId(@IdentifierType int type) {
|
||||
if (mPrimaryId.getType() == type) return mPrimaryId.getValue();
|
||||
for (Identifier id : mSecondaryIds) {
|
||||
if (id.getType() == type) return id.getValue();
|
||||
}
|
||||
throw new IllegalArgumentException("Identifier " + type + " not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all identifier of a given type (either primary or secondary).
|
||||
*
|
||||
* Some identifiers may be provided multiple times, for example
|
||||
* IDENTIFIER_TYPE_AMFM_FREQUENCY for FM Alternate Frequencies.
|
||||
*
|
||||
* @param type type of identifier.
|
||||
* @return a list of identifiers, generated on each call. May be modified.
|
||||
*/
|
||||
public @NonNull Identifier[] getAllIds(@IdentifierType int type) {
|
||||
List<Identifier> out = new ArrayList<>();
|
||||
|
||||
if (mPrimaryId.getType() == type) out.add(mPrimaryId);
|
||||
for (Identifier id : mSecondaryIds) {
|
||||
if (id.getType() == type) out.add(id);
|
||||
}
|
||||
|
||||
return out.toArray(new Identifier[out.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor identifiers are passed as-is to the HAL implementation,
|
||||
* preserving elements order.
|
||||
*
|
||||
* @return a array of vendor identifiers, must not be modified.
|
||||
*/
|
||||
public @NonNull long[] getVendorIds() {
|
||||
return mVendorIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds new ProgramSelector for AM/FM frequency.
|
||||
*
|
||||
* @param band the band.
|
||||
* @param frequencyKhz the frequency in kHz.
|
||||
* @return new ProgramSelector object representing given frequency.
|
||||
* @throws IllegalArgumentException if provided frequency is out of bounds.
|
||||
*/
|
||||
public static @NonNull ProgramSelector createAmFmSelector(
|
||||
@RadioManager.Band int band, int frequencyKhz) {
|
||||
return createAmFmSelector(band, frequencyKhz, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if a given AM/FM frequency is roughly valid and in correct unit.
|
||||
*
|
||||
* It does not check the range precisely. In particular, it may be way off for certain regions.
|
||||
* The main purpose is to avoid passing inproper units, ie. MHz instead of kHz.
|
||||
*
|
||||
* @param isAm true, if AM, false if FM.
|
||||
* @param frequencyKhz the frequency in kHz.
|
||||
* @return true, if the frequency is rougly valid.
|
||||
*/
|
||||
private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) {
|
||||
if (isAm) {
|
||||
return frequencyKhz > 150 && frequencyKhz < 30000;
|
||||
} else {
|
||||
return frequencyKhz > 60000 && frequencyKhz < 110000;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds new ProgramSelector for AM/FM frequency.
|
||||
*
|
||||
* This method variant supports HD Radio subchannels, but it's undesirable to
|
||||
* select them manually. Instead, the value should be retrieved from program list.
|
||||
*
|
||||
* @param band the band.
|
||||
* @param frequencyKhz the frequency in kHz.
|
||||
* @param subChannel 1-based HD Radio subchannel.
|
||||
* @return new ProgramSelector object representing given frequency.
|
||||
* @throws IllegalArgumentException if provided frequency is out of bounds,
|
||||
* or tried setting a subchannel for analog AM/FM.
|
||||
*/
|
||||
public static @NonNull ProgramSelector createAmFmSelector(
|
||||
@RadioManager.Band int band, int frequencyKhz, int subChannel) {
|
||||
boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD);
|
||||
boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD);
|
||||
if (!isAm && !isDigital && band != RadioManager.BAND_FM) {
|
||||
throw new IllegalArgumentException("Unknown band: " + band);
|
||||
}
|
||||
if (subChannel < 0 || subChannel > 8) {
|
||||
throw new IllegalArgumentException("Invalid subchannel: " + subChannel);
|
||||
}
|
||||
if (subChannel > 0 && !isDigital) {
|
||||
throw new IllegalArgumentException("Subchannels are not supported for non-HD radio");
|
||||
}
|
||||
if (!isValidAmFmFrequency(isAm, frequencyKhz)) {
|
||||
throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency");
|
||||
}
|
||||
|
||||
// We can't use AM_HD or FM_HD, because we don't know HD station ID.
|
||||
@ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM;
|
||||
Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz);
|
||||
|
||||
Identifier[] secondary = null;
|
||||
if (subChannel > 0) {
|
||||
/* Stating sub channel for non-HD AM/FM does not give any guarantees,
|
||||
* but we can't do much more without HD station ID.
|
||||
*
|
||||
* The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based.
|
||||
*/
|
||||
secondary = new Identifier[]{
|
||||
new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)};
|
||||
}
|
||||
|
||||
return new ProgramSelector(programType, primary, secondary, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
|
||||
.append(", primary=").append(mPrimaryId);
|
||||
if (mSecondaryIds.length > 0) sb.append(", secondary=").append(mSecondaryIds);
|
||||
if (mVendorIds.length > 0) sb.append(", vendor=").append(mVendorIds);
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// secondaryIds and vendorIds are ignored for equality/hashing
|
||||
return Objects.hash(mProgramType, mPrimaryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof ProgramSelector)) return false;
|
||||
ProgramSelector other = (ProgramSelector) obj;
|
||||
// secondaryIds and vendorIds are ignored for equality/hashing
|
||||
return other.getProgramType() == mProgramType && mPrimaryId.equals(other.getPrimaryId());
|
||||
}
|
||||
|
||||
private ProgramSelector(Parcel in) {
|
||||
mProgramType = in.readInt();
|
||||
mPrimaryId = in.readTypedObject(Identifier.CREATOR);
|
||||
mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
|
||||
if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
|
||||
throw new IllegalArgumentException("secondaryIds list must not contain nulls");
|
||||
}
|
||||
mVendorIds = in.createLongArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mProgramType);
|
||||
dest.writeTypedObject(mPrimaryId, 0);
|
||||
dest.writeTypedArray(mSecondaryIds, 0);
|
||||
dest.writeLongArray(mVendorIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ProgramSelector> CREATOR =
|
||||
new Parcelable.Creator<ProgramSelector>() {
|
||||
public ProgramSelector createFromParcel(Parcel in) {
|
||||
return new ProgramSelector(in);
|
||||
}
|
||||
|
||||
public ProgramSelector[] newArray(int size) {
|
||||
return new ProgramSelector[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A single program identifier component, eg. frequency or channel ID.
|
||||
*
|
||||
* The long value field holds the value in format described in comments for
|
||||
* IdentifierType constants.
|
||||
*/
|
||||
public static final class Identifier implements Parcelable {
|
||||
private final @IdentifierType int mType;
|
||||
private final long mValue;
|
||||
|
||||
public Identifier(@IdentifierType int type, long value) {
|
||||
mType = type;
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of an identifier.
|
||||
*
|
||||
* @return type of an identifier.
|
||||
*/
|
||||
public @IdentifierType int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value of an identifier.
|
||||
*
|
||||
* Its meaning depends on identifier type, ie. for IDENTIFIER_TYPE_AMFM_FREQUENCY type,
|
||||
* the value is a frequency in kHz.
|
||||
*
|
||||
* The range of a value depends on its type; it does not always require the whole long
|
||||
* range. Casting to necessary type (ie. int) without range checking is correct in front-end
|
||||
* code - any range violations are either errors in the framework or in the
|
||||
* HAL implementation. For example, IDENTIFIER_TYPE_AMFM_FREQUENCY always fits in int,
|
||||
* as Integer.MAX_VALUE would mean 2.1THz.
|
||||
*
|
||||
* @return value of an identifier.
|
||||
*/
|
||||
public long getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Identifier(" + mType + ", " + mValue + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mType, mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Identifier)) return false;
|
||||
Identifier other = (Identifier) obj;
|
||||
return other.getType() == mType && other.getValue() == mValue;
|
||||
}
|
||||
|
||||
private Identifier(Parcel in) {
|
||||
mType = in.readInt();
|
||||
mValue = in.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mType);
|
||||
dest.writeLong(mValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Identifier> CREATOR =
|
||||
new Parcelable.Creator<Identifier>() {
|
||||
public Identifier createFromParcel(Parcel in) {
|
||||
return new Identifier(in);
|
||||
}
|
||||
|
||||
public Identifier[] newArray(int size) {
|
||||
return new Identifier[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.hardware.radio;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
@@ -29,6 +30,8 @@ import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -70,7 +73,7 @@ public class RadioManager {
|
||||
/** Radio module class supporting Digital terrestrial radio */
|
||||
public static final int CLASS_DT = 2;
|
||||
|
||||
// keep in sync with radio_band_t in /system/core/incluse/system/radio.h
|
||||
public static final int BAND_INVALID = -1;
|
||||
/** AM radio band (LW/MW/SW).
|
||||
* @see BandDescriptor */
|
||||
public static final int BAND_AM = 0;
|
||||
@@ -83,6 +86,15 @@ public class RadioManager {
|
||||
/** AM HD radio or DRM band.
|
||||
* @see BandDescriptor */
|
||||
public static final int BAND_AM_HD = 3;
|
||||
@IntDef(prefix = { "BAND_" }, value = {
|
||||
BAND_INVALID,
|
||||
BAND_AM,
|
||||
BAND_FM,
|
||||
BAND_AM_HD,
|
||||
BAND_FM_HD,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Band {}
|
||||
|
||||
// keep in sync with radio_region_t in /system/core/incluse/system/radio.h
|
||||
/** Africa, Europe.
|
||||
@@ -1258,8 +1270,7 @@ public class RadioManager {
|
||||
private final static int FLAG_LIVE = 1 << 0;
|
||||
private final static int FLAG_MUTED = 1 << 1;
|
||||
|
||||
private final int mChannel;
|
||||
private final int mSubChannel;
|
||||
@NonNull private final ProgramSelector mSelector;
|
||||
private final boolean mTuned;
|
||||
private final boolean mStereo;
|
||||
private final boolean mDigital;
|
||||
@@ -1268,11 +1279,10 @@ public class RadioManager {
|
||||
private final RadioMetadata mMetadata;
|
||||
private final String mVendorExension;
|
||||
|
||||
ProgramInfo(int channel, int subChannel, boolean tuned, boolean stereo,
|
||||
ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
|
||||
boolean digital, int signalStrength, RadioMetadata metadata, int flags,
|
||||
String vendorExension) {
|
||||
mChannel = channel;
|
||||
mSubChannel = subChannel;
|
||||
mSelector = selector;
|
||||
mTuned = tuned;
|
||||
mStereo = stereo;
|
||||
mDigital = digital;
|
||||
@@ -1282,19 +1292,45 @@ public class RadioManager {
|
||||
mVendorExension = vendorExension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Program selector, necessary for tuning to a program.
|
||||
*
|
||||
* @return the program selector.
|
||||
*/
|
||||
public @NonNull ProgramSelector getSelector() {
|
||||
return mSelector;
|
||||
}
|
||||
|
||||
/** Main channel expressed in units according to band type.
|
||||
* Currently all defined band types express channels as frequency in kHz
|
||||
* @return the program channel
|
||||
* @deprecated Use {@link getSelector()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getChannel() {
|
||||
return mChannel;
|
||||
try {
|
||||
return (int) mSelector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Log.w(TAG, "Not an AM/FM program");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sub channel ID. E.g 1 for HD radio HD1
|
||||
* @return the program sub channel
|
||||
* @deprecated Use {@link getSelector()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getSubChannel() {
|
||||
return mSubChannel;
|
||||
try {
|
||||
return (int) mSelector.getFirstId(
|
||||
ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL) + 1;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// this is a normal behavior for analog AM/FM selector
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** {@code true} if the tuner is currently tuned on a valid station
|
||||
* @return {@code true} if currently tuned, {@code false} otherwise.
|
||||
*/
|
||||
@@ -1362,8 +1398,7 @@ public class RadioManager {
|
||||
}
|
||||
|
||||
private ProgramInfo(Parcel in) {
|
||||
mChannel = in.readInt();
|
||||
mSubChannel = in.readInt();
|
||||
mSelector = in.readParcelable(null);
|
||||
mTuned = in.readByte() == 1;
|
||||
mStereo = in.readByte() == 1;
|
||||
mDigital = in.readByte() == 1;
|
||||
@@ -1390,8 +1425,7 @@ public class RadioManager {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mChannel);
|
||||
dest.writeInt(mSubChannel);
|
||||
dest.writeParcelable(mSelector, 0);
|
||||
dest.writeByte((byte)(mTuned ? 1 : 0));
|
||||
dest.writeByte((byte)(mStereo ? 1 : 0));
|
||||
dest.writeByte((byte)(mDigital ? 1 : 0));
|
||||
@@ -1413,7 +1447,7 @@ public class RadioManager {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProgramInfo [mChannel=" + mChannel + ", mSubChannel=" + mSubChannel
|
||||
return "ProgramInfo [mSelector=" + mSelector
|
||||
+ ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
|
||||
+ ", mFlags=" + mFlags + ", mSignalStrength=" + mSignalStrength
|
||||
+ ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
|
||||
@@ -1424,8 +1458,7 @@ public class RadioManager {
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + mChannel;
|
||||
result = prime * result + mSubChannel;
|
||||
result = prime * result + mSelector.hashCode();
|
||||
result = prime * result + (mTuned ? 1 : 0);
|
||||
result = prime * result + (mStereo ? 1 : 0);
|
||||
result = prime * result + (mDigital ? 1 : 0);
|
||||
@@ -1443,10 +1476,7 @@ public class RadioManager {
|
||||
if (!(obj instanceof ProgramInfo))
|
||||
return false;
|
||||
ProgramInfo other = (ProgramInfo) obj;
|
||||
if (mChannel != other.getChannel())
|
||||
return false;
|
||||
if (mSubChannel != other.getSubChannel())
|
||||
return false;
|
||||
if (!mSelector.equals(other.getSelector())) return false;
|
||||
if (mTuned != other.isTuned())
|
||||
return false;
|
||||
if (mStereo != other.isStereo())
|
||||
@@ -1541,7 +1571,7 @@ public class RadioManager {
|
||||
Log.e(TAG, "Failed to open tuner");
|
||||
return null;
|
||||
}
|
||||
return new TunerAdapter(tuner);
|
||||
return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID);
|
||||
}
|
||||
|
||||
@NonNull private final Context mContext;
|
||||
|
||||
@@ -170,9 +170,21 @@ public abstract class RadioTuner {
|
||||
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
|
||||
* service fails, </li>
|
||||
* </ul>
|
||||
* @deprecated Use {@link tune(ProgramSelector)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @thows IllegalArgumentException if the provided selector is invalid
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -32,11 +32,14 @@ class TunerAdapter extends RadioTuner {
|
||||
@NonNull private final ITuner mTuner;
|
||||
private boolean mIsClosed = false;
|
||||
|
||||
TunerAdapter(ITuner tuner) {
|
||||
private @RadioManager.Band int mBand;
|
||||
|
||||
TunerAdapter(ITuner tuner, @RadioManager.Band int band) {
|
||||
if (tuner == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
mTuner = tuner;
|
||||
mBand = band;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,6 +62,7 @@ class TunerAdapter extends RadioTuner {
|
||||
public int setConfiguration(RadioManager.BandConfig config) {
|
||||
try {
|
||||
mTuner.setConfiguration(config);
|
||||
mBand = config.getType();
|
||||
return RadioManager.STATUS_OK;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Can't set configuration", e);
|
||||
@@ -138,7 +142,7 @@ class TunerAdapter extends RadioTuner {
|
||||
@Override
|
||||
public int tune(int channel, int subChannel) {
|
||||
try {
|
||||
mTuner.tune(channel, subChannel);
|
||||
mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
|
||||
} catch (IllegalStateException e) {
|
||||
Log.e(TAG, "Can't tune", e);
|
||||
return RadioManager.STATUS_INVALID_OPERATION;
|
||||
@@ -152,6 +156,15 @@ class TunerAdapter extends RadioTuner {
|
||||
return RadioManager.STATUS_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tune(@NonNull ProgramSelector selector) {
|
||||
try {
|
||||
mTuner.tune(selector);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("service died", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancel() {
|
||||
try {
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.server.radio;
|
||||
import android.annotation.NonNull;
|
||||
import android.hardware.radio.ITuner;
|
||||
import android.hardware.radio.ITunerCallback;
|
||||
import android.hardware.radio.ProgramSelector;
|
||||
import android.hardware.radio.RadioManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
@@ -44,12 +45,13 @@ class Tuner extends ITuner.Stub {
|
||||
private int mRegion; // TODO(b/62710330): find better solution to handle regions
|
||||
private final boolean mWithAudio;
|
||||
|
||||
Tuner(@NonNull ITunerCallback clientCallback, int halRev, int region, boolean withAudio) {
|
||||
Tuner(@NonNull ITunerCallback clientCallback, int halRev,
|
||||
int region, boolean withAudio, int band) {
|
||||
mClientCallback = clientCallback;
|
||||
mTunerCallback = new TunerCallback(this, clientCallback, halRev);
|
||||
mRegion = region;
|
||||
mWithAudio = withAudio;
|
||||
mNativeContext = nativeInit(halRev, withAudio);
|
||||
mNativeContext = nativeInit(halRev, withAudio, band);
|
||||
mDeathRecipient = this::close;
|
||||
try {
|
||||
mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0);
|
||||
@@ -64,7 +66,7 @@ class Tuner extends ITuner.Stub {
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
private native long nativeInit(int halRev, boolean withAudio);
|
||||
private native long nativeInit(int halRev, boolean withAudio, int band);
|
||||
private native void nativeFinalize(long nativeContext);
|
||||
private native void nativeClose(long nativeContext);
|
||||
|
||||
@@ -74,7 +76,7 @@ class Tuner extends ITuner.Stub {
|
||||
|
||||
private native void nativeStep(long nativeContext, boolean directionDown, boolean skipSubChannel);
|
||||
private native void nativeScan(long nativeContext, boolean directionDown, boolean skipSubChannel);
|
||||
private native void nativeTune(long nativeContext, int channel, int subChannel);
|
||||
private native void nativeTune(long nativeContext, @NonNull ProgramSelector selector);
|
||||
private native void nativeCancel(long nativeContext);
|
||||
|
||||
private native RadioManager.ProgramInfo nativeGetProgramInformation(long nativeContext);
|
||||
@@ -173,10 +175,14 @@ class Tuner extends ITuner.Stub {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tune(int channel, int subChannel) {
|
||||
public void tune(ProgramSelector selector) {
|
||||
if (selector == null) {
|
||||
throw new IllegalArgumentException("The argument must not be a null pointer");
|
||||
}
|
||||
Slog.i(TAG, "Tuning to " + selector);
|
||||
synchronized (mLock) {
|
||||
checkNotClosedLocked();
|
||||
nativeTune(mNativeContext, channel, subChannel);
|
||||
nativeTune(mNativeContext, selector);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,4 +108,6 @@ LOCAL_SHARED_LIBRARIES += \
|
||||
android.frameworks.schedulerservice@1.0 \
|
||||
android.frameworks.sensorservice@1.0 \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES += libscrypt_static
|
||||
LOCAL_STATIC_LIBRARIES += \
|
||||
android.hardware.broadcastradio@1.1-utils-lib \
|
||||
libscrypt_static \
|
||||
|
||||
@@ -210,7 +210,7 @@ static jobject nativeOpenTuner(JNIEnv *env, jobject obj, long nativeContext, jin
|
||||
BandConfig bandConfigHal = convert::BandConfigToHal(env, bandConfig, region);
|
||||
|
||||
auto tuner = make_javaref(env, env->NewObject(gjni.Tuner.clazz, gjni.Tuner.cstor,
|
||||
callback, halRev, region, withAudio));
|
||||
callback, halRev, region, withAudio, bandConfigHal.type));
|
||||
if (tuner == nullptr) {
|
||||
ALOGE("Unable to create new tuner object.");
|
||||
return nullptr;
|
||||
@@ -258,7 +258,7 @@ void register_android_server_radio_RadioService(JNIEnv *env) {
|
||||
auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
|
||||
gjni.Tuner.clazz = MakeGlobalRefOrDie(env, tunerClass);
|
||||
gjni.Tuner.cstor = GetMethodIDOrDie(env, tunerClass, "<init>",
|
||||
"(Landroid/hardware/radio/ITunerCallback;IIZ)V");
|
||||
"(Landroid/hardware/radio/ITunerCallback;IIZI)V");
|
||||
|
||||
auto arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
|
||||
gjni.ArrayList.clazz = MakeGlobalRefOrDie(env, arrayListClass);
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
#include "com_android_server_radio_convert.h"
|
||||
#include "com_android_server_radio_TunerCallback.h"
|
||||
|
||||
#include <JNIHelp.h>
|
||||
#include <Utils.h>
|
||||
#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <core_jni_helpers.h>
|
||||
#include <media/AudioSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <JNIHelp.h>
|
||||
|
||||
namespace android {
|
||||
namespace server {
|
||||
@@ -41,6 +42,7 @@ using hardware::hidl_vec;
|
||||
namespace V1_0 = hardware::broadcastradio::V1_0;
|
||||
namespace V1_1 = hardware::broadcastradio::V1_1;
|
||||
|
||||
using V1_0::Band;
|
||||
using V1_0::BandConfig;
|
||||
using V1_0::MetaData;
|
||||
using V1_0::Result;
|
||||
@@ -79,6 +81,7 @@ struct TunerContext {
|
||||
bool mIsClosed = false;
|
||||
HalRevision mHalRev;
|
||||
bool mWithAudio;
|
||||
Band mBand;
|
||||
sp<V1_0::ITuner> mHalTuner;
|
||||
sp<V1_1::ITuner> mHalTuner11;
|
||||
sp<HalDeathRecipient> mHalDeathRecipient;
|
||||
@@ -100,13 +103,14 @@ static TunerContext& getNativeContext(JNIEnv *env, JavaRef<jobject> const &jTune
|
||||
return getNativeContext(env->GetLongField(jTuner.get(), gjni.Tuner.nativeContext));
|
||||
}
|
||||
|
||||
static jlong nativeInit(JNIEnv *env, jobject obj, jint halRev, bool withAudio) {
|
||||
static jlong nativeInit(JNIEnv *env, jobject obj, jint halRev, bool withAudio, jint band) {
|
||||
ALOGV("nativeInit()");
|
||||
AutoMutex _l(gContextMutex);
|
||||
|
||||
auto ctx = new TunerContext();
|
||||
ctx->mHalRev = static_cast<HalRevision>(halRev);
|
||||
ctx->mWithAudio = withAudio;
|
||||
ctx->mBand = static_cast<Band>(band);
|
||||
|
||||
static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer");
|
||||
return reinterpret_cast<jlong>(ctx);
|
||||
@@ -170,13 +174,17 @@ void setHalTuner(JNIEnv *env, JavaRef<jobject> const &jTuner, sp<V1_0::ITuner> h
|
||||
notifyAudioService(ctx, true);
|
||||
}
|
||||
|
||||
sp<V1_0::ITuner> getHalTuner(jlong nativeContext) {
|
||||
AutoMutex _l(gContextMutex);
|
||||
auto tuner = getNativeContext(nativeContext).mHalTuner;
|
||||
static sp<V1_0::ITuner> getHalTuner(const TunerContext& ctx) {
|
||||
auto tuner = ctx.mHalTuner;
|
||||
LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner is not open");
|
||||
return tuner;
|
||||
}
|
||||
|
||||
sp<V1_0::ITuner> getHalTuner(jlong nativeContext) {
|
||||
AutoMutex _l(gContextMutex);
|
||||
return getHalTuner(getNativeContext(nativeContext));
|
||||
}
|
||||
|
||||
sp<V1_1::ITuner> getHalTuner11(jlong nativeContext) {
|
||||
AutoMutex _l(gContextMutex);
|
||||
return getNativeContext(nativeContext).mHalTuner11;
|
||||
@@ -215,13 +223,17 @@ static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) {
|
||||
|
||||
static void nativeSetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, jobject config) {
|
||||
ALOGV("nativeSetConfiguration()");
|
||||
auto halTuner = getHalTuner(nativeContext);
|
||||
AutoMutex _l(gContextMutex);
|
||||
auto& ctx = getNativeContext(nativeContext);
|
||||
auto halTuner = getHalTuner(ctx);
|
||||
if (halTuner == nullptr) return;
|
||||
|
||||
Region region_unused;
|
||||
BandConfig bandConfigHal = convert::BandConfigToHal(env, config, region_unused);
|
||||
|
||||
convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal));
|
||||
if (convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal))) return;
|
||||
|
||||
ctx.mBand = bandConfigHal.type;
|
||||
}
|
||||
|
||||
static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext,
|
||||
@@ -263,13 +275,26 @@ static void nativeScan(JNIEnv *env, jobject obj, jlong nativeContext,
|
||||
convert::ThrowIfFailed(env, halTuner->scan(dir, skipSubChannel));
|
||||
}
|
||||
|
||||
static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext,
|
||||
jint channel, jint subChannel) {
|
||||
ALOGV("nativeTune(%d, %d)", channel, subChannel);
|
||||
auto halTuner = getHalTuner(nativeContext);
|
||||
if (halTuner == nullptr) return;
|
||||
static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext, jobject jSelector) {
|
||||
ALOGV("nativeTune()");
|
||||
AutoMutex _l(gContextMutex);
|
||||
auto& ctx = getNativeContext(nativeContext);
|
||||
auto halTuner10 = getHalTuner(ctx);
|
||||
auto halTuner11 = ctx.mHalTuner11;
|
||||
if (halTuner10 == nullptr) return;
|
||||
|
||||
convert::ThrowIfFailed(env, halTuner->tune(channel, subChannel));
|
||||
auto selector = convert::ProgramSelectorToHal(env, jSelector);
|
||||
if (halTuner11 != nullptr) {
|
||||
convert::ThrowIfFailed(env, halTuner11->tune_1_1(selector));
|
||||
} else {
|
||||
uint32_t channel, subChannel;
|
||||
if (!V1_1::utils::getLegacyChannel(selector, &channel, &subChannel)) {
|
||||
jniThrowException(env, "java/lang/IllegalArgumentException",
|
||||
"Can't tune to non-AM/FM channel with HAL<1.1");
|
||||
return;
|
||||
}
|
||||
convert::ThrowIfFailed(env, halTuner10->tune(channel, subChannel));
|
||||
}
|
||||
}
|
||||
|
||||
static void nativeCancel(JNIEnv *env, jobject obj, jlong nativeContext) {
|
||||
@@ -282,8 +307,10 @@ static void nativeCancel(JNIEnv *env, jobject obj, jlong nativeContext) {
|
||||
|
||||
static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativeContext) {
|
||||
ALOGV("nativeGetProgramInformation()");
|
||||
auto halTuner10 = getHalTuner(nativeContext);
|
||||
auto halTuner11 = getHalTuner11(nativeContext);
|
||||
AutoMutex _l(gContextMutex);
|
||||
auto& ctx = getNativeContext(nativeContext);
|
||||
auto halTuner10 = getHalTuner(ctx);
|
||||
auto halTuner11 = ctx.mHalTuner11;
|
||||
if (halTuner10 == nullptr) return nullptr;
|
||||
|
||||
JavaRef<jobject> jInfo;
|
||||
@@ -301,7 +328,7 @@ static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativ
|
||||
const V1_0::ProgramInfo& info) {
|
||||
halResult = result;
|
||||
if (result != Result::OK) return;
|
||||
jInfo = convert::ProgramInfoFromHal(env, info);
|
||||
jInfo = convert::ProgramInfoFromHal(env, info, ctx.mBand);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -402,7 +429,7 @@ static bool nativeIsAntennaConnected(JNIEnv *env, jobject obj, jlong nativeConte
|
||||
}
|
||||
|
||||
static const JNINativeMethod gTunerMethods[] = {
|
||||
{ "nativeInit", "(IZ)J", (void*)nativeInit },
|
||||
{ "nativeInit", "(IZI)J", (void*)nativeInit },
|
||||
{ "nativeFinalize", "(J)V", (void*)nativeFinalize },
|
||||
{ "nativeClose", "(J)V", (void*)nativeClose },
|
||||
{ "nativeSetConfiguration", "(JLandroid/hardware/radio/RadioManager$BandConfig;)V",
|
||||
@@ -411,7 +438,7 @@ static const JNINativeMethod gTunerMethods[] = {
|
||||
(void*)nativeGetConfiguration },
|
||||
{ "nativeStep", "(JZZ)V", (void*)nativeStep },
|
||||
{ "nativeScan", "(JZZ)V", (void*)nativeScan },
|
||||
{ "nativeTune", "(JII)V", (void*)nativeTune },
|
||||
{ "nativeTune", "(JLandroid/hardware/radio/ProgramSelector;)V", (void*)nativeTune },
|
||||
{ "nativeCancel", "(J)V", (void*)nativeCancel },
|
||||
{ "nativeGetProgramInformation", "(J)Landroid/hardware/radio/RadioManager$ProgramInfo;",
|
||||
(void*)nativeGetProgramInformation },
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
|
||||
#include "com_android_server_radio_convert.h"
|
||||
|
||||
#include <JNIHelp.h>
|
||||
#include <Utils.h>
|
||||
#include <core_jni_helpers.h>
|
||||
#include <utils/Log.h>
|
||||
#include <JNIHelp.h>
|
||||
|
||||
namespace android {
|
||||
namespace server {
|
||||
@@ -37,7 +38,9 @@ using V1_0::Direction;
|
||||
using V1_0::MetadataType;
|
||||
using V1_0::Result;
|
||||
using V1_0::Rds;
|
||||
using V1_1::ProgramIdentifier;
|
||||
using V1_1::ProgramListResult;
|
||||
using V1_1::ProgramSelector;
|
||||
|
||||
static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
|
||||
|
||||
@@ -87,6 +90,22 @@ static struct {
|
||||
jmethodID cstor;
|
||||
} ProgramInfo;
|
||||
|
||||
struct {
|
||||
jclass clazz;
|
||||
jmethodID cstor;
|
||||
jfieldID programType;
|
||||
jfieldID primaryId;
|
||||
jfieldID secondaryIds;
|
||||
jfieldID vendorIds;
|
||||
|
||||
struct {
|
||||
jclass clazz;
|
||||
jmethodID cstor;
|
||||
jfieldID type;
|
||||
jfieldID value;
|
||||
} Identifier;
|
||||
} ProgramSelector;
|
||||
|
||||
struct {
|
||||
jclass clazz;
|
||||
jmethodID cstor;
|
||||
@@ -383,25 +402,100 @@ JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hidl_vec<V1_0::MetaData> &me
|
||||
return jMetadata;
|
||||
}
|
||||
|
||||
static JavaRef<jobject> ProgramIdentifierFromHal(JNIEnv *env, const ProgramIdentifier &id) {
|
||||
ALOGV("ProgramIdentifierFromHal()");
|
||||
return make_javaref(env, env->NewObject(gjni.ProgramSelector.Identifier.clazz,
|
||||
gjni.ProgramSelector.Identifier.cstor, id.type, id.value));
|
||||
}
|
||||
|
||||
static JavaRef<jobject> ProgramSelectorFromHal(JNIEnv *env, const ProgramSelector &selector) {
|
||||
ALOGV("ProgramSelectorFromHal()");
|
||||
auto jPrimary = ProgramIdentifierFromHal(env, selector.primaryId);
|
||||
|
||||
auto jSecondary = make_javaref(env, env->NewObjectArray(selector.secondaryIds.size(),
|
||||
gjni.ProgramSelector.Identifier.clazz, nullptr));
|
||||
for (size_t i = 0; i < selector.secondaryIds.size(); i++) {
|
||||
auto jId = ProgramIdentifierFromHal(env, selector.secondaryIds[i]);
|
||||
env->SetObjectArrayElement(jSecondary.get(), i, jId.get());
|
||||
}
|
||||
|
||||
auto jVendor = make_javaref(env, env->NewLongArray(selector.vendorIds.size()));
|
||||
auto jVendorElements = env->GetLongArrayElements(jVendor.get(), nullptr);
|
||||
for (size_t i = 0; i < selector.vendorIds.size(); i++) {
|
||||
jVendorElements[i] = selector.vendorIds[i];
|
||||
}
|
||||
env->ReleaseLongArrayElements(jVendor.get(), jVendorElements, 0);
|
||||
|
||||
return make_javaref(env, env->NewObject(gjni.ProgramSelector.clazz, gjni.ProgramSelector.cstor,
|
||||
selector.programType, jPrimary.get(), jSecondary.get(), jVendor.get()));
|
||||
}
|
||||
|
||||
static ProgramIdentifier ProgramIdentifierToHal(JNIEnv *env, jobject jId) {
|
||||
ALOGV("ProgramIdentifierToHal()");
|
||||
|
||||
ProgramIdentifier id = {};
|
||||
id.type = env->GetIntField(jId, gjni.ProgramSelector.Identifier.type);
|
||||
id.value = env->GetLongField(jId, gjni.ProgramSelector.Identifier.value);
|
||||
return id;
|
||||
}
|
||||
|
||||
ProgramSelector ProgramSelectorToHal(JNIEnv *env, jobject jSelector) {
|
||||
ALOGV("ProgramSelectorToHal()");
|
||||
|
||||
ProgramSelector selector = {};
|
||||
|
||||
selector.programType = env->GetIntField(jSelector, gjni.ProgramSelector.programType);
|
||||
|
||||
auto jPrimary = env->GetObjectField(jSelector, gjni.ProgramSelector.primaryId);
|
||||
auto jSecondary = reinterpret_cast<jobjectArray>(
|
||||
env->GetObjectField(jSelector, gjni.ProgramSelector.secondaryIds));
|
||||
auto jVendor = reinterpret_cast<jlongArray>(
|
||||
env->GetObjectField(jSelector, gjni.ProgramSelector.vendorIds));
|
||||
|
||||
if (jPrimary == nullptr || jSecondary == nullptr || jVendor == nullptr) {
|
||||
ALOGE("ProgramSelector object is incomplete");
|
||||
return {};
|
||||
}
|
||||
|
||||
selector.primaryId = ProgramIdentifierToHal(env, jPrimary);
|
||||
auto count = env->GetArrayLength(jSecondary);
|
||||
selector.secondaryIds.resize(count);
|
||||
for (jsize i = 0; i < count; i++) {
|
||||
auto jId = env->GetObjectArrayElement(jSecondary, i);
|
||||
selector.secondaryIds[i] = ProgramIdentifierToHal(env, jId);
|
||||
}
|
||||
|
||||
count = env->GetArrayLength(jVendor);
|
||||
selector.vendorIds.resize(count);
|
||||
auto jVendorElements = env->GetLongArrayElements(jVendor, nullptr);
|
||||
for (jint i = 0; i < count; i++) {
|
||||
selector.vendorIds[i] = jVendorElements[i];
|
||||
}
|
||||
env->ReleaseLongArrayElements(jVendor, jVendorElements, 0);
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
static JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info10,
|
||||
const V1_1::ProgramInfo *info11) {
|
||||
const V1_1::ProgramInfo *info11, const ProgramSelector &selector) {
|
||||
ALOGV("ProgramInfoFromHal()");
|
||||
|
||||
auto jMetadata = MetadataFromHal(env, info10.metadata);
|
||||
auto jVendorExtension = info11 ? make_javastr(env, info11->vendorExension) : nullptr;
|
||||
auto jSelector = ProgramSelectorFromHal(env, selector);
|
||||
|
||||
return make_javaref(env, env->NewObject(gjni.ProgramInfo.clazz, gjni.ProgramInfo.cstor,
|
||||
info10.channel, info10.subChannel, info10.tuned, info10.stereo, info10.digital,
|
||||
info10.signalStrength, jMetadata.get(), info11 ? info11->flags : 0,
|
||||
jVendorExtension.get()));
|
||||
jSelector.get(), info10.tuned, info10.stereo, info10.digital, info10.signalStrength,
|
||||
jMetadata.get(), info11 ? info11->flags : 0, jVendorExtension.get()));
|
||||
}
|
||||
|
||||
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info) {
|
||||
return ProgramInfoFromHal(env, info, nullptr);
|
||||
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band) {
|
||||
auto selector = V1_1::utils::make_selector(band, info.channel, info.subChannel);
|
||||
return ProgramInfoFromHal(env, info, nullptr, selector);
|
||||
}
|
||||
|
||||
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info) {
|
||||
return ProgramInfoFromHal(env, info.base, &info);
|
||||
return ProgramInfoFromHal(env, info.base, &info, info.selector);
|
||||
}
|
||||
|
||||
} // namespace convert
|
||||
@@ -465,7 +559,31 @@ void register_android_server_radio_convert(JNIEnv *env) {
|
||||
auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo");
|
||||
gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass);
|
||||
gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
|
||||
"(IIZZZILandroid/hardware/radio/RadioMetadata;ILjava/lang/String;)V");
|
||||
"(Landroid/hardware/radio/ProgramSelector;ZZZILandroid/hardware/radio/RadioMetadata;I"
|
||||
"Ljava/lang/String;)V");
|
||||
|
||||
auto programSelectorClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector");
|
||||
gjni.ProgramSelector.clazz = MakeGlobalRefOrDie(env, programSelectorClass);
|
||||
gjni.ProgramSelector.cstor = GetMethodIDOrDie(env, programSelectorClass, "<init>",
|
||||
"(ILandroid/hardware/radio/ProgramSelector$Identifier;"
|
||||
"[Landroid/hardware/radio/ProgramSelector$Identifier;[J)V");
|
||||
gjni.ProgramSelector.programType = GetFieldIDOrDie(env, programSelectorClass,
|
||||
"mProgramType", "I");
|
||||
gjni.ProgramSelector.primaryId = GetFieldIDOrDie(env, programSelectorClass,
|
||||
"mPrimaryId", "Landroid/hardware/radio/ProgramSelector$Identifier;");
|
||||
gjni.ProgramSelector.secondaryIds = GetFieldIDOrDie(env, programSelectorClass,
|
||||
"mSecondaryIds", "[Landroid/hardware/radio/ProgramSelector$Identifier;");
|
||||
gjni.ProgramSelector.vendorIds = GetFieldIDOrDie(env, programSelectorClass,
|
||||
"mVendorIds", "[J");
|
||||
|
||||
auto progSelIdClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector$Identifier");
|
||||
gjni.ProgramSelector.Identifier.clazz = MakeGlobalRefOrDie(env, progSelIdClass);
|
||||
gjni.ProgramSelector.Identifier.cstor = GetMethodIDOrDie(env, progSelIdClass,
|
||||
"<init>", "(IJ)V");
|
||||
gjni.ProgramSelector.Identifier.type = GetFieldIDOrDie(env, progSelIdClass,
|
||||
"mType", "I");
|
||||
gjni.ProgramSelector.Identifier.value = GetFieldIDOrDie(env, progSelIdClass,
|
||||
"mValue", "J");
|
||||
|
||||
auto radioMetadataClass = FindClassOrDie(env, "android/hardware/radio/RadioMetadata");
|
||||
gjni.RadioMetadata.clazz = MakeGlobalRefOrDie(env, radioMetadataClass);
|
||||
|
||||
@@ -45,9 +45,10 @@ V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region ®ion);
|
||||
V1_0::Direction DirectionToHal(bool directionDown);
|
||||
|
||||
JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hardware::hidl_vec<V1_0::MetaData> &metadata);
|
||||
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info);
|
||||
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band);
|
||||
JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info);
|
||||
|
||||
V1_1::ProgramSelector ProgramSelectorToHal(JNIEnv *env, jobject jSelector);
|
||||
|
||||
void ThrowParcelableRuntimeException(JNIEnv *env, const std::string& msg);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.hardware.radio.tests;
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.radio.ProgramSelector;
|
||||
import android.hardware.radio.RadioManager;
|
||||
import android.hardware.radio.RadioTuner;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
@@ -64,6 +65,7 @@ public class RadioTest {
|
||||
private final int kConfigCallbackTimeoutMs = 10000;
|
||||
private final int kCancelTimeoutMs = 1000;
|
||||
private final int kTuneCallbackTimeoutMs = 30000;
|
||||
private final int kFullScanTimeoutMs = 60000;
|
||||
|
||||
private RadioManager mRadioManager;
|
||||
private RadioTuner mRadioTuner;
|
||||
@@ -144,11 +146,9 @@ public class RadioTest {
|
||||
assertNotNull(mRadioTuner);
|
||||
verify(mCallback, timeout(kConfigCallbackTimeoutMs)).onConfigurationChanged(any());
|
||||
resetCallback();
|
||||
}
|
||||
|
||||
private void checkAntenna() {
|
||||
boolean isConnected = mRadioTuner.isAntennaConnected();
|
||||
assertTrue(isConnected);
|
||||
boolean isAntennaConnected = mRadioTuner.isAntennaConnected();
|
||||
assertTrue(isAntennaConnected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -256,7 +256,6 @@ public class RadioTest {
|
||||
@Test
|
||||
public void testStep() {
|
||||
openTuner();
|
||||
checkAntenna();
|
||||
|
||||
int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
|
||||
assertEquals(RadioManager.STATUS_OK, ret);
|
||||
@@ -272,7 +271,6 @@ public class RadioTest {
|
||||
@Test
|
||||
public void testTuneAndGetPI() {
|
||||
openTuner();
|
||||
checkAntenna();
|
||||
|
||||
int channel = mFmBandConfig.getLowerLimit() + mFmBandConfig.getSpacing();
|
||||
|
||||
@@ -304,7 +302,6 @@ public class RadioTest {
|
||||
@Test
|
||||
public void testLateCancel() {
|
||||
openTuner();
|
||||
checkAntenna();
|
||||
|
||||
int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, false);
|
||||
assertEquals(RadioManager.STATUS_OK, ret);
|
||||
@@ -317,7 +314,6 @@ public class RadioTest {
|
||||
@Test
|
||||
public void testScanAndCancel() {
|
||||
openTuner();
|
||||
checkAntenna();
|
||||
|
||||
/* There is a possible race condition between scan and cancel commands - the scan may finish
|
||||
* before cancel command is issued. Thus we accept both outcomes in this test.
|
||||
@@ -335,7 +331,6 @@ public class RadioTest {
|
||||
@Test
|
||||
public void testStartBackgroundScan() {
|
||||
openTuner();
|
||||
checkAntenna();
|
||||
|
||||
boolean ret = mRadioTuner.startBackgroundScan();
|
||||
boolean isSupported = mModule.isBackgroundScanningSupported();
|
||||
@@ -345,7 +340,6 @@ public class RadioTest {
|
||||
@Test
|
||||
public void testGetProgramList() {
|
||||
openTuner();
|
||||
checkAntenna();
|
||||
|
||||
try {
|
||||
List<RadioManager.ProgramInfo> list = mRadioTuner.getProgramList(null);
|
||||
@@ -356,6 +350,39 @@ public class RadioTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTuneFromProgramList() {
|
||||
openTuner();
|
||||
|
||||
List<RadioManager.ProgramInfo> list;
|
||||
|
||||
try {
|
||||
list = mRadioTuner.getProgramList(null);
|
||||
assertNotNull(list);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.i(TAG, "Background list is not ready, trying to fix it");
|
||||
|
||||
boolean success = mRadioTuner.startBackgroundScan();
|
||||
assertTrue(success);
|
||||
verify(mCallback, timeout(kFullScanTimeoutMs)).onBackgroundScanComplete();
|
||||
|
||||
list = mRadioTuner.getProgramList(null);
|
||||
assertNotNull(list);
|
||||
}
|
||||
|
||||
if (list.isEmpty()) {
|
||||
Log.i(TAG, "Program list is empty, can't test tune");
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramSelector sel = list.get(0).getSelector();
|
||||
mRadioTuner.tune(sel);
|
||||
ArgumentCaptor<RadioManager.ProgramInfo> infoc =
|
||||
ArgumentCaptor.forClass(RadioManager.ProgramInfo.class);
|
||||
verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(infoc.capture());
|
||||
assertEquals(sel, infoc.getValue().getSelector());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForcedAnalog() {
|
||||
openTuner();
|
||||
|
||||
Reference in New Issue
Block a user