diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 91bad7b4aa91f..f9795c601431a 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -41,13 +41,13 @@ android:supportsRtl="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 c30d4bf322cf9..28072ca4f1137 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 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) { - 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 @@ 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) { + 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 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 67d4b41f164c8..e620dfbafa083 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.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 mDevicesFound; DeviceFilterPair mSelectedDevice; - DevicesAdapter mDevicesAdapter; IFindDeviceCallback mFindCallback; AndroidFuture 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 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 01055beea4f8f..ebfa9171b2b9f 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.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;