Home | History | Annotate | Download | only in tether
      1 /*
      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 
     17 package com.android.settings.wifi.tether;
     18 
     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;
     42 
     43 import com.android.internal.annotations.VisibleForTesting;
     44 
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 public class TetherService extends Service {
     49     private static final String TAG = "TetherService";
     50     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     51 
     52     @VisibleForTesting
     53     public static final String EXTRA_RESULT = "EntitlementResult";
     54 
     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;
     67     private boolean mInProvisionCheck;
     68     private UsageStatsManagerWrapper mUsageManagerWrapper;
     69     private ArrayList<Integer> mCurrentTethers;
     70     private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
     71     private HotspotOffReceiver mHotspotReceiver;
     72 
     73     @Override
     74     public IBinder onBind(Intent intent) {
     75         return null;
     76     }
     77 
     78     @Override
     79     public void onCreate() {
     80         super.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);
     96         }
     97         mHotspotReceiver = new HotspotOffReceiver(this);
     98     }
     99 
    100     @Override
    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);
    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 
    119             if (!mCurrentTethers.contains(type)) {
    120                 if (DEBUG) Log.d(TAG, "Adding tether " + type);
    121                 mCurrentTethers.add(type);
    122             }
    123         }
    124 
    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);
    131                 if (index >= 0) {
    132                     removeTypeAtIndex(index);
    133                 }
    134                 cancelAlarmIfNecessary();
    135             } else {
    136                 if (DEBUG) Log.d(TAG, "Don't cancel alarm during provisioning");
    137             }
    138         }
    139 
    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
    142         // type.
    143         if (intent.getBooleanExtra(ConnectivityManager.EXTRA_SET_ALARM, false)
    144                 && mCurrentTethers.size() == 1) {
    145             scheduleAlarm();
    146         }
    147 
    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);
    153             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.
    158         return START_REDELIVER_INTENT;
    159     }
    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 
    170         unregisterReceivers();
    171         if (DEBUG) Log.d(TAG, "Destroying TetherService");
    172         super.onDestroy();
    173     }
    174 
    175     private void unregisterReceivers() {
    176         unregisterReceiver(mReceiver);
    177         mHotspotReceiver.unregister();
    178     }
    179 
    180     private void removeTypeAtIndex(int index) {
    181         mCurrentTethers.remove(index);
    182         // If we are currently in the middle of a check, we may need to adjust the
    183         // index accordingly.
    184         if (DEBUG) Log.d(TAG, "mCurrentTypeIndex: " + mCurrentTypeIndex);
    185         if (index <= mCurrentTypeIndex && mCurrentTypeIndex > 0) {
    186             mCurrentTypeIndex--;
    187         }
    188     }
    189 
    190     @VisibleForTesting
    191     void setHotspotOffReceiver(HotspotOffReceiver receiver) {
    192         mHotspotReceiver = receiver;
    193     }
    194 
    195     private ArrayList<Integer> stringToTethers(String tethersStr) {
    196         ArrayList<Integer> ret = new ArrayList<Integer>();
    197         if (TextUtils.isEmpty(tethersStr)) return ret;
    198 
    199         String[] tethersSplit = tethersStr.split(",");
    200         for (int i = 0; i < tethersSplit.length; i++) {
    201             ret.add(Integer.parseInt(tethersSplit[i]));
    202         }
    203         return ret;
    204     }
    205 
    206     private String tethersToString(ArrayList<Integer> tethers) {
    207         final StringBuffer buffer = new StringBuffer();
    208         final int N = tethers.size();
    209         for (int i = 0; i < N; i++) {
    210             if (i != 0) {
    211                 buffer.append(',');
    212             }
    213             buffer.append(tethers.get(i));
    214         }
    215 
    216         return buffer.toString();
    217     }
    218 
    219     private void disableWifiTethering() {
    220         ConnectivityManager cm =
    221                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
    222         cm.stopTethering(ConnectivityManager.TETHERING_WIFI);
    223     }
    224 
    225     private void disableUsbTethering() {
    226         ConnectivityManager cm =
    227                 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
    228         cm.setUsbTethering(false);
    229     }
    230 
    231     private void disableBtTethering() {
    232         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    233         if (adapter != null) {
    234             adapter.getProfileProxy(this, new ServiceListener() {
    235                 @Override
    236                 public void onServiceDisconnected(int profile) { }
    237 
    238                 @Override
    239                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
    240                     ((BluetoothPan) proxy).setBluetoothTethering(false);
    241                     adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
    242                 }
    243             }, BluetoothProfile.PAN);
    244         }
    245     }
    246 
    247     private void startProvisioning(int index) {
    248         if (index < mCurrentTethers.size()) {
    249             Intent intent = getProvisionBroadcastIntent(index);
    250             setEntitlementAppActive(index);
    251 
    252             if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
    253                     + " type: " + mCurrentTethers.get(index));
    254 
    255             sendBroadcast(intent);
    256             mInProvisionCheck = true;
    257         }
    258     }
    259 
    260     private Intent getProvisionBroadcastIntent(int index) {
    261         String provisionAction = getResources().getString(
    262                 com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
    263         Intent intent = new Intent(provisionAction);
    264         int type = mCurrentTethers.get(index);
    265         intent.putExtra(TETHER_CHOICE, type);
    266         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
    267                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
    268 
    269         return intent;
    270     }
    271 
    272     private void setEntitlementAppActive(int index) {
    273         final PackageManager packageManager = getPackageManager();
    274         Intent intent = getProvisionBroadcastIntent(index);
    275         List<ResolveInfo> resolvers =
    276                 packageManager.queryBroadcastReceivers(intent, PackageManager.MATCH_ALL);
    277         if (resolvers.isEmpty()) {
    278             Log.e(TAG, "No found BroadcastReceivers for provision intent.");
    279             return;
    280         }
    281 
    282         for (ResolveInfo resolver : resolvers) {
    283             if (resolver.activityInfo.applicationInfo.isSystemApp()) {
    284                 String packageName = resolver.activityInfo.packageName;
    285                 mUsageManagerWrapper.setAppInactive(packageName, false);
    286             }
    287         }
    288     }
    289 
    290     @VisibleForTesting
    291     void scheduleAlarm() {
    292         Intent intent = new Intent(this, TetherService.class);
    293         intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
    294 
    295         PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
    296         AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    297         int period = getResources().getInteger(
    298                 com.android.internal.R.integer.config_mobile_hotspot_provision_check_period);
    299         long periodMs = period * MS_PER_HOUR;
    300         long firstTime = SystemClock.elapsedRealtime() + periodMs;
    301         if (DEBUG) Log.d(TAG, "Scheduling alarm at interval " + periodMs);
    302         alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstTime, periodMs,
    303                 pendingIntent);
    304         mHotspotReceiver.register();
    305     }
    306 
    307     /**
    308      * Cancels the recheck alarm only if no tethering is currently active.
    309      *
    310      * Runs in the background, to get access to bluetooth service that takes time to bind.
    311      */
    312     public static void cancelRecheckAlarmIfNecessary(final Context context, int type) {
    313         Intent intent = new Intent(context, TetherService.class);
    314         intent.putExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE, type);
    315         context.startService(intent);
    316     }
    317 
    318     @VisibleForTesting
    319     void cancelAlarmIfNecessary() {
    320         if (mCurrentTethers.size() != 0) {
    321             if (DEBUG) Log.d(TAG, "Tethering still active, not cancelling alarm");
    322             return;
    323         }
    324         Intent intent = new Intent(this, TetherService.class);
    325         PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, 0);
    326         AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    327         alarmManager.cancel(pendingIntent);
    328         if (DEBUG) Log.d(TAG, "Tethering no longer active, canceling recheck");
    329         mHotspotReceiver.unregister();
    330     }
    331 
    332     private void fireCallbacksForType(int type, int result) {
    333         List<ResultReceiver> callbacksForType = mPendingCallbacks.get(type);
    334         if (callbacksForType == null) {
    335             return;
    336         }
    337         int errorCode = result == RESULT_OK ? ConnectivityManager.TETHER_ERROR_NO_ERROR :
    338                 ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
    339         for (ResultReceiver callback : callbacksForType) {
    340           if (DEBUG) Log.d(TAG, "Firing result: " + errorCode + " to callback");
    341           callback.send(errorCode, null);
    342         }
    343         callbacksForType.clear();
    344     }
    345 
    346     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    347         @Override
    348         public void onReceive(Context context, Intent intent) {
    349             if (DEBUG) Log.d(TAG, "Got provision result " + intent);
    350             String provisionResponse = getResources().getString(
    351                     com.android.internal.R.string.config_mobile_hotspot_provision_response);
    352 
    353             if (provisionResponse.equals(intent.getAction())) {
    354                 if (!mInProvisionCheck) {
    355                     Log.e(TAG, "Unexpected provision response " + intent);
    356                     return;
    357                 }
    358                 int checkType = mCurrentTethers.get(mCurrentTypeIndex);
    359                 mInProvisionCheck = false;
    360                 int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
    361                 if (result != RESULT_OK) {
    362                     switch (checkType) {
    363                         case ConnectivityManager.TETHERING_WIFI:
    364                             disableWifiTethering();
    365                             break;
    366                         case ConnectivityManager.TETHERING_BLUETOOTH:
    367                             disableBtTethering();
    368                             break;
    369                         case ConnectivityManager.TETHERING_USB:
    370                             disableUsbTethering();
    371                             break;
    372                     }
    373                 }
    374                 fireCallbacksForType(checkType, result);
    375 
    376                 if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
    377                     // We are done with all checks, time to die.
    378                     stopSelf();
    379                 } else {
    380                     // Start the next check in our list.
    381                     startProvisioning(mCurrentTypeIndex);
    382                 }
    383             }
    384         }
    385     };
    386 
    387     @VisibleForTesting
    388     void setUsageStatsManagerWrapper(UsageStatsManagerWrapper wrapper) {
    389         mUsageManagerWrapper = wrapper;
    390     }
    391 
    392     /**
    393      * A static helper class used for tests. UsageStatsManager cannot be mocked out becasue
    394      * it's marked final. This class can be mocked out instead.
    395      */
    396     @VisibleForTesting
    397     public static class UsageStatsManagerWrapper {
    398         private final UsageStatsManager mUsageStatsManager;
    399 
    400         UsageStatsManagerWrapper(Context context) {
    401             mUsageStatsManager = (UsageStatsManager)
    402                     context.getSystemService(Context.USAGE_STATS_SERVICE);
    403         }
    404 
    405         void setAppInactive(String packageName, boolean isInactive) {
    406             mUsageStatsManager.setAppInactive(packageName, isInactive);
    407         }
    408     }
    409 }
    410