/* * Copyright (C) 2016 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.network; import static android.os.UserManager.DISALLOW_CONFIG_TETHERING; import static com.android.settingslib.RestrictedLockUtils.checkIfRestrictionEnforced; import static com.android.settingslib.RestrictedLockUtils.hasBaseUserRestriction; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.TetherSettings; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnCreate; import com.android.settingslib.core.lifecycle.events.OnDestroy; import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; import java.util.concurrent.atomic.AtomicReference; public class TetherPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnResume, OnPause, OnDestroy { private static final String KEY_TETHER_SETTINGS = "tether_settings"; private final boolean mAdminDisallowedTetherConfig; private final AtomicReference mBluetoothPan; private final ConnectivityManager mConnectivityManager; private final BluetoothAdapter mBluetoothAdapter; @VisibleForTesting final BluetoothProfile.ServiceListener mBtProfileServiceListener = new android.bluetooth.BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { mBluetoothPan.set((BluetoothPan) proxy); updateSummary(); } public void onServiceDisconnected(int profile) { mBluetoothPan.set(null); } }; private SettingObserver mAirplaneModeObserver; private Preference mPreference; private TetherBroadcastReceiver mTetherReceiver; @VisibleForTesting(otherwise = VisibleForTesting.NONE) TetherPreferenceController() { super(null); mAdminDisallowedTetherConfig = false; mBluetoothPan = new AtomicReference<>(); mConnectivityManager = null; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } public TetherPreferenceController(Context context, Lifecycle lifecycle) { super(context); mBluetoothPan = new AtomicReference<>(); mAdminDisallowedTetherConfig = isTetherConfigDisallowed(context); mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (lifecycle != null) { lifecycle.addObserver(this); } } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(KEY_TETHER_SETTINGS); if (mPreference != null && !mAdminDisallowedTetherConfig) { mPreference.setTitle( com.android.settingslib.Utils.getTetheringLabel(mConnectivityManager)); // Grey out if provisioning is not available. mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext)); } } @Override public boolean isAvailable() { final boolean isBlocked = (!mConnectivityManager.isTetheringSupported() && !mAdminDisallowedTetherConfig) || hasBaseUserRestriction(mContext, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()); return !isBlocked; } @Override public void updateState(Preference preference) { updateSummary(); } @Override public String getPreferenceKey() { return KEY_TETHER_SETTINGS; } @Override public void onCreate(Bundle savedInstanceState) { if (mBluetoothAdapter != null) { mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener, BluetoothProfile.PAN); } } @Override public void onResume() { if (mAirplaneModeObserver == null) { mAirplaneModeObserver = new SettingObserver(); } if (mTetherReceiver == null) { mTetherReceiver = new TetherBroadcastReceiver(); } mContext.registerReceiver( mTetherReceiver, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); mContext.getContentResolver() .registerContentObserver(mAirplaneModeObserver.uri, false, mAirplaneModeObserver); } @Override public void onPause() { if (mAirplaneModeObserver != null) { mContext.getContentResolver().unregisterContentObserver(mAirplaneModeObserver); } if (mTetherReceiver != null) { mContext.unregisterReceiver(mTetherReceiver); } } @Override public void onDestroy() { final BluetoothProfile profile = mBluetoothPan.getAndSet(null); if (profile != null && mBluetoothAdapter != null) { mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile); } } public static boolean isTetherConfigDisallowed(Context context) { return checkIfRestrictionEnforced( context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null; } @VisibleForTesting void updateSummary() { if (mPreference == null) { // Preference is not ready yet. return; } String[] allTethered = mConnectivityManager.getTetheredIfaces(); String[] wifiTetherRegex = mConnectivityManager.getTetherableWifiRegexs(); String[] bluetoothRegex = mConnectivityManager.getTetherableBluetoothRegexs(); boolean hotSpotOn = false; boolean tetherOn = false; if (allTethered != null) { if (wifiTetherRegex != null) { for (String tethered : allTethered) { for (String regex : wifiTetherRegex) { if (tethered.matches(regex)) { hotSpotOn = true; break; } } } } if (allTethered.length > 1) { // We have more than 1 tethered connection tetherOn = true; } else if (allTethered.length == 1) { // We have more than 1 tethered, it's either wifiTether (hotspot), or other type of // tether. tetherOn = !hotSpotOn; } else { // No tethered connection. tetherOn = false; } } if (!tetherOn && bluetoothRegex != null && bluetoothRegex.length > 0 && mBluetoothAdapter != null && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { // Check bluetooth state. It's not included in mConnectivityManager.getTetheredIfaces. final BluetoothPan pan = mBluetoothPan.get(); tetherOn = pan != null && pan.isTetheringOn(); } if (!hotSpotOn && !tetherOn) { // Both off mPreference.setSummary(R.string.switch_off_text); } else if (hotSpotOn && tetherOn) { // Both on mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_on); } else if (hotSpotOn) { mPreference.setSummary(R.string.tether_settings_summary_hotspot_on_tether_off); } else { mPreference.setSummary(R.string.tether_settings_summary_hotspot_off_tether_on); } } private void updateSummaryToOff() { if (mPreference == null) { // Preference is not ready yet. return; } mPreference.setSummary(R.string.switch_off_text); } class SettingObserver extends ContentObserver { public final Uri uri; public SettingObserver() { super(new Handler()); uri = Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON); } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); if (this.uri.equals(uri)) { boolean isAirplaneMode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) != 0; if (isAirplaneMode) { // Airplane mode is on. Update summary to say tether is OFF directly. We cannot // go through updateSummary() because turning off tether takes time, and we // might still get "ON" status when rerun updateSummary(). So, just say it's off updateSummaryToOff(); } } } } @VisibleForTesting class TetherBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { updateSummary(); } } }