2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import android.app.Activity;
20 import android.app.AlarmManager;
21 import android.app.PendingIntent;
22 import android.app.Service;
23 import android.app.usage.UsageStatsManager;
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.BluetoothPan;
26 import android.bluetooth.BluetoothProfile;
27 import android.bluetooth.BluetoothProfile.ServiceListener;
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.SharedPreferences;
33 import android.content.pm.PackageManager;
34 import android.content.pm.ResolveInfo;
35 import android.net.ConnectivityManager;
36 import android.os.IBinder;
37 import android.os.ResultReceiver;
38 import android.os.SystemClock;
39 import android.text.TextUtils;
40 import android.util.ArrayMap;
41 import android.util.Log;
43 import com.android.internal.annotations.VisibleForTesting;
45 import java.util.ArrayList;
46 import java.util.List;
48 public class TetherService extends Service {
49 private static final String TAG = "TetherService";
50 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
53 public static final String EXTRA_RESULT = "EntitlementResult";
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;
60 private static final String TETHER_CHOICE = "TETHER_TYPE";
61 private static final int MS_PER_HOUR = 60 * 60 * 1000;
63 private static final String PREFS = "tetherPrefs";
64 private static final String KEY_TETHERS = "currentTethers";
66 private int mCurrentTypeIndex;
67 private boolean mInProvisionCheck;
68 private UsageStatsManagerWrapper mUsageManagerWrapper;
69 private ArrayList<Integer> mCurrentTethers;
70 private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
71 private HotspotOffReceiver mHotspotReceiver;
74 public IBinder onBind(Intent intent) {
79 public void onCreate() {
81 if (DEBUG) Log.d(TAG, "Creating TetherService");
82 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;
89 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>());
94 if (mUsageManagerWrapper == null) {
95 mUsageManagerWrapper = new UsageStatsManagerWrapper(this);
97 mHotspotReceiver = new HotspotOffReceiver(this);
101 public int onStartCommand(Intent intent, int flags, int startId) {
102 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);
112 // Invalid tether type. Just ignore this request and report failure.
113 callback.send(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, null);
115 return START_NOT_STICKY;
119 if (!mCurrentTethers.contains(type)) {
120 if (DEBUG) Log.d(TAG, "Adding tether " + type);
121 mCurrentTethers.add(type);
125 if (intent.hasExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE)) {
126 if (!mInProvisionCheck) {
127 int type = intent.getIntExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE,
128 ConnectivityManager.TETHERING_INVALID);
129 int index = mCurrentTethers.indexOf(type);
130 if (DEBUG) Log.d(TAG, "Removing tether " + type + ", index " + index);
132 removeTypeAtIndex(index);
134 cancelAlarmIfNecessary();
136 if (DEBUG) Log.d(TAG, "Don't cancel alarm during provisioning");
140 // 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
143 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_SET_ALARM, false)
144 && mCurrentTethers.size() == 1) {
148 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_RUN_PROVISION, false)) {
149 startProvisioning(mCurrentTypeIndex);
150 } else if (!mInProvisionCheck) {
151 // If we aren't running any provisioning, no reason to stay alive.
152 if (DEBUG) Log.d(TAG, "Stopping self. startid: " + startId);
154 return START_NOT_STICKY;
156 // We want to be started if we are killed accidently, so that we can be sure we finish
158 return START_REDELIVER_INTENT;
162 public void onDestroy() {
163 if (mInProvisionCheck) {
164 Log.e(TAG, "TetherService getting destroyed while mid-provisioning"
165 + mCurrentTethers.get(mCurrentTypeIndex));
167 SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
168 prefs.edit().putString(KEY_TETHERS, tethersToString(mCurrentTethers)).commit();
170 if (DEBUG) Log.d(TAG, "Destroying TetherService");
171 unregisterReceiver(mReceiver);
175 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) {
186 void setHotspotOffReceiver(HotspotOffReceiver receiver) {
187 mHotspotReceiver = receiver;
190 private ArrayList<Integer> stringToTethers(String tethersStr) {
191 ArrayList<Integer> ret = new ArrayList<Integer>();
192 if (TextUtils.isEmpty(tethersStr)) return ret;
194 String[] tethersSplit = tethersStr.split(",");
195 for (int i = 0; i < tethersSplit.length; i++) {
196 ret.add(Integer.parseInt(tethersSplit[i]));
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++) {
208 buffer.append(tethers.get(i));
211 return buffer.toString();
214 private void disableWifiTethering() {
215 ConnectivityManager cm =
216 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
217 cm.stopTethering(ConnectivityManager.TETHERING_WIFI);
220 private void disableUsbTethering() {
221 ConnectivityManager cm =
222 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
223 cm.setUsbTethering(false);
226 private void disableBtTethering() {
227 final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
228 if (adapter != null) {
229 adapter.getProfileProxy(this, new ServiceListener() {
231 public void onServiceDisconnected(int profile) { }
234 public void onServiceConnected(int profile, BluetoothProfile proxy) {
235 ((BluetoothPan) proxy).setBluetoothTethering(false);
236 adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
238 }, BluetoothProfile.PAN);
242 private void startProvisioning(int index) {
243 if (index < mCurrentTethers.size()) {
244 Intent intent = getProvisionBroadcastIntent(index);
245 setEntitlementAppActive(index);
247 if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
248 + " type: " + mCurrentTethers.get(index));
250 sendBroadcast(intent);
251 mInProvisionCheck = true;
255 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);
261 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
262 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
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.");
277 for (ResolveInfo resolver : resolvers) {
278 if (resolver.activityInfo.applicationInfo.isSystemApp()) {
279 String packageName = resolver.activityInfo.packageName;
280 mUsageManagerWrapper.setAppInactive(packageName, false);
286 void scheduleAlarm() {
287 Intent intent = new Intent(this, TetherService.class);
288 intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
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,
299 mHotspotReceiver.register();
303 * Cancels the recheck alarm only if no tethering is currently active.
305 * Runs in the background, to get access to bluetooth service that takes time to bind.
307 public static void cancelRecheckAlarmIfNecessary(final Context context, int type) {
308 Intent intent = new Intent(context, TetherService.class);
309 intent.putExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE, type);
310 context.startService(intent);
314 void cancelAlarmIfNecessary() {
315 if (mCurrentTethers.size() != 0) {
316 if (DEBUG) Log.d(TAG, "Tethering still active, not cancelling alarm");
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");
324 mHotspotReceiver.unregister();
327 private void fireCallbacksForType(int type, int result) {
328 List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
329 if (callbacksForType == null) {
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);
338 callbacksForType.clear();
341 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
343 public void onReceive(Context context, Intent intent) {
344 if (DEBUG) Log.d(TAG, "Got provision result " + intent);
345 String provisionResponse = getResources().getString(
346 com.android.internal.R.string.config_mobile_hotspot_provision_response);
348 if (provisionResponse.equals(intent.getAction())) {
349 if (!mInProvisionCheck) {
350 Log.e(TAG, "Unexpected provision response " + intent);
353 int checkType = mCurrentTethers.get(mCurrentTypeIndex);
354 mInProvisionCheck = false;
355 int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
356 if (result != RESULT_OK) {
358 case ConnectivityManager.TETHERING_WIFI:
359 disableWifiTethering();
361 case ConnectivityManager.TETHERING_BLUETOOTH:
362 disableBtTethering();
364 case ConnectivityManager.TETHERING_USB:
365 disableUsbTethering();
369 fireCallbacksForType(checkType, result);
371 if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
372 // We are done with all checks, time to die.
375 // Start the next check in our list.
376 startProvisioning(mCurrentTypeIndex);
383 void setUsageStatsManagerWrapper(UsageStatsManagerWrapper wrapper) {
384 mUsageManagerWrapper = wrapper;
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.
392 public static class UsageStatsManagerWrapper {
393 private final UsageStatsManager mUsageStatsManager;
395 UsageStatsManagerWrapper(Context context) {
396 mUsageStatsManager = (UsageStatsManager)
397 context.getSystemService(Context.USAGE_STATS_SERVICE);
400 void setAppInactive(String packageName, boolean isInactive) {
401 mUsageStatsManager.setAppInactive(packageName, isInactive);