Merge "Deprecate onMetadataChanged."
diff --git a/api/system-current.txt b/api/system-current.txt
index d176e39..6979527 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -17490,7 +17490,7 @@
     method public void onControlChanged(boolean);
     method public void onEmergencyAnnouncement(boolean);
     method public void onError(int);
-    method public void onMetadataChanged(android.hardware.radio.RadioMetadata);
+    method public deprecated void onMetadataChanged(android.hardware.radio.RadioMetadata);
     method public void onProgramInfoChanged(android.hardware.radio.RadioManager.ProgramInfo);
     method public void onProgramListChanged();
     method public void onTrafficAnnouncement(boolean);
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index aed114e..c3bbaec 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -23,8 +23,7 @@
 oneway interface ITunerCallback {
     void onError(int status);
     void onConfigurationChanged(in RadioManager.BandConfig config);
-    void onProgramInfoChanged(in RadioManager.ProgramInfo info);
-    void onMetadataChanged(in RadioMetadata metadata);
+    void onProgramInfoChanged();
     void onTrafficAnnouncement(boolean active);
     void onEmergencyAnnouncement(boolean active);
     void onAntennaState(boolean connected);
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8ef5705..1de80b17 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -1560,7 +1560,7 @@
         Log.d(TAG, "Opening tuner " + moduleId + "...");
 
         ITuner tuner;
-        ITunerCallback halCallback = new TunerCallbackAdapter(callback, handler);
+        TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
         try {
             tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
         } catch (RemoteException e) {
@@ -1571,6 +1571,7 @@
             Log.e(TAG, "Failed to open tuner");
             return null;
         }
+        halCallback.attachTuner(tuner);
         return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID);
     }
 
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index f659273..8e0b0f6 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -330,20 +330,24 @@
          * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)}
          */
         public void onConfigurationChanged(RadioManager.BandConfig config) {}
+
         /**
-         * onProgramInfoChanged() is called upon successful completion of
-         * {@link RadioTuner#step(int, boolean)}, {@link RadioTuner#scan(int, boolean)},
-         * {@link RadioTuner#tune(int, int)} or when a switching to alternate frequency occurs.
-         * Note that if metadata only are updated,  {@link #onMetadataChanged(RadioMetadata)} will
-         * be called.
+         * 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) {}
+
         /**
-         * onMetadataChanged() is called when new meta data are received on current program.
-         * Meta data are also received in {@link RadioManager.ProgramInfo} when
-         *  {@link #onProgramInfoChanged(RadioManager.ProgramInfo)} is called.
+         * 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.
          */
@@ -386,7 +390,7 @@
         /**
          * Called when available program list changed.
          *
-         * Use getProgramList() to get the actual list.
+         * Use {@link RadioTuner#getProgramList(String)} to get an actual list.
          */
         public void onProgramListChanged() {}
     }
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index 155ffa7..8c3826c 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -20,13 +20,21 @@
 import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
 
 /**
  * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
  */
 class TunerCallbackAdapter extends ITunerCallback.Stub {
+    private static final String TAG = "radio.TunerCallbackAdapter";
+
     @NonNull private final RadioTuner.Callback mCallback;
     @NonNull private final Handler mHandler;
+    private final Object mLock = new Object();
+
+    @Nullable private ITuner mTuner;
+    boolean mPendingProgramInfoChanged = false;
 
     TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
         mCallback = callback;
@@ -37,6 +45,14 @@
         }
     }
 
+    public void attachTuner(@NonNull ITuner tuner) {
+        synchronized (mLock) {
+            if (mTuner != null) throw new IllegalStateException();
+            mTuner = tuner;
+            if (mPendingProgramInfoChanged) onProgramInfoChanged();
+        }
+    }
+
     @Override
     public void onError(int status) {
         mHandler.post(() -> mCallback.onError(status));
@@ -48,13 +64,28 @@
     }
 
     @Override
-    public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
-        mHandler.post(() -> mCallback.onProgramInfoChanged(info));
-    }
+    public void onProgramInfoChanged() {
+        synchronized (mLock) {
+            if (mTuner == null) {
+                mPendingProgramInfoChanged = true;
+                return;
+            }
+        }
 
-    @Override
-    public void onMetadataChanged(RadioMetadata metadata) {
-        mHandler.post(() -> mCallback.onMetadataChanged(metadata));
+        RadioManager.ProgramInfo info;
+        try {
+            info = mTuner.getProgramInformation();
+        } catch (RemoteException e) {
+            Log.e(TAG, "service died", e);
+            return;
+        }
+
+        mHandler.post(() -> {
+            mCallback.onProgramInfoChanged(info);
+
+            RadioMetadata metadata = info.getMetadata();
+            if (metadata != null) mCallback.onMetadataChanged(metadata);
+        });
     }
 
     @Override
diff --git a/services/core/java/com/android/server/radio/TunerCallback.java b/services/core/java/com/android/server/radio/TunerCallback.java
index 9430dc9..62110cf 100644
--- a/services/core/java/com/android/server/radio/TunerCallback.java
+++ b/services/core/java/com/android/server/radio/TunerCallback.java
@@ -86,13 +86,8 @@
     }
 
     @Override
-    public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
-        dispatch(() -> mClientCallback.onProgramInfoChanged(info));
-    }
-
-    @Override
-    public void onMetadataChanged(RadioMetadata metadata) {
-        dispatch(() -> mClientCallback.onMetadataChanged(metadata));
+    public void onProgramInfoChanged() {
+        dispatch(() -> mClientCallback.onProgramInfoChanged());
     }
 
     @Override
diff --git a/services/core/jni/com_android_server_radio_TunerCallback.cpp b/services/core/jni/com_android_server_radio_TunerCallback.cpp
index 8df92ae7..815a212 100644
--- a/services/core/jni/com_android_server_radio_TunerCallback.cpp
+++ b/services/core/jni/com_android_server_radio_TunerCallback.cpp
@@ -22,9 +22,10 @@
 #include "com_android_server_radio_convert.h"
 #include "com_android_server_radio_Tuner.h"
 
+#include <JNIHelp.h>
+#include <Utils.h>
 #include <core_jni_helpers.h>
 #include <utils/Log.h>
-#include <JNIHelp.h>
 
 namespace android {
 namespace server {
@@ -37,11 +38,13 @@
 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;
 using V1_1::ITunerCallback;
 using V1_1::ProgramListResult;
+using V1_1::ProgramSelector;
 
 static JavaVM *gvm = nullptr;
 
@@ -53,7 +56,6 @@
         jmethodID onError;
         jmethodID onConfigurationChanged;
         jmethodID onProgramInfoChanged;
-        jmethodID onMetadataChanged;
         jmethodID onTrafficAnnouncement;
         jmethodID onEmergencyAnnouncement;
         jmethodID onAntennaState;
@@ -82,6 +84,8 @@
     NativeCallbackThread mCallbackThread;
     HalRevision mHalRev;
 
+    Band mBand;
+
     DISALLOW_COPY_AND_ASSIGN(NativeCallback);
 
 public:
@@ -99,11 +103,12 @@
     virtual Return<void> emergencyAnnouncement(bool active);
     virtual Return<void> newMetadata(uint32_t channel, uint32_t subChannel,
             const hidl_vec<MetaData>& metadata);
-    virtual Return<void> tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info);
-    virtual Return<void> afSwitch_1_1(const V1_1::ProgramInfo& info);
+    virtual Return<void> tuneComplete_1_1(Result result, const ProgramSelector& selector);
+    virtual Return<void> afSwitch_1_1(const ProgramSelector& selector);
     virtual Return<void> backgroundScanAvailable(bool isAvailable);
     virtual Return<void> backgroundScanComplete(ProgramListResult result);
     virtual Return<void> programListChanged();
+    virtual Return<void> programInfoChanged();
 };
 
 struct TunerCallbackContext {
@@ -171,24 +176,20 @@
     ALOGV("tuneComplete(%d)", result);
 
     if (mHalRev > HalRevision::V1_0) {
-        ALOGD("1.0 callback was ignored");
+        ALOGW("1.0 callback was ignored");
         return Return<void>();
     }
 
-    V1_1::ProgramInfo info_1_1 {
-        .base = info,
-    };
-    return tuneComplete_1_1(result, info_1_1);
+    auto selector = V1_1::utils::make_selector(mBand, info.channel, info.subChannel);
+    return tuneComplete_1_1(result, selector);
 }
 
-Return<void> NativeCallback::tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) {
+Return<void> NativeCallback::tuneComplete_1_1(Result result, const ProgramSelector& selector) {
     ALOGV("tuneComplete_1_1(%d)", result);
 
-    mCallbackThread.enqueue([result, info, this](JNIEnv *env) {
+    mCallbackThread.enqueue([result, this](JNIEnv *env) {
         if (result == Result::OK) {
-            auto jInfo = convert::ProgramInfoFromHal(env, info);
-            if (jInfo == nullptr) return;
-            env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged, jInfo.get());
+            env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
         } else {
             TunerError cause = TunerError::CANCELLED;
             if (result == Result::TIMEOUT) cause = TunerError::SCAN_TIMEOUT;
@@ -204,9 +205,9 @@
     return tuneComplete(Result::OK, info);
 }
 
-Return<void> NativeCallback::afSwitch_1_1(const V1_1::ProgramInfo& info) {
+Return<void> NativeCallback::afSwitch_1_1(const ProgramSelector& selector) {
     ALOGV("afSwitch_1_1()");
-    return tuneComplete_1_1(Result::OK, info);
+    return tuneComplete_1_1(Result::OK, selector);
 }
 
 Return<void> NativeCallback::antennaStateChange(bool connected) {
@@ -244,10 +245,13 @@
     // channel and subChannel are not used
     ALOGV("newMetadata(%d, %d)", channel, subChannel);
 
+    if (mHalRev > HalRevision::V1_0) {
+        ALOGW("1.0 callback was ignored");
+        return Return<void>();
+    }
+
     mCallbackThread.enqueue([this, metadata](JNIEnv *env) {
-        auto jMetadata = convert::MetadataFromHal(env, metadata);
-        if (jMetadata == nullptr) return;
-        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onMetadataChanged, jMetadata.get());
+        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
     });
 
     return Return<void>();
@@ -290,6 +294,16 @@
     return Return<void>();
 }
 
+Return<void> NativeCallback::programInfoChanged() {
+    ALOGV("programInfoChanged()");
+
+    mCallbackThread.enqueue([this](JNIEnv *env) {
+        env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+    });
+
+    return Return<void>();
+}
+
 static TunerCallbackContext& getNativeContext(jlong nativeContextHandle) {
     auto nativeContext = reinterpret_cast<TunerCallbackContext*>(nativeContextHandle);
     LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
@@ -363,9 +377,7 @@
     gjni.TunerCallback.onConfigurationChanged = GetMethodIDOrDie(env, tunerCbClass,
             "onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V");
     gjni.TunerCallback.onProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass,
-            "onProgramInfoChanged", "(Landroid/hardware/radio/RadioManager$ProgramInfo;)V");
-    gjni.TunerCallback.onMetadataChanged = GetMethodIDOrDie(env, tunerCbClass,
-            "onMetadataChanged", "(Landroid/hardware/radio/RadioMetadata;)V");
+            "onProgramInfoChanged", "()V");
     gjni.TunerCallback.onTrafficAnnouncement = GetMethodIDOrDie(env, tunerCbClass,
             "onTrafficAnnouncement", "(Z)V");
     gjni.TunerCallback.onEmergencyAnnouncement = GetMethodIDOrDie(env, tunerCbClass,
diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
index 0c6d0e2..aa5780a 100644
--- a/tests/radio/src/android/hardware/radio/tests/RadioTest.java
+++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
@@ -110,7 +111,7 @@
             mRadioTuner.close();
             mRadioTuner = null;
         }
-        verifyNoMoreInteractions(mCallback);
+        resetCallback();
     }
 
     private void openTuner() {
@@ -118,6 +119,7 @@
     }
 
     private void resetCallback() {
+        verify(mCallback, atLeast(0)).onMetadataChanged(any());
         verifyNoMoreInteractions(mCallback);
         Mockito.reset(mCallback);
     }