/*
 * Copyright (C) 2015 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.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.Bitmap;
import android.os.Handler;

import java.util.List;
import java.util.Map;

/**
 * RadioTuner interface provides methods to control a radio tuner on the device: selecting and
 * configuring the active band, muting/unmuting, scanning and tuning, etc...
 *
 * Obtain a RadioTuner interface by calling {@link RadioManager#openTuner(int,
 * RadioManager.BandConfig, boolean, RadioTuner.Callback, Handler)}.
 * @hide
 */
@SystemApi
public abstract class RadioTuner {

    /** Scanning direction UP for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
    public static final int DIRECTION_UP      = 0;

    /** Scanning directions DOWN for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
    public static final int DIRECTION_DOWN    = 1;

    /**
     * Close the tuner interface. The {@link Callback} callback will not be called
     * anymore and associated resources will be released.
     * Must be called when the tuner is not needed to make hardware resources available to others.
     * */
    public abstract void close();

    /**
     * Set the active band configuration for this module.
     * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
     * in the ModuleProperties of the module with the specified ID.
     * @param config The desired band configuration (FmBandConfig or AmBandConfig).
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int setConfiguration(RadioManager.BandConfig config);

    /**
     * Get current configuration.
     * @param config a BandConfig array of lengh 1 where the configuration is returned.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int getConfiguration(RadioManager.BandConfig[] config);


    /**
     * Set mute state. When muted, the radio tuner audio source is not available for playback on
     * any audio device. when unmuted, the radio tuner audio source is output as a media source
     * and renderd over the audio device selected for media use case.
     * The radio tuner audio source is muted by default when the tuner is first attached.
     * Only effective if the tuner is attached with audio enabled.
     *
     * @param mute the requested mute state.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int setMute(boolean mute);

    /**
     * Get mute state.
     *
     * @return {@code true} if the radio tuner audio source is muted or a problem occured
     * retrieving the mute state, {@code false} otherwise.
     */
    public abstract boolean getMute();

    /**
     * Step up or down by one channel spacing.
     * The operation is asynchronous and {@link Callback}
     * onProgramInfoChanged() will be called when step completes or
     * onError() when cancelled or timeout.
     * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
     * @param skipSubChannel indicates to skip sub channels when the configuration currently
     * selected supports sub channel (e.g HD Radio). N/A otherwise.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int step(int direction, boolean skipSubChannel);

    /**
     * Scan up or down to next valid station.
     * The operation is asynchronous and {@link Callback}
     * onProgramInfoChanged() will be called when scan completes or
     * onError() when cancelled or timeout.
     * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
     * @param skipSubChannel indicates to skip sub channels when the configuration currently
     * selected supports sub channel (e.g HD Radio). N/A otherwise.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int scan(int direction, boolean skipSubChannel);

    /**
     * Tune to a specific frequency.
     * The operation is asynchronous and {@link Callback}
     * onProgramInfoChanged() will be called when tune completes or
     * onError() when cancelled or timeout.
     * @param channel the specific channel or frequency to tune to.
     * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
     * does not support cub channels.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <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.
     *
     * @throws 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
     * {@link #ERROR_CANCELLED}.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int cancel();

    /**
     * Cancels traffic or emergency announcement.
     *
     * If there was no announcement to cancel, no action is taken.
     *
     * There is a race condition between calling cancelAnnouncement and the actual announcement
     * being finished, so onTrafficAnnouncement / onEmergencyAnnouncement callback should be
     * tracked with proper locking.
     */
    public abstract void cancelAnnouncement();

    /**
     * Get current station information.
     * @param info a ProgramInfo array of lengh 1 where the information is returned.
     * @return
     * <ul>
     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
     *  service fails, </li>
     * </ul>
     */
    public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);

    /**
     * Retrieves a {@link Bitmap} for the given image ID or null,
     * if the image was missing from the tuner.
     *
     * This involves doing a call to the tuner, so the bitmap should be cached
     * on the application side.
     *
     * If the method returns null for non-zero ID, it means the image was
     * updated on the tuner side. There is a race conditon between fetching
     * image for an old ID and tuner updating the image (and cleaning up the
     * old image). In such case, a new ProgramInfo with updated image id will
     * be sent with a {@link onProgramInfoChanged} callback.
     *
     * @param id The image identifier, retrieved with
     *           {@link RadioMetadata#getBitmapId(String)}.
     * @return A {@link Bitmap} or null.
     * @throws IllegalArgumentException if id==0
     * @hide This API is not thoroughly elaborated yet
     */
    public abstract @Nullable Bitmap getMetadataImage(int id);

    /**
     * Initiates a background scan to update internally cached program list.
     *
     * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
     *
     * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
     * be called if the return value of this call was {@code true}. As result of this call
     * programListChanged may be triggered (if the scanned list differs).
     *
     * @return {@code true} if the scan was properly scheduled, {@code false} if the scan feature
     * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
     * or permanently if the feature is not supported
     * (see ModuleProperties#isBackgroundScanningSupported()).
     */
    public abstract boolean startBackgroundScan();

    /**
     * Get the list of discovered radio stations.
     *
     * To get the full list, set filter to null or empty map.
     * Keys must be prefixed with unique vendor Java-style namespace,
     * eg. 'com.somecompany.parameter1'.
     *
     * @param vendorFilter vendor-specific selector for radio stations.
     * @return a list of radio stations.
     * @throws IllegalStateException if the scan is in progress or has not been started,
     *         startBackgroundScan() call may fix it.
     * @throws IllegalArgumentException if the vendorFilter argument is not valid.
     */
    public abstract @NonNull List<RadioManager.ProgramInfo>
            getProgramList(@Nullable Map<String, String> vendorFilter);

    /**
     * Checks, if the analog playback is forced, see setAnalogForced.
     *
     * @throws IllegalStateException if the switch is not supported at current
     *         configuration.
     * @return {@code true} if analog is forced, {@code false} otherwise.
     */
    public abstract boolean isAnalogForced();

    /**
     * Forces the analog playback for the supporting radio technology.
     *
     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
     * this option. This is purely user choice, ie. does not reflect digital-
     * analog handover managed from the HAL implementation side.
     *
     * Some radio technologies may not support this, ie. DAB.
     *
     * @param isForced {@code true} to force analog, {@code false} for a default behaviour.
     * @throws IllegalStateException if the switch is not supported at current
     *         configuration.
     */
    public abstract void setAnalogForced(boolean isForced);

    /**
     * Get current antenna connection state for current configuration.
     * Only valid if a configuration has been applied.
     * @return {@code true} if the antenna is connected, {@code false} otherwise.
     */
    public abstract boolean isAntennaConnected();

    /**
     * Indicates if this client actually controls the tuner.
     * Control is always granted after
     * {@link RadioManager#openTuner(int,
     * RadioManager.BandConfig, boolean, Callback, Handler)}
     * returns a non null tuner interface.
     * Control is lost when another client opens an interface on the same tuner.
     * When this happens, {@link Callback#onControlChanged(boolean)} is received.
     * The client can either wait for control to be returned (which is indicated by the same
     * callback) or close and reopen the tuner interface.
     * @return {@code true} if this interface controls the tuner,
     * {@code false} otherwise or if a problem occured retrieving the state.
     */
    public abstract boolean hasControl();

    /** Indicates a failure of radio IC or driver.
     * The application must close and re open the tuner */
    public static final int ERROR_HARDWARE_FAILURE = 0;
    /** Indicates a failure of the radio service.
     * The application must close and re open the tuner */
    public static final  int ERROR_SERVER_DIED = 1;
    /** A pending seek or tune operation was cancelled */
    public static final  int ERROR_CANCELLED = 2;
    /** A pending seek or tune operation timed out */
    public static final  int ERROR_SCAN_TIMEOUT = 3;
    /** The requested configuration could not be applied */
    public static final  int ERROR_CONFIG = 4;
    /** Background scan was interrupted due to hardware becoming temporarily unavailable. */
    public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5;
    /** Background scan failed due to other error, ie. HW failure. */
    public static final int ERROR_BACKGROUND_SCAN_FAILED = 6;

    /**
     * Callback provided by the client application when opening a {@link RadioTuner}
     * to receive asynchronous operation results, updates and error notifications.
     */
    public static abstract class Callback {
        /**
         * onError() is called when an error occured while performing an asynchronous
         * operation of when the hardware or system service experiences a problem.
         * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED},
         * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
         * {@link #ERROR_CONFIG}
         */
        public void onError(int status) {}
        /**
         * onConfigurationChanged() is called upon successful completion of
         * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}
         * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)}
         */
        public void onConfigurationChanged(RadioManager.BandConfig config) {}

        /**
         * Called when program info (including metadata) for the current program has changed.
         *
         * It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
         * {@link RadioTuner#scan(int, boolean)}, {@link RadioTuner#tune(int, int)}; when
         * a switching to alternate frequency occurs; or when metadata is updated.
         */
        public void onProgramInfoChanged(RadioManager.ProgramInfo info) {}

        /**
         * Called when metadata is updated for the current program.
         *
         * @deprecated Use {@link #onProgramInfoChanged(RadioManager.ProgramInfo)} instead.
         */
        @Deprecated
        public void onMetadataChanged(RadioMetadata metadata) {}

        /**
         * onTrafficAnnouncement() is called when a traffic announcement starts and stops.
         */
        public void onTrafficAnnouncement(boolean active) {}
        /**
         * onEmergencyAnnouncement() is called when an emergency annoucement starts and stops.
         */
        public void onEmergencyAnnouncement(boolean active) {}
        /**
         * onAntennaState() is called when the antenna is connected or disconnected.
         */
        public void onAntennaState(boolean connected) {}
        /**
         * onControlChanged() is called when the client loses or gains control of the radio tuner.
         * The control is always granted after a successful call to
         * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}.
         * If another client opens the same tuner, onControlChanged() will be called with
         * control set to {@code false} to indicate loss of control.
         * At this point, RadioTuner APIs other than getters will return
         * {@link RadioManager#STATUS_INVALID_OPERATION}.
         * When the other client releases the tuner, onControlChanged() will be called
         * with control set to {@code true}.
         */
        public void onControlChanged(boolean control) {}

        /**
         * onBackgroundScanAvailabilityChange() is called when background scan
         * feature becomes available or not.
         *
         * @param isAvailable true, if the tuner turned temporarily background-
         *                    capable, false in the other case.
         */
        public void onBackgroundScanAvailabilityChange(boolean isAvailable) {}

        /**
         * Called when a background scan completes successfully.
         */
        public void onBackgroundScanComplete() {}

        /**
         * Called when available program list changed.
         *
         * Use {@link RadioTuner#getProgramList(String)} to get an actual list.
         */
        public void onProgramListChanged() {}
    }

}

