• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
21 import android.content.Context;
22 import android.hardware.usb.UsbManager;
23 import android.net.TetheringManager;
24 import android.os.Handler;
25 import android.os.HandlerExecutor;
26 import android.util.Log;
27 
28 import androidx.annotation.VisibleForTesting;
29 import androidx.preference.PreferenceCategory;
30 import androidx.preference.PreferenceScreen;
31 
32 import com.android.settings.R;
33 import com.android.settings.Utils;
34 import com.android.settings.flags.Flags;
35 import com.android.settingslib.widget.SelectorWithWidgetPreference;
36 
37 import java.util.LinkedHashMap;
38 import java.util.Map;
39 
40 /**
41  * This class controls the radio buttons for choosing between different USB functions.
42  */
43 public class UsbDetailsFunctionsController extends UsbDetailsController
44         implements SelectorWithWidgetPreference.OnClickListener {
45 
46     private static final String TAG = "UsbFunctionsCtrl";
47     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
48 
49     static final Map<Long, Integer> FUNCTIONS_MAP = new LinkedHashMap<>();
50 
51     static {
FUNCTIONS_MAP.put(UsbManager.FUNCTION_MTP, R.string.usb_use_file_transfers)52         FUNCTIONS_MAP.put(UsbManager.FUNCTION_MTP, R.string.usb_use_file_transfers);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_RNDIS, R.string.usb_use_tethering)53         FUNCTIONS_MAP.put(UsbManager.FUNCTION_RNDIS, R.string.usb_use_tethering);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_MIDI, R.string.usb_use_MIDI)54         FUNCTIONS_MAP.put(UsbManager.FUNCTION_MIDI, R.string.usb_use_MIDI);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_PTP, R.string.usb_use_photo_transfers)55         FUNCTIONS_MAP.put(UsbManager.FUNCTION_PTP, R.string.usb_use_photo_transfers);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_UVC, R.string.usb_use_uvc_webcam)56         FUNCTIONS_MAP.put(UsbManager.FUNCTION_UVC, R.string.usb_use_uvc_webcam);
FUNCTIONS_MAP.put(UsbManager.FUNCTION_NONE, R.string.usb_use_charging_only)57         FUNCTIONS_MAP.put(UsbManager.FUNCTION_NONE, R.string.usb_use_charging_only);
58     }
59 
60     private PreferenceCategory mProfilesContainer;
61     private TetheringManager mTetheringManager;
62     private Handler mHandler;
63     @VisibleForTesting
64     OnStartTetheringCallback mOnStartTetheringCallback;
65     @VisibleForTesting
66     long mPreviousFunction;
67 
UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment, UsbBackend backend)68     public UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment,
69             UsbBackend backend) {
70         super(context, fragment, backend);
71         mTetheringManager = context.getSystemService(TetheringManager.class);
72         mOnStartTetheringCallback = new OnStartTetheringCallback();
73         mPreviousFunction = mUsbBackend.getCurrentFunctions();
74         mHandler = new Handler(context.getMainLooper());
75     }
76 
77     @Override
displayPreference(PreferenceScreen screen)78     public void displayPreference(PreferenceScreen screen) {
79         super.displayPreference(screen);
80         mProfilesContainer = screen.findPreference(getPreferenceKey());
81         refresh(/* connected */ false, /* functions */ mUsbBackend.getDefaultUsbFunctions(),
82                 /* powerRole */ 0, /* dataRole */ 0);
83     }
84 
85     /**
86      * Gets a switch preference for the particular option, creating it if needed.
87      */
getProfilePreference(String key, int titleId)88     private SelectorWithWidgetPreference getProfilePreference(String key, int titleId) {
89         SelectorWithWidgetPreference pref = mProfilesContainer.findPreference(key);
90         if (pref == null) {
91             pref = new SelectorWithWidgetPreference(mProfilesContainer.getContext());
92             pref.setKey(key);
93             pref.setTitle(titleId);
94             pref.setSingleLineTitle(false);
95             pref.setOnClickListener(this);
96             mProfilesContainer.addPreference(pref);
97         }
98         return pref;
99     }
100 
101     @Override
refresh(boolean connected, long functions, int powerRole, int dataRole)102     protected void refresh(boolean connected, long functions, int powerRole, int dataRole) {
103         if (DEBUG) {
104             Log.d(TAG, "refresh() connected : " + connected + ", functions : " + functions
105                     + ", powerRole : " + powerRole + ", dataRole : " + dataRole);
106         }
107         if (!connected || dataRole != DATA_ROLE_DEVICE) {
108             mProfilesContainer.setEnabled(false);
109         } else {
110             // Functions are only available in device mode
111             mProfilesContainer.setEnabled(true);
112         }
113         SelectorWithWidgetPreference pref;
114         for (long option : FUNCTIONS_MAP.keySet()) {
115             int title = FUNCTIONS_MAP.get(option);
116             pref = getProfilePreference(UsbBackend.usbFunctionsToString(option), title);
117             // Only show supported options
118             if (mUsbBackend.areFunctionsSupported(option)) {
119                 if (isAccessoryMode(functions)) {
120                     pref.setChecked(UsbManager.FUNCTION_MTP == option);
121                 } else if (functions == UsbManager.FUNCTION_NCM) {
122                     pref.setChecked(UsbManager.FUNCTION_RNDIS == option);
123                 } else {
124                     pref.setChecked(functions == option);
125                 }
126             } else {
127                 mProfilesContainer.removePreference(pref);
128             }
129         }
130     }
131 
132     @Override
onRadioButtonClicked(SelectorWithWidgetPreference preference)133     public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
134         final long function = UsbBackend.usbFunctionsFromString(preference.getKey());
135         if (isAuthRequired(function)) {
136             requireAuthAndExecute(()->handleRadioButtonClicked(preference, function));
137         } else {
138             handleRadioButtonClicked(preference, function);
139         }
140     }
141 
handleRadioButtonClicked(SelectorWithWidgetPreference preference, long function)142     private void handleRadioButtonClicked(SelectorWithWidgetPreference preference, long function) {
143         final long previousFunction = mUsbBackend.getCurrentFunctions();
144         if (DEBUG) {
145             Log.d(TAG, "onRadioButtonClicked() function : " + function + ", toString() : "
146                     + UsbManager.usbFunctionsToString(function) + ", previousFunction : "
147                     + previousFunction + ", toString() : "
148                     + UsbManager.usbFunctionsToString(previousFunction));
149         }
150         if (function != previousFunction && !Utils.isMonkeyRunning()
151                 && !isClickEventIgnored(function, previousFunction)) {
152             mPreviousFunction = previousFunction;
153 
154             //Update the UI in advance to make it looks smooth
155             final SelectorWithWidgetPreference prevPref =
156                     (SelectorWithWidgetPreference) mProfilesContainer.findPreference(
157                             UsbBackend.usbFunctionsToString(mPreviousFunction));
158             if (prevPref != null) {
159                 prevPref.setChecked(false);
160                 preference.setChecked(true);
161             }
162 
163             if (function == UsbManager.FUNCTION_RNDIS || function == UsbManager.FUNCTION_NCM) {
164                 // We need to have entitlement check for usb tethering, so use API in
165                 // TetheringManager.
166                 mTetheringManager.startTethering(
167                         TetheringManager.TETHERING_USB, new HandlerExecutor(mHandler),
168                         mOnStartTetheringCallback);
169             } else {
170                 mUsbBackend.setCurrentFunctions(function);
171             }
172         }
173     }
174 
isAuthRequired(long function)175     private boolean isAuthRequired(long function) {
176         if (!Flags.excludeWebcamAuthChallenge()) {
177             return true;
178         }
179         // Since webcam and MIDI don't transfer any persistent data over USB
180         // don't require authentication.
181         return !(function == UsbManager.FUNCTION_UVC || function == UsbManager.FUNCTION_MIDI);
182     }
183 
isClickEventIgnored(long function, long previousFunction)184     private boolean isClickEventIgnored(long function, long previousFunction) {
185         return isAccessoryMode(previousFunction) && function == UsbManager.FUNCTION_MTP;
186     }
187 
isAccessoryMode(long function)188     private boolean isAccessoryMode(long function) {
189         return (function & UsbManager.FUNCTION_ACCESSORY) != 0;
190     }
191 
192     @Override
isAvailable()193     public boolean isAvailable() {
194         return !Utils.isMonkeyRunning();
195     }
196 
197     @Override
getPreferenceKey()198     public String getPreferenceKey() {
199         return "usb_details_functions";
200     }
201 
202     @VisibleForTesting
203     final class OnStartTetheringCallback implements TetheringManager.StartTetheringCallback {
204 
205         @Override
onTetheringFailed(int error)206         public void onTetheringFailed(int error) {
207             Log.w(TAG, "onTetheringFailed() error : " + error);
208             mUsbBackend.setCurrentFunctions(mPreviousFunction);
209         }
210     }
211 }
212