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