Files
app_Settings/src/com/android/settings/TetherService.java
Doris Ling f94401b06d Remove hotspot receiver from manifest.
The hotspot receiver if used to cancel any tethering alarm set when wifi
ap state changes. Removing it from the manifest, and only register the
receiver when we schedule the tethering alarm.

Change-Id: I97c3be6e2a374949208bbdd4ac5ccc2fabf0291a
Fix: 35968322
Test: make RunSettingsRoboTests
2017-04-11 12:03:54 -07:00

405 lines
16 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.app.usage.UsageStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.ConnectivityManager;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
public class TetherService extends Service {
private static final String TAG = "TetherService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@VisibleForTesting
public static final String EXTRA_RESULT = "EntitlementResult";
// Activity results to match the activity provision protocol.
// Default to something not ok.
private static final int RESULT_DEFAULT = Activity.RESULT_CANCELED;
private static final int RESULT_OK = Activity.RESULT_OK;
private static final String TETHER_CHOICE = "TETHER_TYPE";
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final String PREFS = "tetherPrefs";
private static final String KEY_TETHERS = "currentTethers";
private int mCurrentTypeIndex;
private boolean mInProvisionCheck;
private UsageStatsManagerWrapper mUsageManagerWrapper;
private ArrayList<Integer> mCurrentTethers;
private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
private HotspotOffReceiver mHotspotReceiver;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) Log.d(TAG, "Creating TetherService");
String provisionResponse = getResources().getString(
com.android.internal.R.string.config_mobile_hotspot_provision_response);
registerReceiver(mReceiver, new IntentFilter(provisionResponse),
android.Manifest.permission.CONNECTIVITY_INTERNAL, null);
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
mCurrentTethers = stringToTethers(prefs.getString(KEY_TETHERS, ""));
mCurrentTypeIndex = 0;
mPendingCallbacks = new ArrayMap<>(3);
mPendingCallbacks.put(ConnectivityManager.TETHERING_WIFI, new ArrayList<ResultReceiver>());
mPendingCallbacks.put(ConnectivityManager.TETHERING_USB, new ArrayList<ResultReceiver>());
mPendingCallbacks.put(
ConnectivityManager.TETHERING_BLUETOOTH, new ArrayList<ResultReceiver>());
if (mUsageManagerWrapper == null) {
mUsageManagerWrapper = new UsageStatsManagerWrapper(this);
}
mHotspotReceiver = new HotspotOffReceiver(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.hasExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE)) {
int type = intent.getIntExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE,
ConnectivityManager.TETHERING_INVALID);
ResultReceiver callback =
intent.getParcelableExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK);
if (callback != null) {
List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
if (callbacksForType != null) {
callbacksForType.add(callback);
} else {
// Invalid tether type. Just ignore this request and report failure.
callback.send(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, null);
stopSelf();
return START_NOT_STICKY;
}
}
if (!mCurrentTethers.contains(type)) {
if (DEBUG) Log.d(TAG, "Adding tether " + type);
mCurrentTethers.add(type);
}
}
if (intent.hasExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE)) {
if (!mInProvisionCheck) {
int type = intent.getIntExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE,
ConnectivityManager.TETHERING_INVALID);
int index = mCurrentTethers.indexOf(type);
if (DEBUG) Log.d(TAG, "Removing tether " + type + ", index " + index);
if (index >= 0) {
removeTypeAtIndex(index);
}
cancelAlarmIfNecessary();
} else {
if (DEBUG) Log.d(TAG, "Don't cancel alarm during provisioning");
}
}
// Only set the alarm if we have one tether, meaning the one just added,
// to avoid setting it when it was already set previously for another
// type.
if (intent.getBooleanExtra(ConnectivityManager.EXTRA_SET_ALARM, false)
&& mCurrentTethers.size() == 1) {
scheduleAlarm();
}
if (intent.getBooleanExtra(ConnectivityManager.EXTRA_RUN_PROVISION, false)) {
startProvisioning(mCurrentTypeIndex);
} else if (!mInProvisionCheck) {
// If we aren't running any provisioning, no reason to stay alive.
if (DEBUG) Log.d(TAG, "Stopping self. startid: " + startId);
stopSelf();
return START_NOT_STICKY;
}
// We want to be started if we are killed accidently, so that we can be sure we finish
// the check.
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
if (mInProvisionCheck) {
Log.e(TAG, "TetherService getting destroyed while mid-provisioning"
+ mCurrentTethers.get(mCurrentTypeIndex));
}
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
prefs.edit().putString(KEY_TETHERS, tethersToString(mCurrentTethers)).commit();
if (DEBUG) Log.d(TAG, "Destroying TetherService");
unregisterReceiver(mReceiver);
super.onDestroy();
}
private void removeTypeAtIndex(int index) {
mCurrentTethers.remove(index);
// If we are currently in the middle of a check, we may need to adjust the
// index accordingly.
if (DEBUG) Log.d(TAG, "mCurrentTypeIndex: " + mCurrentTypeIndex);
if (index <= mCurrentTypeIndex && mCurrentTypeIndex > 0) {
mCurrentTypeIndex--;
}
}
@VisibleForTesting
void setHotspotOffReceiver(HotspotOffReceiver receiver) {
mHotspotReceiver = receiver;
}
private ArrayList<Integer> stringToTethers(String tethersStr) {
ArrayList<Integer> ret = new ArrayList<Integer>();
if (TextUtils.isEmpty(tethersStr)) return ret;
String[] tethersSplit = tethersStr.split(",");
for (int i = 0; i < tethersSplit.length; i++) {
ret.add(Integer.parseInt(tethersSplit[i]));
}
return ret;
}
private String tethersToString(ArrayList<Integer> tethers) {
final StringBuffer buffer = new StringBuffer();
final int N = tethers.size();
for (int i = 0; i < N; i++) {
if (i != 0) {
buffer.append(',');
}
buffer.append(tethers.get(i));
}
return buffer.toString();
}
private void disableWifiTethering() {
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
cm.stopTethering(ConnectivityManager.TETHERING_WIFI);
}
private void disableUsbTethering() {
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
cm.setUsbTethering(false);
}
private void disableBtTethering() {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
adapter.getProfileProxy(this, new ServiceListener() {
@Override
public void onServiceDisconnected(int profile) { }
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
((BluetoothPan) proxy).setBluetoothTethering(false);
adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
}
}, BluetoothProfile.PAN);
}
}
private void startProvisioning(int index) {
if (index < mCurrentTethers.size()) {
Intent intent = getProvisionBroadcastIntent(index);
setEntitlementAppActive(index);
if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
+ " type: " + mCurrentTethers.get(index));
sendBroadcast(intent);
mInProvisionCheck = true;
}
}
private Intent getProvisionBroadcastIntent(int index) {
String provisionAction = getResources().getString(
com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
Intent intent = new Intent(provisionAction);
int type = mCurrentTethers.get(index);
intent.putExtra(TETHER_CHOICE, type);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
return intent;
}
private void setEntitlementAppActive(int index) {
final PackageManager packageManager = getPackageManager();
Intent intent = getProvisionBroadcastIntent(index);
List<ResolveInfo> resolvers =
packageManager.queryBroadcastReceivers(intent, PackageManager.MATCH_ALL);
if (resolvers.isEmpty()) {
Log.e(TAG, "No found BroadcastReceivers for provision intent.");
return;
}
for (ResolveInfo resolver : resolvers) {
if (resolver.activityInfo.applicationInfo.isSystemApp()) {
String packageName = resolver.activityInfo.packageName;
mUsageManagerWrapper.setAppInactive(packageName, false);
}
}
}
@VisibleForTesting
void scheduleAlarm() {
Intent intent = new Intent(this, TetherService.class);
intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
int period = getResources().getInteger(
com.android.internal.R.integer.config_mobile_hotspot_provision_check_period);
long periodMs = period * MS_PER_HOUR;
long firstTime = SystemClock.elapsedRealtime() + periodMs;
if (DEBUG) Log.d(TAG, "Scheduling alarm at interval " + periodMs);
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstTime, periodMs,
pendingIntent);
mHotspotReceiver.register();
}
/**
* Cancels the recheck alarm only if no tethering is currently active.
*
* Runs in the background, to get access to bluetooth service that takes time to bind.
*/
public static void cancelRecheckAlarmIfNecessary(final Context context, int type) {
Intent intent = new Intent(context, TetherService.class);
intent.putExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE, type);
context.startService(intent);
}
@VisibleForTesting
void cancelAlarmIfNecessary() {
if (mCurrentTethers.size() != 0) {
if (DEBUG) Log.d(TAG, "Tethering still active, not cancelling alarm");
return;
}
Intent intent = new Intent(this, TetherService.class);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
if (DEBUG) Log.d(TAG, "Tethering no longer active, canceling recheck");
mHotspotReceiver.unregister();
}
private void fireCallbacksForType(int type, int result) {
List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
if (callbacksForType == null) {
return;
}
int errorCode = result == RESULT_OK ? ConnectivityManager.TETHER_ERROR_NO_ERROR :
ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
for (ResultReceiver callback : callbacksForType) {
if (DEBUG) Log.d(TAG, "Firing result: " + errorCode + " to callback");
callback.send(errorCode, null);
}
callbacksForType.clear();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Got provision result " + intent);
String provisionResponse = getResources().getString(
com.android.internal.R.string.config_mobile_hotspot_provision_response);
if (provisionResponse.equals(intent.getAction())) {
if (!mInProvisionCheck) {
Log.e(TAG, "Unexpected provision response " + intent);
return;
}
int checkType = mCurrentTethers.get(mCurrentTypeIndex);
mInProvisionCheck = false;
int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
if (result != RESULT_OK) {
switch (checkType) {
case ConnectivityManager.TETHERING_WIFI:
disableWifiTethering();
break;
case ConnectivityManager.TETHERING_BLUETOOTH:
disableBtTethering();
break;
case ConnectivityManager.TETHERING_USB:
disableUsbTethering();
break;
}
}
fireCallbacksForType(checkType, result);
if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
// We are done with all checks, time to die.
stopSelf();
} else {
// Start the next check in our list.
startProvisioning(mCurrentTypeIndex);
}
}
}
};
@VisibleForTesting
void setUsageStatsManagerWrapper(UsageStatsManagerWrapper wrapper) {
mUsageManagerWrapper = wrapper;
}
/**
* A static helper class used for tests. UsageStatsManager cannot be mocked out becasue
* it's marked final. This class can be mocked out instead.
*/
@VisibleForTesting
public static class UsageStatsManagerWrapper {
private final UsageStatsManager mUsageStatsManager;
UsageStatsManagerWrapper(Context context) {
mUsageStatsManager = (UsageStatsManager)
context.getSystemService(Context.USAGE_STATS_SERVICE);
}
void setAppInactive(String packageName, boolean isInactive) {
mUsageStatsManager.setAppInactive(packageName, isInactive);
}
}
}