Introduce LiveData for CDM Activity and Discovery

Test: atest CtsCompanionDeviceManagerCoreTestCases
      atest CtsCompanionDeviceManagerUiAutomationTestCases
      atest CtsOsTestCases:CompanionDeviceManagerTest

Bug: 211722613
Change-Id: Icb84fe42cf02b1e0db4546fefee70afdd97ee7f3
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index 4a52650..0e60873 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -36,5 +36,11 @@
     defaults: ["platform_app_defaults"],
     srcs: ["src/**/*.java"],
 
+    static_libs: [
+        "androidx.lifecycle_lifecycle-livedata",
+        "androidx.lifecycle_lifecycle-extensions",
+        "androidx.appcompat_appcompat",
+    ],
+
     platform_apis: true,
 }
diff --git a/packages/CompanionDeviceManager/res/drawable/dialog_background.xml b/packages/CompanionDeviceManager/res/drawable/dialog_background.xml
index a017f41..ef7052d 100644
--- a/packages/CompanionDeviceManager/res/drawable/dialog_background.xml
+++ b/packages/CompanionDeviceManager/res/drawable/dialog_background.xml
@@ -16,7 +16,7 @@
 
 <inset xmlns:android="http://schemas.android.com/apk/res/android">
     <shape android:shape="rectangle">
-        <corners android:radius="?android:attr/dialogCornerRadius" />
+        <corners android:radius="@*android:dimen/config_dialogCornerRadius" />
         <solid android:color="?android:attr/colorBackground" />
     </shape>
 </inset>
diff --git a/packages/CompanionDeviceManager/res/values/themes.xml b/packages/CompanionDeviceManager/res/values/themes.xml
index 66729347..8559ef6 100644
--- a/packages/CompanionDeviceManager/res/values/themes.xml
+++ b/packages/CompanionDeviceManager/res/values/themes.xml
@@ -17,7 +17,9 @@
 <resources>
 
     <style name="ChooserActivity"
-           parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar">
+           parent="@style/Theme.AppCompat.Light.Dialog">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
         <item name="*android:windowFixedHeightMajor">100%</item>
         <item name="*android:windowFixedHeightMinor">100%</item>
         <item name="android:windowBackground">@android:color/transparent</item>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 27c14af..9d3fc7f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -22,8 +22,8 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
-import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE;
-import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.TIMEOUT_OBSERVABLE;
+import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
+import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
@@ -32,7 +32,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Activity;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -50,7 +49,13 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-public class CompanionDeviceActivity extends Activity {
+import androidx.appcompat.app.AppCompatActivity;
+
+/**
+ *  A CompanionDevice activity response for showing the available
+ *  nearby devices to be associated with.
+ */
+public class CompanionDeviceActivity extends AppCompatActivity {
     private static final boolean DEBUG = false;
     private static final String TAG = CompanionDeviceActivity.class.getSimpleName();
 
@@ -126,7 +131,9 @@
         // Start discovery services if needed.
         if (!mRequest.isSelfManaged()) {
             CompanionDeviceDiscoveryService.startForRequest(this, mRequest);
-            TIMEOUT_OBSERVABLE.addObserver((o, arg) -> cancel(true));
+            // TODO(b/217749191): Create the ViewModel for the LiveData
+            CompanionDeviceDiscoveryService.getDiscoveryState().observe(
+                    /* LifeCycleOwner */ this, this::onDiscoveryStateChanged);
         }
         // Init UI.
         initUI();
@@ -158,10 +165,6 @@
         if (!isDone()) {
             cancel(false); // will finish()
         }
-
-        TIMEOUT_OBSERVABLE.deleteObservers();
-        // mAdapter may also be observing - need to remove it.
-        SCAN_RESULTS_OBSERVABLE.deleteObservers();
     }
 
     @Override
@@ -210,6 +213,13 @@
         }
     }
 
+    private void onDiscoveryStateChanged(DiscoveryState newState) {
+        if (newState == FINISHED_TIMEOUT
+                && CompanionDeviceDiscoveryService.getScanResult().getValue().isEmpty()) {
+            cancel(true);
+        }
+    }
+
     private void onUserSelectedDevice(@NonNull DeviceFilterPair<?> selectedDevice) {
         if (mSelectedDevice != null) {
             if (DEBUG) Log.w(TAG, "Already selected.");
@@ -380,9 +390,12 @@
         mSummary.setText(summary);
 
         mAdapter = new DeviceListAdapter(this);
-        SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
+
         // TODO: hide the list and show a spinner until a first device matching device is found.
         mListView.setAdapter(mAdapter);
+        CompanionDeviceDiscoveryService.getScanResult().observe(
+                /* lifecycleOwner */ this,
+                /* observer */ mAdapter);
 
         // "Remove" consent button: users would need to click on the list item.
         mButtonAllow.setVisibility(View.GONE);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index f859130..5d48708 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -56,13 +56,18 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.Observable;
 
+/**
+ *  A CompanionDevice service response for scanning nearby devices
+ */
 public class CompanionDeviceDiscoveryService extends Service {
     private static final boolean DEBUG = false;
     private static final String TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
@@ -78,12 +83,10 @@
             "com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY";
     private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
 
-
-    // TODO: replace with LiveData-s?
-    static final Observable TIMEOUT_OBSERVABLE = new MyObservable();
-    static final Observable SCAN_RESULTS_OBSERVABLE = new MyObservable();
-
-    private static CompanionDeviceDiscoveryService sInstance;
+    private static MutableLiveData<List<DeviceFilterPair<?>>> sScanResultsLiveData =
+            new MutableLiveData<>(Collections.emptyList());
+    private static MutableLiveData<DiscoveryState> sStateLiveData =
+            new MutableLiveData<>(DiscoveryState.NOT_STARTED);
 
     private BluetoothManager mBtManager;
     private BluetoothAdapter mBtAdapter;
@@ -100,12 +103,25 @@
 
     private final Runnable mTimeoutRunnable = this::timeout;
 
+    /**
+     * A state enum for devices' discovery.
+     */
+    enum DiscoveryState {
+        NOT_STARTED,
+        STARTING,
+        DISCOVERY_IN_PROGRESS,
+        FINISHED_STOPPED,
+        FINISHED_TIMEOUT
+    }
+
     static void startForRequest(
             @NonNull Context context, @NonNull AssociationRequest associationRequest) {
         requireNonNull(associationRequest);
         final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
         intent.setAction(ACTION_START_DISCOVERY);
         intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest);
+        sStateLiveData.setValue(DiscoveryState.STARTING);
+
         context.startService(intent);
     }
 
@@ -115,10 +131,12 @@
         context.startService(intent);
     }
 
-    @MainThread
-    static @NonNull List<DeviceFilterPair<?>> getScanResults() {
-        return sInstance != null ? new ArrayList<>(sInstance.mDevicesFound)
-                : Collections.emptyList();
+    static LiveData<List<DeviceFilterPair<?>>> getScanResult() {
+        return sScanResultsLiveData;
+    }
+
+    static LiveData<DiscoveryState> getDiscoveryState() {
+        return sStateLiveData;
     }
 
     @Override
@@ -126,8 +144,6 @@
         super.onCreate();
         if (DEBUG) Log.d(TAG, "onCreate()");
 
-        sInstance = this;
-
         mBtManager = getSystemService(BluetoothManager.class);
         mBtAdapter = mBtManager.getAdapter();
         mBleScanner = mBtAdapter.getBluetoothLeScanner();
@@ -148,6 +164,7 @@
 
             case ACTION_STOP_DISCOVERY:
                 stopDiscoveryAndFinish();
+                sStateLiveData.setValue(DiscoveryState.FINISHED_STOPPED);
                 break;
         }
         return START_NOT_STICKY;
@@ -157,8 +174,6 @@
     public void onDestroy() {
         super.onDestroy();
         if (DEBUG) Log.d(TAG, "onDestroy()");
-
-        sInstance = null;
     }
 
     @MainThread
@@ -168,6 +183,8 @@
 
         if (mDiscoveryStarted) throw new RuntimeException("Discovery in progress.");
         mDiscoveryStarted = true;
+        sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS);
+        sScanResultsLiveData.setValue(Collections.emptyList());
 
         final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
         final List<BluetoothDeviceFilter> btFilters =
@@ -329,7 +346,7 @@
             // First: make change.
             mDevicesFound.add(device);
             // Then: notify observers.
-            SCAN_RESULTS_OBSERVABLE.notifyObservers();
+            sScanResultsLiveData.setValue(mDevicesFound);
         });
     }
 
@@ -340,7 +357,7 @@
             // First: make change.
             mDevicesFound.remove(device);
             // Then: notify observers.
-            SCAN_RESULTS_OBSERVABLE.notifyObservers();
+            sScanResultsLiveData.setValue(mDevicesFound);
         });
     }
 
@@ -362,7 +379,7 @@
     private void timeout() {
         if (DEBUG) Log.i(TAG, "timeout()");
         stopDiscoveryAndFinish();
-        TIMEOUT_OBSERVABLE.notifyObservers();
+        sStateLiveData.setValue(DiscoveryState.FINISHED_TIMEOUT);
     }
 
     @Override
@@ -470,12 +487,4 @@
         }
         return result;
     }
-
-    private static class MyObservable extends Observable {
-        @Override
-        public void notifyObservers() {
-            setChanged();
-            super.notifyObservers();
-        }
-    }
 }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
index 2499cf0..198b778 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -26,14 +26,14 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.lifecycle.Observer;
+
 import java.util.List;
-import java.util.Observable;
-import java.util.Observer;
 
 /**
  * Adapter for the list of "found" devices.
  */
-class DeviceListAdapter extends BaseAdapter implements Observer {
+class DeviceListAdapter extends BaseAdapter implements Observer<List<DeviceFilterPair<?>>> {
     private final Context mContext;
 
     // List if pairs (display name, address)
@@ -59,12 +59,6 @@
     }
 
     @Override
-    public void update(Observable o, Object arg) {
-        mDevices = CompanionDeviceDiscoveryService.getScanResults();
-        notifyDataSetChanged();
-    }
-
-    @Override
     public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
         final View view = convertView != null
                 ? convertView
@@ -89,4 +83,10 @@
         // final Drawable icon = getTintedIcon(mResources, iconRes);
         // iconView.setImageDrawable(icon);
     }
+
+    @Override
+    public void onChanged(List<DeviceFilterPair<?>> deviceFilterPairs) {
+        mDevices = deviceFilterPairs;
+        notifyDataSetChanged();
+    }
 }