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