blob: f2e44ee132ed82a5e0336c6f227edf73fe03e7a2 [file] [log] [blame]
/**
* 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 com.android.server.broadcastradio.hal1;
import android.annotation.NonNull;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
class Tuner extends ITuner.Stub {
private static final String TAG = "BroadcastRadioService.Tuner";
/**
* This field is used by native code, do not access or modify.
*/
private final long mNativeContext;
private final Object mLock = new Object();
@NonNull private final TunerCallback mTunerCallback;
@NonNull private final ITunerCallback mClientCallback;
@NonNull private final IBinder.DeathRecipient mDeathRecipient;
private boolean mIsClosed = false;
private boolean mIsMuted = false;
private int mRegion;
private final boolean mWithAudio;
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, band);
mDeathRecipient = this::close;
try {
mClientCallback.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException ex) {
close();
}
}
@Override
protected void finalize() throws Throwable {
nativeFinalize(mNativeContext);
super.finalize();
}
private native long nativeInit(int halRev, boolean withAudio, int band);
private native void nativeFinalize(long nativeContext);
private native void nativeClose(long nativeContext);
private native void nativeSetConfiguration(long nativeContext,
@NonNull RadioManager.BandConfig config);
private native RadioManager.BandConfig nativeGetConfiguration(long nativeContext, int region);
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, @NonNull ProgramSelector selector);
private native void nativeCancel(long nativeContext);
private native void nativeCancelAnnouncement(long nativeContext);
private native boolean nativeStartBackgroundScan(long nativeContext);
private native List<RadioManager.ProgramInfo> nativeGetProgramList(long nativeContext,
Map<String, String> vendorFilter);
private native byte[] nativeGetImage(long nativeContext, int id);
private native boolean nativeIsAnalogForced(long nativeContext);
private native void nativeSetAnalogForced(long nativeContext, boolean isForced);
@Override
public void close() {
synchronized (mLock) {
if (mIsClosed) return;
mIsClosed = true;
mTunerCallback.detach();
mClientCallback.asBinder().unlinkToDeath(mDeathRecipient, 0);
nativeClose(mNativeContext);
}
}
@Override
public boolean isClosed() {
return mIsClosed;
}
private void checkNotClosedLocked() {
if (mIsClosed) {
throw new IllegalStateException("Tuner is closed, no further operations are allowed");
}
}
private boolean checkConfiguredLocked() {
if (mTunerCallback.isInitialConfigurationDone()) return true;
Slog.w(TAG, "Initial configuration is still pending, skipping the operation");
return false;
}
@Override
public void setConfiguration(RadioManager.BandConfig config) {
if (config == null) {
throw new IllegalArgumentException("The argument must not be a null pointer");
}
synchronized (mLock) {
checkNotClosedLocked();
nativeSetConfiguration(mNativeContext, config);
mRegion = config.getRegion();
}
}
@Override
public RadioManager.BandConfig getConfiguration() {
synchronized (mLock) {
checkNotClosedLocked();
return nativeGetConfiguration(mNativeContext, mRegion);
}
}
@Override
public void setMuted(boolean mute) {
if (!mWithAudio) {
throw new IllegalStateException("Can't operate on mute - no audio requested");
}
synchronized (mLock) {
checkNotClosedLocked();
if (mIsMuted == mute) return;
mIsMuted = mute;
Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app");
}
}
@Override
public boolean isMuted() {
if (!mWithAudio) {
Slog.w(TAG, "Tuner did not request audio, pretending it was muted");
return true;
}
synchronized (mLock) {
checkNotClosedLocked();
return mIsMuted;
}
}
@Override
public void step(boolean directionDown, boolean skipSubChannel) {
synchronized (mLock) {
checkNotClosedLocked();
if (!checkConfiguredLocked()) return;
nativeStep(mNativeContext, directionDown, skipSubChannel);
}
}
@Override
public void scan(boolean directionDown, boolean skipSubChannel) {
synchronized (mLock) {
checkNotClosedLocked();
if (!checkConfiguredLocked()) return;
nativeScan(mNativeContext, directionDown, skipSubChannel);
}
}
@Override
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();
if (!checkConfiguredLocked()) return;
nativeTune(mNativeContext, selector);
}
}
@Override
public void cancel() {
synchronized (mLock) {
checkNotClosedLocked();
nativeCancel(mNativeContext);
}
}
@Override
public void cancelAnnouncement() {
synchronized (mLock) {
checkNotClosedLocked();
nativeCancelAnnouncement(mNativeContext);
}
}
@Override
public Bitmap getImage(int id) {
if (id == 0) {
throw new IllegalArgumentException("Image ID is missing");
}
byte[] rawImage;
synchronized (mLock) {
rawImage = nativeGetImage(mNativeContext, id);
}
if (rawImage == null || rawImage.length == 0) {
return null;
}
return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
}
@Override
public boolean startBackgroundScan() {
synchronized (mLock) {
checkNotClosedLocked();
return nativeStartBackgroundScan(mNativeContext);
}
}
List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
Map<String, String> sFilter = vendorFilter;
synchronized (mLock) {
checkNotClosedLocked();
List<RadioManager.ProgramInfo> list = nativeGetProgramList(mNativeContext, sFilter);
if (list == null) {
throw new IllegalStateException("Program list is not ready");
}
return list;
}
}
@Override
public void startProgramListUpdates(ProgramList.Filter filter) {
mTunerCallback.startProgramListUpdates(filter);
}
@Override
public void stopProgramListUpdates() {
mTunerCallback.stopProgramListUpdates();
}
@Override
public boolean isConfigFlagSupported(int flag) {
return flag == RadioManager.CONFIG_FORCE_ANALOG;
}
@Override
public boolean isConfigFlagSet(int flag) {
if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
synchronized (mLock) {
checkNotClosedLocked();
return nativeIsAnalogForced(mNativeContext);
}
}
throw new UnsupportedOperationException("Not supported by HAL 1.x");
}
@Override
public void setConfigFlag(int flag, boolean value) {
if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
synchronized (mLock) {
checkNotClosedLocked();
nativeSetAnalogForced(mNativeContext, value);
return;
}
}
throw new UnsupportedOperationException("Not supported by HAL 1.x");
}
@Override
public Map setParameters(Map parameters) {
throw new UnsupportedOperationException("Not supported by HAL 1.x");
}
@Override
public Map getParameters(List<String> keys) {
throw new UnsupportedOperationException("Not supported by HAL 1.x");
}
}