1 /*
2  * Copyright (C) 2018 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.connecteddevice.usb;
18 
19 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
20 import static android.net.ConnectivityManager.TETHERING_USB;
21 
22 import android.content.Context;
23 import android.hardware.usb.UsbManager;
24 import android.net.ConnectivityManager;
25 
26 import androidx.annotation.VisibleForTesting;
27 import androidx.preference.PreferenceCategory;
28 import androidx.preference.PreferenceScreen;
29 
30 import com.android.settings.R;
31 import com.android.settings.Utils;
32 import com.android.settingslib.widget.RadioButtonPreference;
33 
34 import java.util.LinkedHashMap;
35 import java.util.Map;
36 
37 /**
38  * This class controls the radio buttons for choosing between different USB functions.
39  */
40 public class UsbDetailsFunctionsController extends UsbDetailsController
41         implements RadioButtonPreference.OnClickListener {
42 
43     static final Map<Long, Integer> FUNCTIONS_MAP = new LinkedHashMap<>();
44 
45     static {
FUNCTIONS_MAP.put(UsbManager.FUNCTION_MTP, R.string.usb_use_file_transfers)46         FUNCTIONS_MAP.put(UsbManager.FUNCTION_MTP, R.string.usb_use_file_transfers);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_RNDIS, R.string.usb_use_tethering)47         FUNCTIONS_MAP.put(UsbManager.FUNCTION_RNDIS, R.string.usb_use_tethering);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_MIDI, R.string.usb_use_MIDI)48         FUNCTIONS_MAP.put(UsbManager.FUNCTION_MIDI, R.string.usb_use_MIDI);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_PTP, R.string.usb_use_photo_transfers)49         FUNCTIONS_MAP.put(UsbManager.FUNCTION_PTP, R.string.usb_use_photo_transfers);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_NONE, R.string.usb_use_charging_only)50         FUNCTIONS_MAP.put(UsbManager.FUNCTION_NONE, R.string.usb_use_charging_only);
51     }
52 
53     private PreferenceCategory mProfilesContainer;
54     private ConnectivityManager mConnectivityManager;
55     @VisibleForTesting
56     OnStartTetheringCallback mOnStartTetheringCallback;
57     @VisibleForTesting
58     long mPreviousFunction;
59 
UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment, UsbBackend backend)60     public UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment,
61             UsbBackend backend) {
62         super(context, fragment, backend);
63         mConnectivityManager = context.getSystemService(ConnectivityManager.class);
64         mOnStartTetheringCallback = new OnStartTetheringCallback();
65         mPreviousFunction = mUsbBackend.getCurrentFunctions();
66     }
67 
68     @Override
displayPreference(PreferenceScreen screen)69     public void displayPreference(PreferenceScreen screen) {
70         super.displayPreference(screen);
71         mProfilesContainer = screen.findPreference(getPreferenceKey());
72     }
73 
74     /**
75      * Gets a switch preference for the particular option, creating it if needed.
76      */
getProfilePreference(String key, int titleId)77     private RadioButtonPreference getProfilePreference(String key, int titleId) {
78         RadioButtonPreference pref = mProfilesContainer.findPreference(key);
79         if (pref == null) {
80             pref = new RadioButtonPreference(mProfilesContainer.getContext());
81             pref.setKey(key);
82             pref.setTitle(titleId);
83             pref.setOnClickListener(this);
84             mProfilesContainer.addPreference(pref);
85         }
86         return pref;
87     }
88 
89     @Override
refresh(boolean connected, long functions, int powerRole, int dataRole)90     protected void refresh(boolean connected, long functions, int powerRole, int dataRole) {
91         if (!connected || dataRole != DATA_ROLE_DEVICE) {
92             mProfilesContainer.setEnabled(false);
93         } else {
94             // Functions are only available in device mode
95             mProfilesContainer.setEnabled(true);
96         }
97         RadioButtonPreference pref;
98         for (long option : FUNCTIONS_MAP.keySet()) {
99             int title = FUNCTIONS_MAP.get(option);
100             pref = getProfilePreference(UsbBackend.usbFunctionsToString(option), title);
101             // Only show supported options
102             if (mUsbBackend.areFunctionsSupported(option)) {
103                 pref.setChecked(functions == option);
104             } else {
105                 mProfilesContainer.removePreference(pref);
106             }
107         }
108     }
109 
110     @Override
onRadioButtonClicked(RadioButtonPreference preference)111     public void onRadioButtonClicked(RadioButtonPreference preference) {
112         final long function = UsbBackend.usbFunctionsFromString(preference.getKey());
113         final long previousFunction = mUsbBackend.getCurrentFunctions();
114         if (function != previousFunction && !Utils.isMonkeyRunning()) {
115             mPreviousFunction = previousFunction;
116 
117             //Update the UI in advance to make it looks smooth
118             final RadioButtonPreference prevPref =
119                     (RadioButtonPreference) mProfilesContainer.findPreference(
120                             UsbBackend.usbFunctionsToString(mPreviousFunction));
121             if (prevPref != null) {
122                 prevPref.setChecked(false);
123                 preference.setChecked(true);
124             }
125 
126             if (function == UsbManager.FUNCTION_RNDIS) {
127                 // We need to have entitlement check for usb tethering, so use API in
128                 // ConnectivityManager.
129                 mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */,
130                         mOnStartTetheringCallback);
131             } else {
132                 mUsbBackend.setCurrentFunctions(function);
133             }
134         }
135     }
136 
137     @Override
isAvailable()138     public boolean isAvailable() {
139         return !Utils.isMonkeyRunning();
140     }
141 
142     @Override
getPreferenceKey()143     public String getPreferenceKey() {
144         return "usb_details_functions";
145     }
146 
147     @VisibleForTesting
148     final class OnStartTetheringCallback extends
149             ConnectivityManager.OnStartTetheringCallback {
150 
151         @Override
onTetheringFailed()152         public void onTetheringFailed() {
153             super.onTetheringFailed();
154             mUsbBackend.setCurrentFunctions(mPreviousFunction);
155         }
156     }
157 }
158