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.net.TetheringManager.TETHERING_USB;
20 
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.graphics.drawable.Drawable;
24 import android.hardware.usb.UsbManager;
25 import android.net.TetheringManager;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.HandlerExecutor;
29 import android.util.Log;
30 
31 import androidx.annotation.VisibleForTesting;
32 import androidx.preference.PreferenceScreen;
33 
34 import com.android.settings.R;
35 import com.android.settings.Utils;
36 import com.android.settings.development.DeveloperOptionAwareMixin;
37 import com.android.settings.widget.RadioButtonPickerFragment;
38 import com.android.settingslib.widget.CandidateInfo;
39 import com.android.settingslib.widget.FooterPreference;
40 import com.android.settingslib.widget.SelectorWithWidgetPreference;
41 
42 import com.google.android.collect.Lists;
43 
44 import java.util.List;
45 
46 /**
47  * Provides options for selecting the default USB mode.
48  */
49 public class UsbDefaultFragment extends RadioButtonPickerFragment implements
50         DeveloperOptionAwareMixin {
51 
52     private static final String TAG = "UsbDefaultFragment";
53 
54     @VisibleForTesting
55     UsbBackend mUsbBackend;
56     @VisibleForTesting
57     TetheringManager mTetheringManager;
58     @VisibleForTesting
59     OnStartTetheringCallback mOnStartTetheringCallback = new OnStartTetheringCallback();
60     @VisibleForTesting
61     long mPreviousFunctions;
62     @VisibleForTesting
63     long mCurrentFunctions;
64     @VisibleForTesting
65     boolean mIsStartTethering = false;
66     @VisibleForTesting
67     Handler mHandler;
68 
69     private UsbConnectionBroadcastReceiver mUsbReceiver;
70     private boolean mIsConnected = false;
71 
72     @VisibleForTesting
73     UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
74             (connected, functions, powerRole, dataRole, isUsbConfigured) -> {
75                 final long defaultFunctions = mUsbBackend.getDefaultUsbFunctions();
76                 Log.d(TAG, "UsbConnectionListener() connected : " + connected + ", functions : "
77                         + functions + ", defaultFunctions : " + defaultFunctions
78                         + ", mIsStartTethering : " + mIsStartTethering
79                         + ", isUsbConfigured : " + isUsbConfigured);
80                 if (connected && !mIsConnected && ((defaultFunctions == UsbManager.FUNCTION_RNDIS
81                         || defaultFunctions == UsbManager.FUNCTION_NCM)
82                         && defaultFunctions == functions)
83                         && !mIsStartTethering) {
84                     mCurrentFunctions = defaultFunctions;
85                     startTethering();
86                 }
87 
88                 if ((mIsStartTethering || isUsbConfigured) && connected) {
89                     mCurrentFunctions = functions;
90                     refresh(functions);
91                     mIsStartTethering = false;
92                 }
93                 mIsConnected = connected;
94             };
95 
96     @Override
onAttach(Context context)97     public void onAttach(Context context) {
98         super.onAttach(context);
99         mUsbBackend = new UsbBackend(context);
100         mTetheringManager = context.getSystemService(TetheringManager.class);
101         mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener,
102                 mUsbBackend);
103         mHandler = new Handler(context.getMainLooper());
104         getSettingsLifecycle().addObserver(mUsbReceiver);
105         mCurrentFunctions = mUsbBackend.getDefaultUsbFunctions();
106     }
107 
108     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)109     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
110         super.onCreatePreferences(savedInstanceState, rootKey);
111         getPreferenceScreen().addPreference(new FooterPreference.Builder(getActivity()).setTitle(
112                 R.string.usb_default_info).build());
113     }
114 
115     @Override
getMetricsCategory()116     public int getMetricsCategory() {
117         return SettingsEnums.USB_DEFAULT;
118     }
119 
120     @Override
getPreferenceScreenResId()121     protected int getPreferenceScreenResId() {
122         return R.xml.usb_default_fragment;
123     }
124 
125     @Override
getCandidates()126     protected List<? extends CandidateInfo> getCandidates() {
127         List<CandidateInfo> ret = Lists.newArrayList();
128         for (final long option : UsbDetailsFunctionsController.FUNCTIONS_MAP.keySet()) {
129             final String title = getContext().getString(
130                     UsbDetailsFunctionsController.FUNCTIONS_MAP.get(option));
131             final String key = UsbBackend.usbFunctionsToString(option);
132 
133             // Only show supported functions
134             if (mUsbBackend.areFunctionsSupported(option)) {
135                 ret.add(new CandidateInfo(true /* enabled */) {
136                     @Override
137                     public CharSequence loadLabel() {
138                         return title;
139                     }
140 
141                     @Override
142                     public Drawable loadIcon() {
143                         return null;
144                     }
145 
146                     @Override
147                     public String getKey() {
148                         return key;
149                     }
150                 });
151             }
152         }
153         return ret;
154     }
155 
156     @Override
getDefaultKey()157     protected String getDefaultKey() {
158         long defaultUsbFunctions = mUsbBackend.getDefaultUsbFunctions();
159         // Because we didn't have an option for NCM, so make FUNCTION_NCM corresponding to
160         // FUNCTION_RNDIS for initializing the UI.
161         return UsbBackend.usbFunctionsToString(defaultUsbFunctions == UsbManager.FUNCTION_NCM
162                 ? UsbManager.FUNCTION_RNDIS : defaultUsbFunctions);
163     }
164 
165     @Override
setDefaultKey(String key)166     protected boolean setDefaultKey(String key) {
167         long functions = UsbBackend.usbFunctionsFromString(key);
168         mPreviousFunctions = mUsbBackend.getCurrentFunctions();
169         if (!Utils.isMonkeyRunning()) {
170             if (functions == UsbManager.FUNCTION_RNDIS || functions == UsbManager.FUNCTION_NCM) {
171                 // We need to have entitlement check for usb tethering, so use API in
172                 // TetheringManager.
173                 mCurrentFunctions = functions;
174                 startTethering();
175             } else {
176                 mIsStartTethering = false;
177                 mCurrentFunctions = functions;
178                 mUsbBackend.setDefaultUsbFunctions(functions);
179             }
180 
181         }
182         return true;
183     }
184 
startTethering()185     private void startTethering() {
186         Log.d(TAG, "startTethering()");
187         mIsStartTethering = true;
188         mTetheringManager.startTethering(TETHERING_USB, new HandlerExecutor(mHandler),
189                 mOnStartTetheringCallback);
190     }
191 
192     @Override
onPause()193     public void onPause() {
194         super.onPause();
195         mCurrentFunctions = mUsbBackend.getCurrentFunctions();
196         Log.d(TAG, "onPause() : current functions : " + mCurrentFunctions);
197         mUsbBackend.setDefaultUsbFunctions(mCurrentFunctions);
198     }
199 
200     @VisibleForTesting
201     final class OnStartTetheringCallback implements
202             TetheringManager.StartTetheringCallback {
203 
204         @Override
onTetheringStarted()205         public void onTetheringStarted() {
206             // Set default usb functions again to make internal data persistent
207             mCurrentFunctions = mUsbBackend.getCurrentFunctions();
208             Log.d(TAG, "onTetheringStarted() : mCurrentFunctions " + mCurrentFunctions);
209             mUsbBackend.setDefaultUsbFunctions(mCurrentFunctions);
210         }
211 
212         @Override
onTetheringFailed(int error)213         public void onTetheringFailed(int error) {
214             Log.w(TAG, "onTetheringFailed() error : " + error);
215             mUsbBackend.setDefaultUsbFunctions(mPreviousFunctions);
216             updateCandidates();
217         }
218     }
219 
refresh(long functions)220     private void refresh(long functions) {
221         final PreferenceScreen screen = getPreferenceScreen();
222         for (long option : UsbDetailsFunctionsController.FUNCTIONS_MAP.keySet()) {
223             final SelectorWithWidgetPreference pref =
224                     screen.findPreference(UsbBackend.usbFunctionsToString(option));
225             if (pref != null) {
226                 final boolean isSupported = mUsbBackend.areFunctionsSupported(option);
227                 pref.setEnabled(isSupported);
228                 if (isSupported) {
229                     if (functions == UsbManager.FUNCTION_NCM) {
230                         pref.setChecked(UsbManager.FUNCTION_RNDIS == option);
231                     } else {
232                         pref.setChecked(functions == option);
233                     }
234                 }
235             }
236         }
237     }
238 }