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