blob: fce3f27423989bc4fac35857f9f9d0e316819a5e [file] [log] [blame]
Jason Monk37832d62014-12-10 17:21:51 -05001/*
2 * Copyright (C) 2008 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
17package com.android.settings;
18
19import android.app.Activity;
20import android.app.AlarmManager;
21import android.app.PendingIntent;
22import android.app.Service;
Jeremy Kleinb04fae22016-07-29 19:22:21 -070023import android.app.usage.UsageStatsManager;
Jason Monk37832d62014-12-10 17:21:51 -050024import android.bluetooth.BluetoothAdapter;
25import android.bluetooth.BluetoothPan;
26import android.bluetooth.BluetoothProfile;
27import android.bluetooth.BluetoothProfile.ServiceListener;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.SharedPreferences;
Jeremy Kleinb04fae22016-07-29 19:22:21 -070033import android.content.pm.PackageManager;
34import android.content.pm.ResolveInfo;
Jason Monk37832d62014-12-10 17:21:51 -050035import android.net.ConnectivityManager;
Jason Monk37832d62014-12-10 17:21:51 -050036import android.os.IBinder;
Jeremy Klein8a934762016-01-22 16:14:05 -080037import android.os.ResultReceiver;
Jason Monk37832d62014-12-10 17:21:51 -050038import android.os.SystemClock;
39import android.text.TextUtils;
Jeremy Klein8a934762016-01-22 16:14:05 -080040import android.util.ArrayMap;
Jason Monk37832d62014-12-10 17:21:51 -050041import android.util.Log;
42
Jeremy Kleinf6b67132016-02-01 18:58:57 -080043import com.android.internal.annotations.VisibleForTesting;
Jason Monk37832d62014-12-10 17:21:51 -050044
45import java.util.ArrayList;
Jeremy Klein8a934762016-01-22 16:14:05 -080046import java.util.List;
Jason Monk37832d62014-12-10 17:21:51 -050047
48public class TetherService extends Service {
49 private static final String TAG = "TetherService";
50 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
51
Jeremy Kleinf6b67132016-02-01 18:58:57 -080052 @VisibleForTesting
53 public static final String EXTRA_RESULT = "EntitlementResult";
Jason Monk37832d62014-12-10 17:21:51 -050054
55 // Activity results to match the activity provision protocol.
56 // Default to something not ok.
57 private static final int RESULT_DEFAULT = Activity.RESULT_CANCELED;
58 private static final int RESULT_OK = Activity.RESULT_OK;
59
60 private static final String TETHER_CHOICE = "TETHER_TYPE";
61 private static final int MS_PER_HOUR = 60 * 60 * 1000;
62
63 private static final String PREFS = "tetherPrefs";
64 private static final String KEY_TETHERS = "currentTethers";
65
66 private int mCurrentTypeIndex;
Jason Monk37832d62014-12-10 17:21:51 -050067 private boolean mInProvisionCheck;
Jeremy Kleinb04fae22016-07-29 19:22:21 -070068 private UsageStatsManagerWrapper mUsageManagerWrapper;
Jason Monk37832d62014-12-10 17:21:51 -050069 private ArrayList<Integer> mCurrentTethers;
Jeremy Klein8a934762016-01-22 16:14:05 -080070 private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
Doris Lingf94401b2017-03-30 14:40:25 -070071 private HotspotOffReceiver mHotspotReceiver;
Jason Monk37832d62014-12-10 17:21:51 -050072
73 @Override
74 public IBinder onBind(Intent intent) {
75 return null;
76 }
77
78 @Override
79 public void onCreate() {
80 super.onCreate();
Jason Monk359170f2015-09-01 13:20:55 -040081 if (DEBUG) Log.d(TAG, "Creating TetherService");
Jason Monk37832d62014-12-10 17:21:51 -050082 String provisionResponse = getResources().getString(
83 com.android.internal.R.string.config_mobile_hotspot_provision_response);
84 registerReceiver(mReceiver, new IntentFilter(provisionResponse),
85 android.Manifest.permission.CONNECTIVITY_INTERNAL, null);
86 SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
87 mCurrentTethers = stringToTethers(prefs.getString(KEY_TETHERS, ""));
88 mCurrentTypeIndex = 0;
Jeremy Klein8a934762016-01-22 16:14:05 -080089 mPendingCallbacks = new ArrayMap<>(3);
90 mPendingCallbacks.put(ConnectivityManager.TETHERING_WIFI, new ArrayList<ResultReceiver>());
91 mPendingCallbacks.put(ConnectivityManager.TETHERING_USB, new ArrayList<ResultReceiver>());
92 mPendingCallbacks.put(
93 ConnectivityManager.TETHERING_BLUETOOTH, new ArrayList<ResultReceiver>());
Jeremy Kleinb04fae22016-07-29 19:22:21 -070094 if (mUsageManagerWrapper == null) {
95 mUsageManagerWrapper = new UsageStatsManagerWrapper(this);
96 }
Doris Lingf94401b2017-03-30 14:40:25 -070097 mHotspotReceiver = new HotspotOffReceiver(this);
Jason Monk37832d62014-12-10 17:21:51 -050098 }
99
100 @Override
101 public int onStartCommand(Intent intent, int flags, int startId) {
Jeremy Klein8a934762016-01-22 16:14:05 -0800102 if (intent.hasExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE)) {
103 int type = intent.getIntExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE,
104 ConnectivityManager.TETHERING_INVALID);
105 ResultReceiver callback =
106 intent.getParcelableExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK);
107 if (callback != null) {
108 List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
109 if (callbacksForType != null) {
110 callbacksForType.add(callback);
111 } else {
112 // Invalid tether type. Just ignore this request and report failure.
113 callback.send(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, null);
114 stopSelf();
115 return START_NOT_STICKY;
116 }
117 }
118
Jason Monk37832d62014-12-10 17:21:51 -0500119 if (!mCurrentTethers.contains(type)) {
120 if (DEBUG) Log.d(TAG, "Adding tether " + type);
121 mCurrentTethers.add(type);
122 }
123 }
Hyejin5565b5c2015-09-10 23:26:53 -0700124
Jeremy Klein8a934762016-01-22 16:14:05 -0800125 if (intent.hasExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE)) {
Hyejin5565b5c2015-09-10 23:26:53 -0700126 if (!mInProvisionCheck) {
Jeremy Klein8a934762016-01-22 16:14:05 -0800127 int type = intent.getIntExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE,
128 ConnectivityManager.TETHERING_INVALID);
Hyejin5565b5c2015-09-10 23:26:53 -0700129 int index = mCurrentTethers.indexOf(type);
130 if (DEBUG) Log.d(TAG, "Removing tether " + type + ", index " + index);
131 if (index >= 0) {
Jeremy Kleine3e7b952016-01-25 14:43:49 -0800132 removeTypeAtIndex(index);
Jason Monk37832d62014-12-10 17:21:51 -0500133 }
Hyejin5565b5c2015-09-10 23:26:53 -0700134 cancelAlarmIfNecessary();
135 } else {
136 if (DEBUG) Log.d(TAG, "Don't cancel alarm during provisioning");
Jason Monk37832d62014-12-10 17:21:51 -0500137 }
Jason Monk37832d62014-12-10 17:21:51 -0500138 }
Hyejin5565b5c2015-09-10 23:26:53 -0700139
Jason Monk37832d62014-12-10 17:21:51 -0500140 // Only set the alarm if we have one tether, meaning the one just added,
141 // to avoid setting it when it was already set previously for another
142 // type.
Jeremy Klein8a934762016-01-22 16:14:05 -0800143 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_SET_ALARM, false)
Jason Monk37832d62014-12-10 17:21:51 -0500144 && mCurrentTethers.size() == 1) {
145 scheduleAlarm();
146 }
147
Jeremy Klein8a934762016-01-22 16:14:05 -0800148 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_RUN_PROVISION, false)) {
Jason Monk37832d62014-12-10 17:21:51 -0500149 startProvisioning(mCurrentTypeIndex);
150 } else if (!mInProvisionCheck) {
151 // If we aren't running any provisioning, no reason to stay alive.
Jeremy Klein8a934762016-01-22 16:14:05 -0800152 if (DEBUG) Log.d(TAG, "Stopping self. startid: " + startId);
Jason Monk37832d62014-12-10 17:21:51 -0500153 stopSelf();
154 return START_NOT_STICKY;
155 }
156 // We want to be started if we are killed accidently, so that we can be sure we finish
157 // the check.
Jason Monk09ea9722015-09-03 12:48:25 -0400158 return START_REDELIVER_INTENT;
Jason Monk37832d62014-12-10 17:21:51 -0500159 }
160
161 @Override
162 public void onDestroy() {
163 if (mInProvisionCheck) {
164 Log.e(TAG, "TetherService getting destroyed while mid-provisioning"
165 + mCurrentTethers.get(mCurrentTypeIndex));
166 }
167 SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
168 prefs.edit().putString(KEY_TETHERS, tethersToString(mCurrentTethers)).commit();
169
Jason Monk359170f2015-09-01 13:20:55 -0400170 if (DEBUG) Log.d(TAG, "Destroying TetherService");
Jason Monk37832d62014-12-10 17:21:51 -0500171 unregisterReceiver(mReceiver);
172 super.onDestroy();
173 }
174
Jeremy Kleine3e7b952016-01-25 14:43:49 -0800175 private void removeTypeAtIndex(int index) {
176 mCurrentTethers.remove(index);
177 // If we are currently in the middle of a check, we may need to adjust the
178 // index accordingly.
179 if (DEBUG) Log.d(TAG, "mCurrentTypeIndex: " + mCurrentTypeIndex);
180 if (index <= mCurrentTypeIndex && mCurrentTypeIndex > 0) {
181 mCurrentTypeIndex--;
182 }
183 }
184
Doris Lingf94401b2017-03-30 14:40:25 -0700185 @VisibleForTesting
186 void setHotspotOffReceiver(HotspotOffReceiver receiver) {
187 mHotspotReceiver = receiver;
188 }
189
Jason Monk37832d62014-12-10 17:21:51 -0500190 private ArrayList<Integer> stringToTethers(String tethersStr) {
191 ArrayList<Integer> ret = new ArrayList<Integer>();
192 if (TextUtils.isEmpty(tethersStr)) return ret;
193
194 String[] tethersSplit = tethersStr.split(",");
195 for (int i = 0; i < tethersSplit.length; i++) {
196 ret.add(Integer.parseInt(tethersSplit[i]));
197 }
198 return ret;
199 }
200
201 private String tethersToString(ArrayList<Integer> tethers) {
202 final StringBuffer buffer = new StringBuffer();
203 final int N = tethers.size();
204 for (int i = 0; i < N; i++) {
205 if (i != 0) {
206 buffer.append(',');
207 }
208 buffer.append(tethers.get(i));
209 }
210
211 return buffer.toString();
212 }
213
Jason Monk37832d62014-12-10 17:21:51 -0500214 private void disableWifiTethering() {
Christopher Wiley9d251272016-07-18 11:16:15 -0700215 ConnectivityManager cm =
216 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
217 cm.stopTethering(ConnectivityManager.TETHERING_WIFI);
Jason Monk37832d62014-12-10 17:21:51 -0500218 }
219
220 private void disableUsbTethering() {
221 ConnectivityManager cm =
222 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
223 cm.setUsbTethering(false);
224 }
225
226 private void disableBtTethering() {
227 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
228 if (adapter != null) {
229 adapter.getProfileProxy(this, new ServiceListener() {
230 @Override
231 public void onServiceDisconnected(int profile) { }
232
233 @Override
234 public void onServiceConnected(int profile, BluetoothProfile proxy) {
235 ((BluetoothPan) proxy).setBluetoothTethering(false);
236 adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
237 }
238 }, BluetoothProfile.PAN);
239 }
240 }
241
242 private void startProvisioning(int index) {
Hyejin5565b5c2015-09-10 23:26:53 -0700243 if (index < mCurrentTethers.size()) {
Jeremy Kleinb04fae22016-07-29 19:22:21 -0700244 Intent intent = getProvisionBroadcastIntent(index);
245 setEntitlementAppActive(index);
246
247 if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
248 + " type: " + mCurrentTethers.get(index));
Jeremy Kleine3e7b952016-01-25 14:43:49 -0800249
Hyejin5565b5c2015-09-10 23:26:53 -0700250 sendBroadcast(intent);
251 mInProvisionCheck = true;
252 }
Jason Monk37832d62014-12-10 17:21:51 -0500253 }
254
Jeremy Kleinb04fae22016-07-29 19:22:21 -0700255 private Intent getProvisionBroadcastIntent(int index) {
256 String provisionAction = getResources().getString(
257 com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
258 Intent intent = new Intent(provisionAction);
259 int type = mCurrentTethers.get(index);
260 intent.putExtra(TETHER_CHOICE, type);
Christopher Tate9ea3dcb2017-02-22 11:10:10 -0800261 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
262 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Jeremy Kleinb04fae22016-07-29 19:22:21 -0700263
264 return intent;
265 }
266
267 private void setEntitlementAppActive(int index) {
268 final PackageManager packageManager = getPackageManager();
269 Intent intent = getProvisionBroadcastIntent(index);
270 List<ResolveInfo> resolvers =
271 packageManager.queryBroadcastReceivers(intent, PackageManager.MATCH_ALL);
272 if (resolvers.isEmpty()) {
273 Log.e(TAG, "No found BroadcastReceivers for provision intent.");
274 return;
275 }
276
277 for (ResolveInfo resolver : resolvers) {
278 if (resolver.activityInfo.applicationInfo.isSystemApp()) {
279 String packageName = resolver.activityInfo.packageName;
280 mUsageManagerWrapper.setAppInactive(packageName, false);
281 }
282 }
283 }
284
Doris Lingf94401b2017-03-30 14:40:25 -0700285 @VisibleForTesting
286 void scheduleAlarm() {
Jason Monk37832d62014-12-10 17:21:51 -0500287 Intent intent = new Intent(this, TetherService.class);
Jeremy Klein8a934762016-01-22 16:14:05 -0800288 intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
Jason Monk37832d62014-12-10 17:21:51 -0500289
290 PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
291 AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
292 int period = getResources().getInteger(
293 com.android.internal.R.integer.config_mobile_hotspot_provision_check_period);
294 long periodMs = period * MS_PER_HOUR;
295 long firstTime = SystemClock.elapsedRealtime() + periodMs;
296 if (DEBUG) Log.d(TAG, "Scheduling alarm at interval " + periodMs);
297 alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstTime, periodMs,
298 pendingIntent);
Doris Lingf94401b2017-03-30 14:40:25 -0700299 mHotspotReceiver.register();
Jason Monk37832d62014-12-10 17:21:51 -0500300 }
301
302 /**
303 * Cancels the recheck alarm only if no tethering is currently active.
304 *
305 * Runs in the background, to get access to bluetooth service that takes time to bind.
306 */
307 public static void cancelRecheckAlarmIfNecessary(final Context context, int type) {
308 Intent intent = new Intent(context, TetherService.class);
Jeremy Klein8a934762016-01-22 16:14:05 -0800309 intent.putExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE, type);
Jason Monk37832d62014-12-10 17:21:51 -0500310 context.startService(intent);
311 }
312
Doris Lingf94401b2017-03-30 14:40:25 -0700313 @VisibleForTesting
314 void cancelAlarmIfNecessary() {
Jason Monk37832d62014-12-10 17:21:51 -0500315 if (mCurrentTethers.size() != 0) {
316 if (DEBUG) Log.d(TAG, "Tethering still active, not cancelling alarm");
317 return;
318 }
319 Intent intent = new Intent(this, TetherService.class);
320 PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
321 AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
322 alarmManager.cancel(pendingIntent);
323 if (DEBUG) Log.d(TAG, "Tethering no longer active, canceling recheck");
Doris Lingf94401b2017-03-30 14:40:25 -0700324 mHotspotReceiver.unregister();
Jason Monk37832d62014-12-10 17:21:51 -0500325 }
326
Jeremy Klein8a934762016-01-22 16:14:05 -0800327 private void fireCallbacksForType(int type, int result) {
328 List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
329 if (callbacksForType == null) {
330 return;
331 }
332 int errorCode = result == RESULT_OK ? ConnectivityManager.TETHER_ERROR_NO_ERROR :
333 ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
334 for (ResultReceiver callback : callbacksForType) {
335 if (DEBUG) Log.d(TAG, "Firing result: " + errorCode + " to callback");
336 callback.send(errorCode, null);
337 }
338 callbacksForType.clear();
339 }
340
Jason Monk37832d62014-12-10 17:21:51 -0500341 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
342 @Override
343 public void onReceive(Context context, Intent intent) {
344 if (DEBUG) Log.d(TAG, "Got provision result " + intent);
Jeremy Kleinf6b67132016-02-01 18:58:57 -0800345 String provisionResponse = getResources().getString(
Jason Monk37832d62014-12-10 17:21:51 -0500346 com.android.internal.R.string.config_mobile_hotspot_provision_response);
Hyejin5565b5c2015-09-10 23:26:53 -0700347
Jason Monk37832d62014-12-10 17:21:51 -0500348 if (provisionResponse.equals(intent.getAction())) {
Jason Monk359170f2015-09-01 13:20:55 -0400349 if (!mInProvisionCheck) {
350 Log.e(TAG, "Unexpected provision response " + intent);
351 return;
352 }
Jason Monk37832d62014-12-10 17:21:51 -0500353 int checkType = mCurrentTethers.get(mCurrentTypeIndex);
Hyejin5565b5c2015-09-10 23:26:53 -0700354 mInProvisionCheck = false;
Jeremy Klein8a934762016-01-22 16:14:05 -0800355 int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
356 if (result != RESULT_OK) {
Jason Monk37832d62014-12-10 17:21:51 -0500357 switch (checkType) {
Jeremy Klein8a934762016-01-22 16:14:05 -0800358 case ConnectivityManager.TETHERING_WIFI:
Jason Monk37832d62014-12-10 17:21:51 -0500359 disableWifiTethering();
360 break;
Jeremy Klein8a934762016-01-22 16:14:05 -0800361 case ConnectivityManager.TETHERING_BLUETOOTH:
Jason Monk37832d62014-12-10 17:21:51 -0500362 disableBtTethering();
363 break;
Jeremy Klein8a934762016-01-22 16:14:05 -0800364 case ConnectivityManager.TETHERING_USB:
Jason Monk37832d62014-12-10 17:21:51 -0500365 disableUsbTethering();
366 break;
367 }
368 }
Jeremy Klein8a934762016-01-22 16:14:05 -0800369 fireCallbacksForType(checkType, result);
Hyejin5565b5c2015-09-10 23:26:53 -0700370
371 if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
Jason Monk37832d62014-12-10 17:21:51 -0500372 // We are done with all checks, time to die.
373 stopSelf();
374 } else {
375 // Start the next check in our list.
376 startProvisioning(mCurrentTypeIndex);
377 }
378 }
379 }
380 };
381
Jeremy Kleinb04fae22016-07-29 19:22:21 -0700382 @VisibleForTesting
383 void setUsageStatsManagerWrapper(UsageStatsManagerWrapper wrapper) {
384 mUsageManagerWrapper = wrapper;
385 }
386
387 /**
388 * A static helper class used for tests. UsageStatsManager cannot be mocked out becasue
389 * it's marked final. This class can be mocked out instead.
390 */
391 @VisibleForTesting
392 public static class UsageStatsManagerWrapper {
393 private final UsageStatsManager mUsageStatsManager;
394
395 UsageStatsManagerWrapper(Context context) {
396 mUsageStatsManager = (UsageStatsManager)
397 context.getSystemService(Context.USAGE_STATS_SERVICE);
398 }
399
400 void setAppInactive(String packageName, boolean isInactive) {
401 mUsageStatsManager.setAppInactive(packageName, isInactive);
402 }
403 }
Jason Monk37832d62014-12-10 17:21:51 -0500404}