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.settings.bluetooth;
18 
19 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
20 
21 import android.app.settings.SettingsEnums;
22 import android.bluetooth.BluetoothDevice;
23 import android.content.Context;
24 import android.os.Bundle;
25 import android.provider.DeviceConfig;
26 import android.view.Menu;
27 import android.view.MenuInflater;
28 import android.view.MenuItem;
29 
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.settings.R;
33 import com.android.settings.core.SettingsUIDeviceConfig;
34 import com.android.settings.dashboard.RestrictedDashboardFragment;
35 import com.android.settings.overlay.FeatureFactory;
36 import com.android.settings.slices.BlockingSlicePrefController;
37 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
38 import com.android.settingslib.bluetooth.LocalBluetoothManager;
39 import com.android.settingslib.core.AbstractPreferenceController;
40 import com.android.settingslib.core.lifecycle.Lifecycle;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 public class BluetoothDeviceDetailsFragment extends RestrictedDashboardFragment {
46     public static final String KEY_DEVICE_ADDRESS = "device_address";
47     private static final String TAG = "BTDeviceDetailsFrg";
48 
49     @VisibleForTesting
50     static int EDIT_DEVICE_NAME_ITEM_ID = Menu.FIRST;
51 
52     /**
53      * An interface to let tests override the normal mechanism for looking up the
54      * CachedBluetoothDevice and LocalBluetoothManager, and substitute their own mocks instead.
55      * This is only needed in situations where you instantiate the fragment indirectly (eg via an
56      * intent) and can't use something like spying on an instance you construct directly via
57      * newInstance.
58      */
59     @VisibleForTesting
60     interface TestDataFactory {
getDevice(String deviceAddress)61         CachedBluetoothDevice getDevice(String deviceAddress);
getManager(Context context)62         LocalBluetoothManager getManager(Context context);
63     }
64 
65     @VisibleForTesting
66     static TestDataFactory sTestDataFactory;
67 
68     @VisibleForTesting
69     String mDeviceAddress;
70     @VisibleForTesting
71     LocalBluetoothManager mManager;
72     @VisibleForTesting
73     CachedBluetoothDevice mCachedDevice;
74 
BluetoothDeviceDetailsFragment()75     public BluetoothDeviceDetailsFragment() {
76         super(DISALLOW_CONFIG_BLUETOOTH);
77     }
78 
79     @VisibleForTesting
getLocalBluetoothManager(Context context)80     LocalBluetoothManager getLocalBluetoothManager(Context context) {
81         if (sTestDataFactory != null) {
82             return sTestDataFactory.getManager(context);
83         }
84         return Utils.getLocalBtManager(context);
85     }
86 
87     @VisibleForTesting
getCachedDevice(String deviceAddress)88     CachedBluetoothDevice getCachedDevice(String deviceAddress) {
89         if (sTestDataFactory != null) {
90             return sTestDataFactory.getDevice(deviceAddress);
91         }
92         BluetoothDevice remoteDevice =
93                 mManager.getBluetoothAdapter().getRemoteDevice(deviceAddress);
94         return mManager.getCachedDeviceManager().findDevice(remoteDevice);
95     }
96 
newInstance(String deviceAddress)97     public static BluetoothDeviceDetailsFragment newInstance(String deviceAddress) {
98         Bundle args = new Bundle(1);
99         args.putString(KEY_DEVICE_ADDRESS, deviceAddress);
100         BluetoothDeviceDetailsFragment fragment = new BluetoothDeviceDetailsFragment();
101         fragment.setArguments(args);
102         return fragment;
103     }
104 
105     @Override
onAttach(Context context)106     public void onAttach(Context context) {
107         mDeviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
108         mManager = getLocalBluetoothManager(context);
109         mCachedDevice = getCachedDevice(mDeviceAddress);
110         if (mCachedDevice == null) {
111             // Close this page if device is null with invalid device mac address
112             finish();
113             return;
114         }
115         super.onAttach(context);
116         use(AdvancedBluetoothDetailsHeaderController.class).init(mCachedDevice);
117 
118         final BluetoothFeatureProvider featureProvider = FeatureFactory.getFactory(
119                 context).getBluetoothFeatureProvider(context);
120         final boolean sliceEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
121                 SettingsUIDeviceConfig.BT_SLICE_SETTINGS_ENABLED, true);
122 
123         use(BlockingSlicePrefController.class).setSliceUri(sliceEnabled
124                 ? featureProvider.getBluetoothDeviceSettingsUri(mCachedDevice.getDevice())
125                 : null);
126     }
127 
128     @Override
getMetricsCategory()129     public int getMetricsCategory() {
130         return SettingsEnums.BLUETOOTH_DEVICE_DETAILS;
131     }
132 
133     @Override
getLogTag()134     protected String getLogTag() {
135         return TAG;
136     }
137 
138     @Override
getPreferenceScreenResId()139     protected int getPreferenceScreenResId() {
140         return R.xml.bluetooth_device_details_fragment;
141     }
142 
143     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)144     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
145         MenuItem item = menu.add(0, EDIT_DEVICE_NAME_ITEM_ID, 0, R.string.bluetooth_rename_button);
146         item.setIcon(com.android.internal.R.drawable.ic_mode_edit);
147         item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
148         super.onCreateOptionsMenu(menu, inflater);
149     }
150 
151     @Override
onOptionsItemSelected(MenuItem menuItem)152     public boolean onOptionsItemSelected(MenuItem menuItem) {
153         if (menuItem.getItemId() == EDIT_DEVICE_NAME_ITEM_ID) {
154             RemoteDeviceNameDialogFragment.newInstance(mCachedDevice).show(
155                     getFragmentManager(), RemoteDeviceNameDialogFragment.TAG);
156             return true;
157         }
158         return super.onOptionsItemSelected(menuItem);
159     }
160 
161     @Override
createPreferenceControllers(Context context)162     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
163         ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
164 
165         if (mCachedDevice != null) {
166             Lifecycle lifecycle = getSettingsLifecycle();
167             controllers.add(new BluetoothDetailsHeaderController(context, this, mCachedDevice,
168                     lifecycle, mManager));
169             controllers.add(new BluetoothDetailsButtonsController(context, this, mCachedDevice,
170                     lifecycle));
171             controllers.add(new BluetoothDetailsProfilesController(context, this, mManager,
172                     mCachedDevice, lifecycle));
173             controllers.add(new BluetoothDetailsMacAddressController(context, this, mCachedDevice,
174                     lifecycle));
175         }
176         return controllers;
177     }
178 }
179