Home | History | Annotate | Download | only in network
      1 /*
      2  * Copyright (C) 2016 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 package com.android.settings.network;
     17 
     18 import static android.os.UserManager.DISALLOW_CONFIG_TETHERING;
     19 import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced;
     20 import static com.android.settingslib.RestrictedLockUtils.hasBaseUserRestriction;
     21 
     22 import android.bluetooth.BluetoothAdapter;
     23 import android.bluetooth.BluetoothPan;
     24 import android.bluetooth.BluetoothProfile;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.database.ContentObserver;
     30 import android.net.ConnectivityManager;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.UserHandle;
     35 import android.provider.Settings;
     36 import android.support.annotation.VisibleForTesting;
     37 import android.support.v7.preference.Preference;
     38 import android.support.v7.preference.PreferenceScreen;
     39 
     40 import com.android.settings.R;
     41 import com.android.settings.TetherSettings;
     42 import com.android.settings.core.PreferenceControllerMixin;
     43 import com.android.settingslib.core.AbstractPreferenceController;
     44 import com.android.settingslib.core.lifecycle.Lifecycle;
     45 import com.android.settingslib.core.lifecycle.LifecycleObserver;
     46 import com.android.settingslib.core.lifecycle.events.OnCreate;
     47 import com.android.settingslib.core.lifecycle.events.OnDestroy;
     48 import com.android.settingslib.core.lifecycle.events.OnPause;
     49 import com.android.settingslib.core.lifecycle.events.OnResume;
     50 
     51 import java.util.concurrent.atomic.AtomicReference;
     52 
     53 public class TetherPreferenceController extends AbstractPreferenceController implements
     54         PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy {
     55 
     56     private static final String KEY_TETHER_SETTINGS = "tether_settings";
     57 
     58     private final boolean mAdminDisallowedTetherConfig;
     59     private final AtomicReference<BluetoothPan> mBluetoothPan;
     60     private final ConnectivityManager mConnectivityManager;
     61     private final BluetoothAdapter mBluetoothAdapter;
     62     @VisibleForTesting
     63     final BluetoothProfile.ServiceListener mBtProfileServiceListener =
     64             new android.bluetooth.BluetoothProfile.ServiceListener() {
     65                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
     66                     mBluetoothPan.set((BluetoothPan) proxy);
     67                     updateSummary();
     68                 }
     69 
     70                 public void onServiceDisconnected(int profile) {
     71                     mBluetoothPan.set(null);
     72                 }
     73             };
     74 
     75     private SettingObserver mAirplaneModeObserver;
     76     private Preference mPreference;
     77     private TetherBroadcastReceiver mTetherReceiver;
     78 
     79     @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     80     TetherPreferenceController() {
     81         super(null);
     82         mAdminDisallowedTetherConfig = false;
     83         mBluetoothPan = new AtomicReference<>();
     84         mConnectivityManager = null;
     85         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     86     }
     87 
     88     public TetherPreferenceController(Context context, Lifecycle lifecycle) {
     89         super(context);
     90         mBluetoothPan = new AtomicReference<>();
     91         mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context);
     92         mConnectivityManager =
     93                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
     94         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     95         if (lifecycle != null) {
     96             lifecycle.addObserver(this);
     97         }
     98     }
     99 
    100     @Override
    101     public void displayPreference(PreferenceScreen screen) {
    102         super.displayPreference(screen);
    103         mPreference = screen.findPreference(KEY_TETHER_SETTINGS);
    104         if (mPreference != null && !mAdminDisallowedTetherConfig) {
    105             mPreference.setTitle(
    106                     com.android.settingslib.Utils.getTetheringLabel(mConnectivityManager));
    107 
    108             // Grey out if provisioning is not available.
    109             mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext));
    110         }
    111     }
    112 
    113     @Override
    114     public boolean isAvailable() {
    115         final boolean isBlocked =
    116                 (!mConnectivityManager.isTetheringSupported() && !mAdminDisallowedTetherConfig)
    117                         || hasBaseUserRestriction(mContext, DISALLOW_CONFIG_TETHERING,
    118                         UserHandle.myUserId());
    119         return !isBlocked;
    120     }
    121 
    122     @Override
    123     public void updateState(Preference preference) {
    124         updateSummary();
    125     }
    126 
    127     @Override
    128     public String getPreferenceKey() {
    129         return KEY_TETHER_SETTINGS;
    130     }
    131 
    132     @Override
    133     public void onCreate(Bundle savedInstanceState) {
    134         if (mBluetoothAdapter != null) {
    135             mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener,
    136                     BluetoothProfile.PAN);
    137         }
    138     }
    139 
    140     @Override
    141     public void onResume() {
    142         if (mAirplaneModeObserver == null) {
    143             mAirplaneModeObserver = new SettingObserver();
    144         }
    145         if (mTetherReceiver == null) {
    146             mTetherReceiver = new TetherBroadcastReceiver();
    147         }
    148         mContext.registerReceiver(
    149                 mTetherReceiver, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
    150         mContext.getContentResolver()
    151                 .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver);
    152     }
    153 
    154     @Override
    155     public void onPause() {
    156         if (mAirplaneModeObserver != null) {
    157             mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver);
    158         }
    159         if (mTetherReceiver != null) {
    160             mContext.unregisterReceiver(mTetherReceiver);
    161         }
    162     }
    163 
    164     @Override
    165     public void onDestroy() {
    166         final BluetoothProfile profile = mBluetoothPan.getAndSet(null);
    167         if (profile != null && mBluetoothAdapter != null) {
    168             mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile);
    169         }
    170     }
    171 
    172     public static boolean isTetherConfigDisallowed(Context context) {
    173         return checkIfRestrictionEnforced(
    174                 context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null;
    175     }
    176 
    177     @VisibleForTesting
    178     void updateSummary() {
    179         if (mPreference == null) {
    180             // Preference is not ready yet.
    181             return;
    182         }
    183         String[] allTethered = mConnectivityManager.getTetheredIfaces();
    184         String[] wifiTetherRegex = mConnectivityManager.getTetherableWifiRegexs();
    185         String[] bluetoothRegex = mConnectivityManager.getTetherableBluetoothRegexs();
    186 
    187         boolean hotSpotOn = false;
    188         boolean tetherOn = false;
    189         if (allTethered != null) {
    190             if (wifiTetherRegex != null) {
    191                 for (String tethered : allTethered) {
    192                     for (String regex : wifiTetherRegex) {
    193                         if (tethered.matches(regex)) {
    194                             hotSpotOn = true;
    195                             break;
    196                         }
    197                     }
    198                 }
    199             }
    200             if (allTethered.length > 1) {
    201                 // We have more than 1 tethered connection
    202                 tetherOn = true;
    203             } else if (allTethered.length == 1) {
    204                 // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of
    205                 // tether.
    206                 tetherOn = !hotSpotOn;
    207             } else {
    208                 // No tethered connection.
    209                 tetherOn = false;
    210             }
    211         }
    212         if (!tetherOn
    213                 && bluetoothRegex != null && bluetoothRegex.length > 0
    214                 && mBluetoothAdapter != null
    215                 && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
    216             // Check bluetooth state. It's not included in mConnectivityManager.getTetheredIfaces.
    217             final BluetoothPan pan = mBluetoothPan.get();
    218             tetherOn = pan != null && pan.isTetheringOn();
    219         }
    220         if (!hotSpotOn && !tetherOn) {
    221             // Both off
    222             mPreference.setSummary(R.string.switch_off_text);
    223         } else if (hotSpotOn && tetherOn) {
    224             // Both on
    225             mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on);
    226         } else if (hotSpotOn) {
    227             mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off);
    228         } else {
    229             mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on);
    230         }
    231     }
    232 
    233     private void updateSummaryToOff() {
    234         if (mPreference == null) {
    235             // Preference is not ready yet.
    236             return;
    237         }
    238         mPreference.setSummary(R.string.switch_off_text);
    239     }
    240 
    241     class SettingObserver extends ContentObserver {
    242 
    243         public final Uri uri;
    244 
    245         public SettingObserver() {
    246             super(new Handler());
    247             uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON);
    248         }
    249 
    250         @Override
    251         public void onChange(boolean selfChange, Uri uri) {
    252             super.onChange(selfChange, uri);
    253             if (this.uri.equals(uri)) {
    254                 boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(),
    255                         Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
    256                 if (isAirplaneMode) {
    257                     // Airplane mode is on. Update summary to say tether is OFF directly. We cannot
    258                     // go through updateSummary() because turning off tether takes time, and we
    259                     // might still get "ON" status when rerun updateSummary(). So, just say it's off
    260                     updateSummaryToOff();
    261                 }
    262             }
    263         }
    264     }
    265 
    266     @VisibleForTesting
    267     class TetherBroadcastReceiver extends BroadcastReceiver {
    268 
    269         @Override
    270         public void onReceive(Context context, Intent intent) {
    271             updateSummary();
    272         }
    273 
    274     }
    275 }
    276