Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 145d5a89 authored by Eugene Susla's avatar Eugene Susla
Browse files

Use Activity Context for CDM ListView Adapter

Using a service context has potential corner cases where it tries
to create UI using it, causing issues.

Test: presubmit
Bug: 180931292
Change-Id: I3a6d13ff69797cb5c7c2f25b810785e458d69430
parent f6ed5fc5
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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">
+118 −18
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE

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.companion.CompanionDeviceManager;
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 @@ public class DeviceChooserActivity extends Activity {
                    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) {
            mDevicesAdapter = new DevicesAdapter();
            mDeviceListView.setAdapter(mDevicesAdapter);
            mDeviceListView.setOnItemClickListener((adapterView, view, pos, l) -> {
                getService().mSelectedDevice =
                        (DeviceFilterPair) adapterView.getItemAtPosition(pos);
                    adapter.notifyDataSetChanged();
                }
                mDevicesAdapter.notifyDataSetChanged();
            });
            adapter.registerDataSetObserver(new DataSetObserver() {
            mDevicesAdapter.registerDataSetObserver(new DataSetObserver() {
                @Override
                public void onChanged() {
                    onSelectionUpdate();
@@ -133,6 +142,12 @@ public class DeviceChooserActivity extends Activity {
        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 @@ public class DeviceChooserActivity extends Activity {
    }

    private void cancel() {
        Log.i(LOG_TAG, "cancel()");
        getService().onCancel();
        setResult(RESULT_CANCELED);
        finish();
@@ -170,6 +186,14 @@ public class DeviceChooserActivity extends Activity {
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (sInstance == this) {
            sInstance = null;
        }
    }

    private CharSequence getCallingAppName() {
        try {
            final PackageManager packageManager = getPackageManager();
@@ -217,15 +241,91 @@ public class DeviceChooserActivity extends Activity {
        }
    }

    private DeviceDiscoveryService getService() {
        return DeviceDiscoveryService.sInstance;
    private CompanionDeviceDiscoveryService getService() {
        return CompanionDeviceDiscoveryService.sInstance;
    }

    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;
        }
    }
}
+17 −100
Original line number Diff line number Diff line
@@ -50,9 +50,6 @@ import android.content.BroadcastReceiver;
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.Parcelable;
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.ArrayList;
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 @@ public class DeviceDiscoveryService extends Service {
    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 @@ public class DeviceDiscoveryService extends Service {
            mFindCallback = findCallback;
            mServiceCallback = serviceCallback;
            Handler.getMain().sendMessage(obtainMessage(
                    DeviceDiscoveryService::startDiscovery, DeviceDiscoveryService.this, request));
                    CompanionDeviceDiscoveryService::startDiscovery,
                    CompanionDeviceDiscoveryService.this, request));
        }
    };

@@ -151,7 +143,6 @@ public class DeviceDiscoveryService extends Service {
        mWifiManager = getSystemService(WifiManager.class);

        mDevicesFound = new ArrayList<>();
        mDevicesAdapter = new DevicesAdapter();

        sInstance = this;
    }
@@ -165,7 +156,8 @@ public class DeviceDiscoveryService extends Service {
            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 @@ public class DeviceDiscoveryService extends Service {
        }
        mIsScanning = true;
        Handler.getMain().sendMessageDelayed(
                obtainMessage(DeviceDiscoveryService::stopScan, this),
                obtainMessage(CompanionDeviceDiscoveryService::stopScan, this),
                SCAN_TIMEOUT);
    }

@@ -237,7 +229,7 @@ public class DeviceDiscoveryService extends Service {
        stopScan();
        mDevicesFound.clear();
        mSelectedDevice = null;
        mDevicesAdapter.notifyDataSetChanged();
        CompanionDeviceActivity.notifyDevicesChanged();
    }

    @Override
@@ -252,7 +244,7 @@ public class DeviceDiscoveryService extends Service {
        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 @@ public class DeviceDiscoveryService extends Service {
        if (device == null) return;

        Handler.getMain().sendMessage(obtainMessage(
                DeviceDiscoveryService::onDeviceFoundMainThread, this, device));
                CompanionDeviceDiscoveryService::onDeviceFoundMainThread, this, device));
    }

    @MainThread
@@ -292,7 +284,7 @@ public class DeviceDiscoveryService extends Service {
            onReadyToShowUI();
        }
        mDevicesFound.add(device);
        mDevicesAdapter.notifyDataSetChanged();
        CompanionDeviceActivity.notifyDevicesChanged();
    }

    //TODO also, on timeout -> call onFailure
@@ -300,7 +292,7 @@ public class DeviceDiscoveryService extends Service {
        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 @@ public class DeviceDiscoveryService extends Service {
    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 @@ public class DeviceDiscoveryService extends Service {
        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.
     *
+1 −6
Original line number Diff line number Diff line
@@ -142,18 +142,13 @@ import java.util.concurrent.ConcurrentHashMap;
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;