• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.companiondevicemanager;
18 
19 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
20 import static android.text.TextUtils.emptyIfNull;
21 import static android.text.TextUtils.isEmpty;
22 import static android.text.TextUtils.withoutPrefix;
23 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
24 
25 import static java.util.Objects.requireNonNull;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.app.Activity;
30 import android.companion.AssociationRequest;
31 import android.companion.CompanionDeviceManager;
32 import android.content.Intent;
33 import android.content.pm.PackageManager;
34 import android.content.res.Resources;
35 import android.content.res.TypedArray;
36 import android.database.DataSetObserver;
37 import android.graphics.Color;
38 import android.graphics.drawable.Drawable;
39 import android.os.Bundle;
40 import android.text.Html;
41 import android.util.Log;
42 import android.util.SparseArray;
43 import android.util.TypedValue;
44 import android.view.Gravity;
45 import android.view.View;
46 import android.view.ViewGroup;
47 import android.widget.BaseAdapter;
48 import android.widget.ListView;
49 import android.widget.ProgressBar;
50 import android.widget.TextView;
51 
52 import com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DeviceFilterPair;
53 import com.android.internal.util.Preconditions;
54 
55 public class CompanionDeviceActivity extends Activity {
56 
57     private static final boolean DEBUG = false;
58     private static final String LOG_TAG = CompanionDeviceActivity.class.getSimpleName();
59 
60     static CompanionDeviceActivity sInstance;
61 
62     View mLoadingIndicator = null;
63     ListView mDeviceListView;
64     private View mPairButton;
65     private View mCancelButton;
66 
67     DevicesAdapter mDevicesAdapter;
68 
69     @Override
onCreate(Bundle savedInstanceState)70     public void onCreate(Bundle savedInstanceState) {
71         super.onCreate(savedInstanceState);
72 
73         Log.i(LOG_TAG, "Starting UI for " + getService().mRequest);
74 
75         if (getService().mDevicesFound.isEmpty()) {
76             Log.e(LOG_TAG, "About to show UI, but no devices to show");
77         }
78 
79         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
80         sInstance = this;
81 
82         String deviceProfile = getRequest().getDeviceProfile();
83         String profilePrivacyDisclaimer = emptyIfNull(getRequest()
84                 .getDeviceProfilePrivilegesDescription())
85                 .replace("APP_NAME", getCallingAppName());
86         boolean useDeviceProfile = deviceProfile != null && !isEmpty(profilePrivacyDisclaimer);
87         String profileName = useDeviceProfile
88                 ? getDeviceProfileName(deviceProfile)
89                 : getString(R.string.profile_name_generic);
90 
91         if (getRequest().isSingleDevice()) {
92             setContentView(R.layout.device_confirmation);
93             final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
94             setTitle(Html.fromHtml(getString(
95                     R.string.confirmation_title,
96                     getCallingAppName(),
97                     profileName,
98                     selectedDevice.getDisplayName()), 0));
99             mPairButton = findViewById(R.id.button_pair);
100             mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice));
101             getService().mSelectedDevice = selectedDevice;
102             onSelectionUpdate();
103             if (getRequest().isSkipPrompt()) {
104                 onDeviceConfirmed(selectedDevice);
105             }
106         } else {
107             setContentView(R.layout.device_chooser);
108             mPairButton = findViewById(R.id.button_pair);
109             mPairButton.setVisibility(View.GONE);
110             setTitle(Html.fromHtml(getString(R.string.chooser_title,
111                     profileName,
112                     getCallingAppName()), 0));
113             mDeviceListView = findViewById(R.id.device_list);
114             mDevicesAdapter = new DevicesAdapter();
115             mDeviceListView.setAdapter(mDevicesAdapter);
116             mDeviceListView.setOnItemClickListener((adapterView, view, pos, l) -> {
117                 getService().mSelectedDevice =
118                         (DeviceFilterPair) adapterView.getItemAtPosition(pos);
119                 mDevicesAdapter.notifyDataSetChanged();
120             });
121             mDevicesAdapter.registerDataSetObserver(new DataSetObserver() {
122                 @Override
123                 public void onChanged() {
124                     onSelectionUpdate();
125                 }
126             });
127             mDeviceListView.addFooterView(mLoadingIndicator = getProgressBar(), null, false);
128         }
129 
130         TextView profileSummary = findViewById(R.id.profile_summary);
131 
132         if (useDeviceProfile) {
133             profileSummary.setVisibility(View.VISIBLE);
134             String deviceRef = getRequest().isSingleDevice()
135                     ? getService().mDevicesFound.get(0).getDisplayName()
136                     : profileName;
137             profileSummary.setText(getString(R.string.profile_summary,
138                     deviceRef,
139                     profilePrivacyDisclaimer));
140         } else {
141             profileSummary.setVisibility(View.GONE);
142         }
143 
144         getService().mActivity = this;
145 
146         mCancelButton = findViewById(R.id.button_cancel);
147         mCancelButton.setOnClickListener(v -> cancel());
148     }
149 
notifyDevicesChanged()150     static void notifyDevicesChanged() {
151         if (sInstance != null && sInstance.mDevicesAdapter != null && !sInstance.isFinishing()) {
152             sInstance.mDevicesAdapter.notifyDataSetChanged();
153         }
154     }
155 
getRequest()156     private AssociationRequest getRequest() {
157         return getService().mRequest;
158     }
159 
getDeviceProfileName(@ullable String deviceProfile)160     private String getDeviceProfileName(@Nullable String deviceProfile) {
161         if (deviceProfile == null) {
162             return getString(R.string.profile_name_generic);
163         }
164         switch (deviceProfile) {
165             case AssociationRequest.DEVICE_PROFILE_WATCH: {
166                 return getString(R.string.profile_name_watch);
167             }
168             default: {
169                 Log.w(LOG_TAG,
170                         "No localized profile name found for device profile: " + deviceProfile);
171                 return withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile)
172                         .toLowerCase()
173                         .replace('_', ' ');
174             }
175         }
176     }
177 
cancel()178     private void cancel() {
179         Log.i(LOG_TAG, "cancel()");
180         getService().onCancel();
181         setResult(RESULT_CANCELED);
182         finish();
183     }
184 
185     @Override
onStop()186     protected void onStop() {
187         super.onStop();
188         if (!isFinishing() && !isChangingConfigurations()) {
189             Log.i(LOG_TAG, "onStop() - cancelling");
190             cancel();
191         }
192     }
193 
194     @Override
onDestroy()195     protected void onDestroy() {
196         super.onDestroy();
197         if (sInstance == this) {
198             sInstance = null;
199         }
200     }
201 
getCallingAppName()202     private CharSequence getCallingAppName() {
203         try {
204             final PackageManager packageManager = getPackageManager();
205             String callingPackage = Preconditions.checkStringNotEmpty(
206                     getCallingPackage(),
207                     "This activity must be called for result");
208             return packageManager.getApplicationLabel(
209                     packageManager.getApplicationInfo(callingPackage, 0));
210         } catch (PackageManager.NameNotFoundException e) {
211             throw new RuntimeException(e);
212         }
213     }
214 
215     @Override
getCallingPackage()216     public String getCallingPackage() {
217         return requireNonNull(getRequest().getCallingPackage());
218     }
219 
220     @Override
setTitle(CharSequence title)221     public void setTitle(CharSequence title) {
222         final TextView titleView = findViewById(R.id.title);
223         final int padding = getPadding(getResources());
224         titleView.setPadding(padding, padding, padding, padding);
225         titleView.setText(title);
226     }
227 
getProgressBar()228     private ProgressBar getProgressBar() {
229         final ProgressBar progressBar = new ProgressBar(this);
230         progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL);
231         final int padding = getPadding(getResources());
232         progressBar.setPadding(padding, padding, padding, padding);
233         return progressBar;
234     }
235 
getPadding(Resources r)236     static int getPadding(Resources r) {
237         return r.getDimensionPixelSize(R.dimen.padding);
238     }
239 
onSelectionUpdate()240     private void onSelectionUpdate() {
241         DeviceFilterPair selectedDevice = getService().mSelectedDevice;
242         if (mPairButton.getVisibility() != View.VISIBLE && selectedDevice != null) {
243             onDeviceConfirmed(selectedDevice);
244         } else {
245             mPairButton.setEnabled(selectedDevice != null);
246         }
247     }
248 
getService()249     private CompanionDeviceDiscoveryService getService() {
250         return CompanionDeviceDiscoveryService.sInstance;
251     }
252 
onDeviceConfirmed(DeviceFilterPair selectedDevice)253     protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) {
254         Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")");
255         getService().onDeviceSelected(
256                 getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
257         setResult(RESULT_OK,
258                 new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device));
259         finish();
260     }
261 
262     class DevicesAdapter extends BaseAdapter {
263         private final Drawable mBluetoothIcon = icon(android.R.drawable.stat_sys_data_bluetooth);
264         private final Drawable mWifiIcon = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
265 
266         private SparseArray<Integer> mColors = new SparseArray();
267 
icon(int drawableRes)268         private Drawable icon(int drawableRes) {
269             Drawable icon = getResources().getDrawable(drawableRes, null);
270             icon.setTint(Color.DKGRAY);
271             return icon;
272         }
273 
274         @Override
getView( int position, @Nullable View convertView, @NonNull ViewGroup parent)275         public View getView(
276                 int position,
277                 @Nullable View convertView,
278                 @NonNull ViewGroup parent) {
279             TextView view = convertView instanceof TextView
280                     ? (TextView) convertView
281                     : newView();
282             bind(view, getItem(position));
283             return view;
284         }
285 
bind(TextView textView, DeviceFilterPair device)286         private void bind(TextView textView, DeviceFilterPair device) {
287             textView.setText(device.getDisplayName());
288             textView.setBackgroundColor(
289                     device.equals(getService().mSelectedDevice)
290                             ? getColor(android.R.attr.colorControlHighlight)
291                             : Color.TRANSPARENT);
292             textView.setCompoundDrawablesWithIntrinsicBounds(
293                     device.device instanceof android.net.wifi.ScanResult
294                             ? mWifiIcon
295                             : mBluetoothIcon,
296                     null, null, null);
297             textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground));
298         }
299 
newView()300         private TextView newView() {
301             final TextView textView = new TextView(CompanionDeviceActivity.this);
302             textView.setTextColor(getColor(android.R.attr.colorForeground));
303             final int padding = CompanionDeviceActivity.getPadding(getResources());
304             textView.setPadding(padding, padding, padding, padding);
305             textView.setCompoundDrawablePadding(padding);
306             return textView;
307         }
308 
getColor(int colorAttr)309         private int getColor(int colorAttr) {
310             if (mColors.contains(colorAttr)) {
311                 return mColors.get(colorAttr);
312             }
313             TypedValue typedValue = new TypedValue();
314             TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr });
315             int result = a.getColor(0, 0);
316             a.recycle();
317             mColors.put(colorAttr, result);
318             return result;
319         }
320 
321         @Override
getCount()322         public int getCount() {
323             return getService().mDevicesFound.size();
324         }
325 
326         @Override
getItem(int position)327         public DeviceFilterPair getItem(int position) {
328             return getService().mDevicesFound.get(position);
329         }
330 
331         @Override
getItemId(int position)332         public long getItemId(int position) {
333             return position;
334         }
335     }
336 }
337