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