Merge "Implement ProgramSelector for font-end Java APIs."

This commit is contained in:
Tomasz Wasilczyk
2017-07-18 15:36:22 +00:00
committed by Android (Google) Code Review
15 changed files with 900 additions and 73 deletions

View File

@@ -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 \

View File

@@ -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

View File

@@ -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

View 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;

View 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];
}
};
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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 \

View File

@@ -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);

View File

@@ -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 },

View File

@@ -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);

View File

@@ -45,9 +45,10 @@ V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region &region);
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);

View File

@@ -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();