Merge "Make CDM UI more test(CTS)-friendly"
This commit is contained in:
committed by
Android (Google) Code Review
commit
b98ac4bb64
@@ -14,6 +14,7 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/activity_confirmation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/dialog_background"
|
||||
@@ -23,6 +24,8 @@
|
||||
android:padding="18dp"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
@@ -30,7 +33,6 @@
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="12dp"
|
||||
style="@*android:style/TextAppearance.Widget.Toolbar.Title"/>
|
||||
<!-- style="@*android:style/TextAppearance.Widget.Toolbar.Title" -->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/summary"
|
||||
@@ -61,8 +63,10 @@
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_cancel"
|
||||
android:id="@+id/btn_negative"
|
||||
style="@android:style/Widget.Material.Button.Borderless.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -70,7 +74,7 @@
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_allow"
|
||||
android:id="@+id/btn_positive"
|
||||
style="@android:style/Widget.Material.Button.Borderless.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2021 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/list_item_device"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginRight="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -21,6 +21,8 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO
|
||||
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.Utils.getApplicationLabel;
|
||||
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
|
||||
import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
|
||||
@@ -91,10 +93,13 @@ public class CompanionDeviceActivity extends Activity {
|
||||
|
||||
// The flag used to prevent double taps, that may lead to sending several requests for creating
|
||||
// an association to CDM.
|
||||
private boolean mAssociationApproved;
|
||||
private boolean mApproved;
|
||||
private boolean mCancelled;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
if (DEBUG) Log.d(TAG, "onCreate()");
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
|
||||
}
|
||||
@@ -117,25 +122,12 @@ public class CompanionDeviceActivity extends Activity {
|
||||
// Start discovery services if needed.
|
||||
if (!mRequest.isSelfManaged()) {
|
||||
CompanionDeviceDiscoveryService.startForRequest(this, mRequest);
|
||||
TIMEOUT_OBSERVABLE.addObserver((o, arg) -> cancel(true));
|
||||
}
|
||||
// Init UI.
|
||||
initUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
|
||||
|
||||
// TODO: handle config changes without cancelling.
|
||||
if (!isFinishing()) {
|
||||
cancel(); // will finish()
|
||||
}
|
||||
|
||||
// mAdapter may be observing - need to remove it.
|
||||
CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.deleteObservers();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
// Handle another incoming request (while we are not done with the original - mRequest -
|
||||
@@ -153,6 +145,39 @@ public class CompanionDeviceActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
|
||||
|
||||
// TODO: handle config changes without cancelling.
|
||||
if (!isDone()) {
|
||||
cancel(false); // will finish()
|
||||
}
|
||||
|
||||
TIMEOUT_OBSERVABLE.deleteObservers();
|
||||
// mAdapter may also be observing - need to remove it.
|
||||
SCAN_RESULTS_OBSERVABLE.deleteObservers();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (DEBUG) Log.d(TAG, "onDestroy()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (DEBUG) Log.d(TAG, "onBackPressed()");
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (DEBUG) Log.d(TAG, "finish()", new Exception("Stack Trace Dump"));
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
|
||||
|
||||
@@ -164,10 +189,9 @@ public class CompanionDeviceActivity extends Activity {
|
||||
mListView = findViewById(R.id.device_list);
|
||||
mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position));
|
||||
|
||||
mButtonAllow = findViewById(R.id.button_allow);
|
||||
mButtonAllow.setOnClickListener(this::onAllowButtonClick);
|
||||
|
||||
findViewById(R.id.button_cancel).setOnClickListener(v -> cancel());
|
||||
mButtonAllow = findViewById(R.id.btn_positive);
|
||||
mButtonAllow.setOnClickListener(this::onPositiveButtonClick);
|
||||
findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick);
|
||||
|
||||
final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
|
||||
if (mRequest.isSelfManaged()) {
|
||||
@@ -179,6 +203,33 @@ public class CompanionDeviceActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
private void onAssociationApproved(@Nullable MacAddress macAddress) {
|
||||
if (isDone()) {
|
||||
if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
|
||||
return;
|
||||
}
|
||||
mApproved = true;
|
||||
|
||||
if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
|
||||
|
||||
if (!mRequest.isSelfManaged()) {
|
||||
requireNonNull(macAddress);
|
||||
CompanionDeviceDiscoveryService.stop(this);
|
||||
}
|
||||
|
||||
final Bundle data = new Bundle();
|
||||
data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest);
|
||||
data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder());
|
||||
if (macAddress != null) {
|
||||
data.putParcelable(EXTRA_MAC_ADDRESS, macAddress);
|
||||
}
|
||||
|
||||
data.putParcelable(EXTRA_RESULT_RECEIVER,
|
||||
prepareResultReceiverForIpc(mOnAssociationCreatedReceiver));
|
||||
|
||||
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
|
||||
}
|
||||
|
||||
private void onAssociationCreated(@NonNull AssociationInfo association) {
|
||||
if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
|
||||
|
||||
@@ -186,17 +237,26 @@ public class CompanionDeviceActivity extends Activity {
|
||||
setResultAndFinish(association);
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
if (DEBUG) Log.i(TAG, "cancel()");
|
||||
private void cancel(boolean discoveryTimeout) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "cancel(), discoveryTimeout=" + discoveryTimeout,
|
||||
new Exception("Stack Trace Dump"));
|
||||
}
|
||||
|
||||
if (isDone()) {
|
||||
if (DEBUG) Log.w(TAG, "Already done: " + (mApproved ? "Approved" : "Cancelled"));
|
||||
return;
|
||||
}
|
||||
mCancelled = true;
|
||||
|
||||
// Stop discovery service if it was used.
|
||||
if (!mRequest.isSelfManaged()) {
|
||||
if (!mRequest.isSelfManaged() || discoveryTimeout) {
|
||||
CompanionDeviceDiscoveryService.stop(this);
|
||||
}
|
||||
|
||||
// First send callback to the app directly...
|
||||
try {
|
||||
mAppCallback.onFailure("Cancelled.");
|
||||
mAppCallback.onFailure(discoveryTimeout ? "Timeout." : "Cancelled.");
|
||||
} catch (RemoteException ignore) {
|
||||
}
|
||||
|
||||
@@ -297,7 +357,7 @@ public class CompanionDeviceActivity extends Activity {
|
||||
mSummary.setText(summary);
|
||||
|
||||
mAdapter = new DeviceListAdapter(this);
|
||||
CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
|
||||
SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
|
||||
// TODO: hide the list and show a spinner until a first device matching device is found.
|
||||
mListView.setAdapter(mAdapter);
|
||||
|
||||
@@ -313,8 +373,8 @@ public class CompanionDeviceActivity extends Activity {
|
||||
onAssociationApproved(macAddress);
|
||||
}
|
||||
|
||||
private void onAllowButtonClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "onAllowButtonClick()");
|
||||
private void onPositiveButtonClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "on_Positive_ButtonClick()");
|
||||
|
||||
// Disable the button, to prevent more clicks.
|
||||
v.setEnabled(false);
|
||||
@@ -330,28 +390,17 @@ public class CompanionDeviceActivity extends Activity {
|
||||
onAssociationApproved(macAddress);
|
||||
}
|
||||
|
||||
private void onAssociationApproved(@Nullable MacAddress macAddress) {
|
||||
if (mAssociationApproved) return;
|
||||
mAssociationApproved = true;
|
||||
private void onNegativeButtonClick(View v) {
|
||||
if (DEBUG) Log.d(TAG, "on_Negative_ButtonClick()");
|
||||
|
||||
if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
|
||||
// Disable the button, to prevent more clicks.
|
||||
v.setEnabled(false);
|
||||
|
||||
if (!mRequest.isSelfManaged()) {
|
||||
requireNonNull(macAddress);
|
||||
CompanionDeviceDiscoveryService.stop(this);
|
||||
}
|
||||
cancel(false);
|
||||
}
|
||||
|
||||
final Bundle data = new Bundle();
|
||||
data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest);
|
||||
data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder());
|
||||
if (macAddress != null) {
|
||||
data.putParcelable(EXTRA_MAC_ADDRESS, macAddress);
|
||||
}
|
||||
|
||||
data.putParcelable(EXTRA_RESULT_RECEIVER,
|
||||
prepareResultReceiverForIpc(mOnAssociationCreatedReceiver));
|
||||
|
||||
mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
|
||||
private boolean isDone() {
|
||||
return mApproved || mCancelled;
|
||||
}
|
||||
|
||||
private final ResultReceiver mOnAssociationCreatedReceiver =
|
||||
|
||||
@@ -22,6 +22,8 @@ import static com.android.internal.util.CollectionUtils.filter;
|
||||
import static com.android.internal.util.CollectionUtils.find;
|
||||
import static com.android.internal.util.CollectionUtils.map;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.MainThread;
|
||||
@@ -50,6 +52,7 @@ import android.net.wifi.WifiManager;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcelable;
|
||||
import android.os.SystemProperties;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -64,13 +67,17 @@ public class CompanionDeviceDiscoveryService extends Service {
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
|
||||
|
||||
private static final String SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout";
|
||||
private static final long TIMEOUT_DEFAULT = 20_000L; // 20 seconds
|
||||
private static final long TIMEOUT_MIN = 1_000L; // 1 sec
|
||||
private static final long TIMEOUT_MAX = 60_000L; // 1 min
|
||||
|
||||
private static final String ACTION_START_DISCOVERY =
|
||||
"com.android.companiondevicemanager.action.START_DISCOVERY";
|
||||
private static final String ACTION_STOP_DISCOVERY =
|
||||
"com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY";
|
||||
private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
|
||||
|
||||
private static final long SCAN_TIMEOUT = 20_000L; // 20 seconds
|
||||
|
||||
// TODO: replace with LiveData-s?
|
||||
static final Observable TIMEOUT_OBSERVABLE = new MyObservable();
|
||||
@@ -180,8 +187,7 @@ public class CompanionDeviceDiscoveryService extends Service {
|
||||
// Start BLE scanning (if needed)
|
||||
mBleScanCallback = startBleScanningIfNeeded(bleFilters, forceStartScanningAll);
|
||||
|
||||
// Schedule a time-out.
|
||||
Handler.getMain().postDelayed(mTimeoutRunnable, SCAN_TIMEOUT);
|
||||
scheduleTimeout();
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -338,6 +344,21 @@ public class CompanionDeviceDiscoveryService extends Service {
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleTimeout() {
|
||||
long timeout = SystemProperties.getLong(SYS_PROP_DEBUG_TIMEOUT, -1);
|
||||
if (timeout <= 0) {
|
||||
// 0 or negative values indicate that the sysprop was never set or should be ignored.
|
||||
timeout = TIMEOUT_DEFAULT;
|
||||
} else {
|
||||
timeout = min(timeout, TIMEOUT_MAX); // should be <= 1 min (TIMEOUT_MAX)
|
||||
timeout = max(timeout, TIMEOUT_MIN); // should be >= 1 sec (TIMEOUT_MIN)
|
||||
}
|
||||
|
||||
if (DEBUG) Log.d(TAG, "scheduleTimeout(), timeout=" + timeout);
|
||||
|
||||
Handler.getMain().postDelayed(mTimeoutRunnable, timeout);
|
||||
}
|
||||
|
||||
private void timeout() {
|
||||
if (DEBUG) Log.i(TAG, "timeout()");
|
||||
stopDiscoveryAndFinish();
|
||||
|
||||
@@ -16,18 +16,14 @@
|
||||
|
||||
package com.android.companiondevicemanager;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
@@ -39,51 +35,12 @@ import java.util.Observer;
|
||||
*/
|
||||
class DeviceListAdapter extends BaseAdapter implements Observer {
|
||||
private final Context mContext;
|
||||
private final Resources mResources;
|
||||
|
||||
private final Drawable mBluetoothIcon;
|
||||
private final Drawable mWifiIcon;
|
||||
|
||||
private final @ColorInt int mTextColor;
|
||||
|
||||
// List if pairs (display name, address)
|
||||
private List<DeviceFilterPair<?>> mDevices;
|
||||
|
||||
DeviceListAdapter(Context context) {
|
||||
mContext = context;
|
||||
mResources = context.getResources();
|
||||
mBluetoothIcon = getTintedIcon(mResources, android.R.drawable.stat_sys_data_bluetooth);
|
||||
mWifiIcon = getTintedIcon(mResources, com.android.internal.R.drawable.ic_wifi_signal_3);
|
||||
mTextColor = getColor(context, android.R.attr.colorForeground);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
final TextView view = convertView != null ? (TextView) convertView : newView();
|
||||
bind(view, getItem(position));
|
||||
return view;
|
||||
}
|
||||
|
||||
private void bind(TextView textView, DeviceFilterPair<?> item) {
|
||||
textView.setText(item.getDisplayName());
|
||||
textView.setBackgroundColor(Color.TRANSPARENT);
|
||||
/*
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
item.getDevice() instanceof android.net.wifi.ScanResult
|
||||
? mWifiIcon
|
||||
: mBluetoothIcon,
|
||||
null, null, null);
|
||||
textView.getCompoundDrawables()[0].setTint(mTextColor);
|
||||
*/
|
||||
}
|
||||
|
||||
private TextView newView() {
|
||||
final TextView textView = new TextView(mContext);
|
||||
textView.setTextColor(mTextColor);
|
||||
final int padding = 24;
|
||||
textView.setPadding(padding, padding, padding, padding);
|
||||
//textView.setCompoundDrawablePadding(padding);
|
||||
return textView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,17 +64,29 @@ class DeviceListAdapter extends BaseAdapter implements Observer {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private @ColorInt int getColor(Context context, int attr) {
|
||||
final TypedArray a = context.obtainStyledAttributes(new TypedValue().data,
|
||||
new int[] { attr });
|
||||
final int color = a.getColor(0, 0);
|
||||
a.recycle();
|
||||
return color;
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
final View view = convertView != null
|
||||
? convertView
|
||||
: LayoutInflater.from(mContext).inflate(R.layout.list_item_device, parent, false);
|
||||
|
||||
final DeviceFilterPair<?> item = getItem(position);
|
||||
bindView(view, item);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private static Drawable getTintedIcon(Resources resources, int drawableRes) {
|
||||
Drawable icon = resources.getDrawable(drawableRes, null);
|
||||
icon.setTint(Color.DKGRAY);
|
||||
return icon;
|
||||
private void bindView(@NonNull View view, DeviceFilterPair<?> item) {
|
||||
final TextView textView = view.findViewById(android.R.id.text1);
|
||||
textView.setText(item.getDisplayName());
|
||||
|
||||
final ImageView iconView = view.findViewById(android.R.id.icon);
|
||||
|
||||
// TODO(b/211417476): Set either Bluetooth or WiFi icon.
|
||||
iconView.setVisibility(View.GONE);
|
||||
// final int iconRes = isBt ? android.R.drawable.stat_sys_data_bluetooth
|
||||
// : com.android.internal.R.drawable.ic_wifi_signal_3;
|
||||
// final Drawable icon = getTintedIcon(mResources, iconRes);
|
||||
// iconView.setImageDrawable(icon);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user