Merge "Use Activity Context for CDM ListView Adapter" into sc-dev
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 91bad7b..f9795c6 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -41,13 +41,13 @@
         android:supportsRtl="true">
 
         <service
-            android:name=".DeviceDiscoveryService"
+            android:name=".CompanionDeviceDiscoveryService"
             android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
             android:exported="true">
         </service>
 
         <activity
-            android:name=".DeviceChooserActivity"
+            android:name=".CompanionDeviceActivity"
             android:theme="@style/ChooserActivity"
             android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
             android:exported="true">
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
similarity index 62%
rename from packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
rename to packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index c30d4bf..28072ca 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -24,6 +24,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.companion.AssociationRequest;
@@ -31,41 +32,52 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.database.DataSetObserver;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.text.Html;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
-import android.widget.AdapterView;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
 import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.companiondevicemanager.DeviceDiscoveryService.DeviceFilterPair;
+import com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DeviceFilterPair;
 import com.android.internal.util.Preconditions;
 
-public class DeviceChooserActivity extends Activity {
+public class CompanionDeviceActivity extends Activity {
 
     private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "DeviceChooserActivity";
+    private static final String LOG_TAG = CompanionDeviceActivity.class.getSimpleName();
+
+    static CompanionDeviceActivity sInstance;
 
     View mLoadingIndicator = null;
     ListView mDeviceListView;
     private View mPairButton;
     private View mCancelButton;
 
+    DevicesAdapter mDevicesAdapter;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        if (DEBUG) Log.i(LOG_TAG, "Started with intent " + getIntent());
+        Log.i(LOG_TAG, "Starting UI for " + getService().mRequest);
 
         if (getService().mDevicesFound.isEmpty()) {
             Log.e(LOG_TAG, "About to show UI, but no devices to show");
         }
 
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        sInstance = this;
 
         String deviceProfile = getRequest().getDeviceProfile();
         String profilePrivacyDisclaimer = emptyIfNull(getRequest()
@@ -96,17 +108,14 @@
                     profileName,
                     getCallingAppName()), 0));
             mDeviceListView = findViewById(R.id.device_list);
-            final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter;
-            mDeviceListView.setAdapter(adapter);
-            mDeviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-                @Override
-                public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) {
-                    getService().mSelectedDevice =
-                            (DeviceFilterPair) adapterView.getItemAtPosition(pos);
-                    adapter.notifyDataSetChanged();
-                }
+            mDevicesAdapter = new DevicesAdapter();
+            mDeviceListView.setAdapter(mDevicesAdapter);
+            mDeviceListView.setOnItemClickListener((adapterView, view, pos, l) -> {
+                getService().mSelectedDevice =
+                        (DeviceFilterPair) adapterView.getItemAtPosition(pos);
+                mDevicesAdapter.notifyDataSetChanged();
             });
-            adapter.registerDataSetObserver(new DataSetObserver() {
+            mDevicesAdapter.registerDataSetObserver(new DataSetObserver() {
                 @Override
                 public void onChanged() {
                     onSelectionUpdate();
@@ -133,6 +142,12 @@
         mCancelButton.setOnClickListener(v -> cancel());
     }
 
+    static void notifyDevicesChanged() {
+        if (sInstance != null && !sInstance.isFinishing()) {
+            sInstance.mDevicesAdapter.notifyDataSetChanged();
+        }
+    }
+
     private AssociationRequest getRequest() {
         return getService().mRequest;
     }
@@ -156,6 +171,7 @@
     }
 
     private void cancel() {
+        Log.i(LOG_TAG, "cancel()");
         getService().onCancel();
         setResult(RESULT_CANCELED);
         finish();
@@ -170,6 +186,14 @@
         }
     }
 
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sInstance == this) {
+            sInstance = null;
+        }
+    }
+
     private CharSequence getCallingAppName() {
         try {
             final PackageManager packageManager = getPackageManager();
@@ -217,15 +241,91 @@
         }
     }
 
-    private DeviceDiscoveryService getService() {
-        return DeviceDiscoveryService.sInstance;
+    private CompanionDeviceDiscoveryService getService() {
+        return CompanionDeviceDiscoveryService.sInstance;
     }
 
-   protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) {
+    protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) {
+        Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")");
         getService().onDeviceSelected(
                 getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
         setResult(RESULT_OK,
                 new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device));
         finish();
     }
+
+    class DevicesAdapter extends BaseAdapter {
+        private final Drawable mBluetoothIcon = icon(android.R.drawable.stat_sys_data_bluetooth);
+        private final Drawable mWifiIcon = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
+
+        private SparseArray<Integer> mColors = new SparseArray();
+
+        private Drawable icon(int drawableRes) {
+            Drawable icon = getResources().getDrawable(drawableRes, null);
+            icon.setTint(Color.DKGRAY);
+            return icon;
+        }
+
+        @Override
+        public View getView(
+                int position,
+                @Nullable View convertView,
+                @NonNull ViewGroup parent) {
+            TextView view = convertView instanceof TextView
+                    ? (TextView) convertView
+                    : newView();
+            bind(view, getItem(position));
+            return view;
+        }
+
+        private void bind(TextView textView, DeviceFilterPair device) {
+            textView.setText(device.getDisplayName());
+            textView.setBackgroundColor(
+                    device.equals(getService().mSelectedDevice)
+                            ? getColor(android.R.attr.colorControlHighlight)
+                            : Color.TRANSPARENT);
+            textView.setCompoundDrawablesWithIntrinsicBounds(
+                    device.device instanceof android.net.wifi.ScanResult
+                            ? mWifiIcon
+                            : mBluetoothIcon,
+                    null, null, null);
+            textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground));
+        }
+
+        private TextView newView() {
+            final TextView textView = new TextView(CompanionDeviceActivity.this);
+            textView.setTextColor(getColor(android.R.attr.colorForeground));
+            final int padding = CompanionDeviceActivity.getPadding(getResources());
+            textView.setPadding(padding, padding, padding, padding);
+            textView.setCompoundDrawablePadding(padding);
+            return textView;
+        }
+
+        private int getColor(int colorAttr) {
+            if (mColors.contains(colorAttr)) {
+                return mColors.get(colorAttr);
+            }
+            TypedValue typedValue = new TypedValue();
+            TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr });
+            int result = a.getColor(0, 0);
+            a.recycle();
+            mColors.put(colorAttr, result);
+            return result;
+        }
+
+        @Override
+        public int getCount() {
+            return getService().mDevicesFound.size();
+        }
+
+        @Override
+        public DeviceFilterPair getItem(int position) {
+            return getService().mDevicesFound.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+    }
 }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
similarity index 80%
rename from packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
rename to packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 67d4b41..e620dfb 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -50,9 +50,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.IBinder;
@@ -60,12 +57,6 @@
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
 
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.ArrayUtils;
@@ -76,14 +67,14 @@
 import java.util.List;
 import java.util.Objects;
 
-public class DeviceDiscoveryService extends Service {
+public class CompanionDeviceDiscoveryService extends Service {
 
     private static final boolean DEBUG = false;
-    private static final String LOG_TAG = "DeviceDiscoveryService";
+    private static final String LOG_TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
 
     private static final long SCAN_TIMEOUT = 20000;
 
-    static DeviceDiscoveryService sInstance;
+    static CompanionDeviceDiscoveryService sInstance;
 
     private BluetoothManager mBluetoothManager;
     private BluetoothAdapter mBluetoothAdapter;
@@ -102,12 +93,12 @@
     AssociationRequest mRequest;
     List<DeviceFilterPair> mDevicesFound;
     DeviceFilterPair mSelectedDevice;
-    DevicesAdapter mDevicesAdapter;
     IFindDeviceCallback mFindCallback;
 
     AndroidFuture<Association> mServiceCallback;
     boolean mIsScanning = false;
-    @Nullable DeviceChooserActivity mActivity = null;
+    @Nullable
+    CompanionDeviceActivity mActivity = null;
 
     private final ICompanionDeviceDiscoveryService mBinder =
             new ICompanionDeviceDiscoveryService.Stub() {
@@ -125,7 +116,8 @@
             mFindCallback = findCallback;
             mServiceCallback = serviceCallback;
             Handler.getMain().sendMessage(obtainMessage(
-                    DeviceDiscoveryService::startDiscovery, DeviceDiscoveryService.this, request));
+                    CompanionDeviceDiscoveryService::startDiscovery,
+                    CompanionDeviceDiscoveryService.this, request));
         }
     };
 
@@ -151,7 +143,6 @@
         mWifiManager = getSystemService(WifiManager.class);
 
         mDevicesFound = new ArrayList<>();
-        mDevicesAdapter = new DevicesAdapter();
 
         sInstance = this;
     }
@@ -165,7 +156,8 @@
             mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
             mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
             mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLeDeviceFilter.class);
-            mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter);
+            mBLEScanFilters
+                    = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter);
 
             reset();
         } else if (DEBUG) Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
@@ -223,7 +215,7 @@
         }
         mIsScanning = true;
         Handler.getMain().sendMessageDelayed(
-                obtainMessage(DeviceDiscoveryService::stopScan, this),
+                obtainMessage(CompanionDeviceDiscoveryService::stopScan, this),
                 SCAN_TIMEOUT);
     }
 
@@ -237,7 +229,7 @@
         stopScan();
         mDevicesFound.clear();
         mSelectedDevice = null;
-        mDevicesAdapter.notifyDataSetChanged();
+        CompanionDeviceActivity.notifyDevicesChanged();
     }
 
     @Override
@@ -252,7 +244,7 @@
         if (!mIsScanning) return;
         mIsScanning = false;
 
-        DeviceChooserActivity activity = mActivity;
+        CompanionDeviceActivity activity = mActivity;
         if (activity != null) {
             if (activity.mDeviceListView != null) {
                 activity.mDeviceListView.removeFooterView(activity.mLoadingIndicator);
@@ -276,7 +268,7 @@
         if (device == null) return;
 
         Handler.getMain().sendMessage(obtainMessage(
-                DeviceDiscoveryService::onDeviceFoundMainThread, this, device));
+                CompanionDeviceDiscoveryService::onDeviceFoundMainThread, this, device));
     }
 
     @MainThread
@@ -292,7 +284,7 @@
             onReadyToShowUI();
         }
         mDevicesFound.add(device);
-        mDevicesAdapter.notifyDataSetChanged();
+        CompanionDeviceActivity.notifyDevicesChanged();
     }
 
     //TODO also, on timeout -> call onFailure
@@ -300,7 +292,7 @@
         try {
             mFindCallback.onSuccess(PendingIntent.getActivity(
                     this, 0,
-                    new Intent(this, DeviceChooserActivity.class),
+                    new Intent(this, CompanionDeviceActivity.class),
                     PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
                             | PendingIntent.FLAG_IMMUTABLE));
         } catch (RemoteException e) {
@@ -311,13 +303,13 @@
     private void onDeviceLost(@Nullable DeviceFilterPair device) {
         Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
         Handler.getMain().sendMessage(obtainMessage(
-                DeviceDiscoveryService::onDeviceLostMainThread, this, device));
+                CompanionDeviceDiscoveryService::onDeviceLostMainThread, this, device));
     }
 
     @MainThread
     void onDeviceLostMainThread(@Nullable DeviceFilterPair device) {
         mDevicesFound.remove(device);
-        mDevicesAdapter.notifyDataSetChanged();
+        CompanionDeviceActivity.notifyDevicesChanged();
     }
 
     void onDeviceSelected(String callingPackage, String deviceAddress) {
@@ -331,81 +323,6 @@
         mServiceCallback.cancel(true);
     }
 
-    class DevicesAdapter extends BaseAdapter {
-        private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
-        private Drawable WIFI_ICON = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
-
-        private SparseArray<Integer> mColors = new SparseArray();
-
-        private Drawable icon(int drawableRes) {
-            Drawable icon = getResources().getDrawable(drawableRes, null);
-            icon.setTint(Color.DKGRAY);
-            return icon;
-        }
-
-        @Override
-        public View getView(
-                int position,
-                @Nullable View convertView,
-                @NonNull ViewGroup parent) {
-            TextView view = convertView instanceof TextView
-                    ? (TextView) convertView
-                    : newView();
-            bind(view, getItem(position));
-            return view;
-        }
-
-        private void bind(TextView textView, DeviceFilterPair device) {
-            textView.setText(device.getDisplayName());
-            textView.setBackgroundColor(
-                    device.equals(mSelectedDevice)
-                            ? getColor(android.R.attr.colorControlHighlight)
-                            : Color.TRANSPARENT);
-            textView.setCompoundDrawablesWithIntrinsicBounds(
-                    device.device instanceof android.net.wifi.ScanResult
-                        ? WIFI_ICON
-                        : BLUETOOTH_ICON,
-                    null, null, null);
-            textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground));
-        }
-
-        private TextView newView() {
-            final TextView textView = new TextView(DeviceDiscoveryService.this);
-            textView.setTextColor(getColor(android.R.attr.colorForeground));
-            final int padding = DeviceChooserActivity.getPadding(getResources());
-            textView.setPadding(padding, padding, padding, padding);
-            textView.setCompoundDrawablePadding(padding);
-            return textView;
-        }
-
-        private int getColor(int colorAttr) {
-            if (mColors.contains(colorAttr)) {
-                return mColors.get(colorAttr);
-            }
-            TypedValue typedValue = new TypedValue();
-            TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr });
-            int result = a.getColor(0, 0);
-            a.recycle();
-            mColors.put(colorAttr, result);
-            return result;
-        }
-
-        @Override
-        public int getCount() {
-            return mDevicesFound.size();
-        }
-
-        @Override
-        public DeviceFilterPair getItem(int position) {
-            return mDevicesFound.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-    }
-
     /**
      * A pair of device and a filter that matched this device if any.
      *
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 01055be..ebfa917 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -142,18 +142,13 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
 
-//TODO onStop schedule unbind in 5 seconds
-//TODO make sure APIs are only callable from currently focused app
-//TODO schedule stopScan on activity destroy(except if configuration change)
-//TODO on associate called again after configuration change -> replace old callback with new
-//TODO avoid leaking calling activity in IFindDeviceCallback (see PrintManager#print for example)
 /** @hide */
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient {
 
     private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
             CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
-            ".DeviceDiscoveryService");
+            ".CompanionDeviceDiscoveryService");
 
     private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000;
     private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000;