We're getting signals from the radio and it sometimes drops out for a while. This change will delay applying an empty country code for 15sec but apply non-empty country codes immediately. It uses a sequence number so we only apply the most recent change, even if it's delayed. Also secures the wifi call to set the country code as we can't allow apps to set it willy-nilly. bug:11062898 Change-Id: I610039a833e26d4c5c8b7b9ac1b7546f3c83446a
1600 lines
58 KiB
Java
1600 lines
58 KiB
Java
/*
|
|
* Copyright (C) 2010 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.server.wifi;
|
|
|
|
import android.app.ActivityManager;
|
|
import android.app.AppOpsManager;
|
|
import android.bluetooth.BluetoothAdapter;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.pm.PackageManager;
|
|
import android.database.ContentObserver;
|
|
import android.net.DhcpInfo;
|
|
import android.net.DhcpResults;
|
|
import android.net.LinkAddress;
|
|
import android.net.NetworkUtils;
|
|
import android.net.RouteInfo;
|
|
import android.net.wifi.IWifiManager;
|
|
import android.net.wifi.ScanResult;
|
|
import android.net.wifi.BatchedScanResult;
|
|
import android.net.wifi.BatchedScanSettings;
|
|
import android.net.wifi.WifiConfiguration;
|
|
import android.net.wifi.WifiConfiguration.ProxySettings;
|
|
import android.net.wifi.WifiInfo;
|
|
import android.net.wifi.WifiManager;
|
|
import android.net.wifi.WifiStateMachine;
|
|
import android.net.wifi.WifiWatchdogStateMachine;
|
|
import android.os.Binder;
|
|
import android.os.Handler;
|
|
import android.os.Messenger;
|
|
import android.os.HandlerThread;
|
|
import android.os.IBinder;
|
|
import android.os.INetworkManagementService;
|
|
import android.os.Message;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemProperties;
|
|
import android.os.UserHandle;
|
|
import android.os.WorkSource;
|
|
import android.os.AsyncTask;
|
|
import android.provider.Settings;
|
|
import android.util.Log;
|
|
import android.util.Slog;
|
|
|
|
import java.io.FileNotFoundException;
|
|
import java.io.BufferedReader;
|
|
import java.io.FileDescriptor;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
|
|
import java.net.InetAddress;
|
|
import java.net.Inet4Address;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
import com.android.internal.R;
|
|
import com.android.internal.app.IBatteryStats;
|
|
import com.android.internal.telephony.TelephonyIntents;
|
|
import com.android.internal.util.AsyncChannel;
|
|
import com.android.server.am.BatteryStatsService;
|
|
import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
|
|
import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
|
|
import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
|
|
import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
|
|
import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
|
|
import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
|
|
import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
|
|
import static com.android.server.wifi.WifiController.CMD_SET_AP;
|
|
import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
|
|
import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
|
|
/**
|
|
* WifiService handles remote WiFi operation requests by implementing
|
|
* the IWifiManager interface.
|
|
*
|
|
* @hide
|
|
*/
|
|
public final class WifiService extends IWifiManager.Stub {
|
|
private static final String TAG = "WifiService";
|
|
private static final boolean DBG = false;
|
|
|
|
final WifiStateMachine mWifiStateMachine;
|
|
|
|
private final Context mContext;
|
|
|
|
final LockList mLocks = new LockList();
|
|
// some wifi lock statistics
|
|
private int mFullHighPerfLocksAcquired;
|
|
private int mFullHighPerfLocksReleased;
|
|
private int mFullLocksAcquired;
|
|
private int mFullLocksReleased;
|
|
private int mScanLocksAcquired;
|
|
private int mScanLocksReleased;
|
|
|
|
private final List<Multicaster> mMulticasters =
|
|
new ArrayList<Multicaster>();
|
|
private int mMulticastEnabled;
|
|
private int mMulticastDisabled;
|
|
|
|
private final IBatteryStats mBatteryStats;
|
|
private final AppOpsManager mAppOps;
|
|
|
|
private String mInterfaceName;
|
|
|
|
/* Tracks the open wi-fi network notification */
|
|
private WifiNotificationController mNotificationController;
|
|
/* Polls traffic stats and notifies clients */
|
|
private WifiTrafficPoller mTrafficPoller;
|
|
/* Tracks the persisted states for wi-fi & airplane mode */
|
|
final WifiSettingsStore mSettingsStore;
|
|
|
|
final boolean mBatchedScanSupported;
|
|
|
|
/**
|
|
* Asynchronous channel to WifiStateMachine
|
|
*/
|
|
private AsyncChannel mWifiStateMachineChannel;
|
|
|
|
/**
|
|
* Handles client connections
|
|
*/
|
|
private class ClientHandler extends Handler {
|
|
|
|
ClientHandler(android.os.Looper looper) {
|
|
super(looper);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
switch (msg.what) {
|
|
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
|
|
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
|
|
if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
|
|
// We track the clients by the Messenger
|
|
// since it is expected to be always available
|
|
mTrafficPoller.addClient(msg.replyTo);
|
|
} else {
|
|
Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
|
|
}
|
|
break;
|
|
}
|
|
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
|
|
if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
|
|
if (DBG) Slog.d(TAG, "Send failed, client connection lost");
|
|
} else {
|
|
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
|
|
}
|
|
mTrafficPoller.removeClient(msg.replyTo);
|
|
break;
|
|
}
|
|
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
|
|
AsyncChannel ac = new AsyncChannel();
|
|
ac.connect(mContext, this, msg.replyTo);
|
|
break;
|
|
}
|
|
/* Client commands are forwarded to state machine */
|
|
case WifiManager.CONNECT_NETWORK:
|
|
case WifiManager.SAVE_NETWORK: {
|
|
WifiConfiguration config = (WifiConfiguration) msg.obj;
|
|
int networkId = msg.arg1;
|
|
if (config != null && config.isValid()) {
|
|
// This is restricted because there is no UI for the user to
|
|
// monitor/control PAC.
|
|
if (config.proxySettings != ProxySettings.PAC) {
|
|
if (DBG) Slog.d(TAG, "Connect with config" + config);
|
|
mWifiStateMachine.sendMessage(Message.obtain(msg));
|
|
} else {
|
|
Slog.e(TAG, "ClientHandler.handleMessage cannot process msg with PAC");
|
|
if (msg.what == WifiManager.CONNECT_NETWORK) {
|
|
replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
|
|
} else {
|
|
replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
|
|
}
|
|
}
|
|
} else if (config == null
|
|
&& networkId != WifiConfiguration.INVALID_NETWORK_ID) {
|
|
if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
|
|
mWifiStateMachine.sendMessage(Message.obtain(msg));
|
|
} else {
|
|
Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
|
|
if (msg.what == WifiManager.CONNECT_NETWORK) {
|
|
replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
|
|
} else {
|
|
replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case WifiManager.FORGET_NETWORK:
|
|
case WifiManager.START_WPS:
|
|
case WifiManager.CANCEL_WPS:
|
|
case WifiManager.DISABLE_NETWORK:
|
|
case WifiManager.RSSI_PKTCNT_FETCH: {
|
|
mWifiStateMachine.sendMessage(Message.obtain(msg));
|
|
break;
|
|
}
|
|
default: {
|
|
Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void replyFailed(Message msg, int what) {
|
|
Message reply = msg.obtain();
|
|
reply.what = what;
|
|
reply.arg1 = WifiManager.INVALID_ARGS;
|
|
try {
|
|
msg.replyTo.send(reply);
|
|
} catch (RemoteException e) {
|
|
// There's not much we can do if reply can't be sent!
|
|
}
|
|
}
|
|
}
|
|
private ClientHandler mClientHandler;
|
|
|
|
/**
|
|
* Handles interaction with WifiStateMachine
|
|
*/
|
|
private class WifiStateMachineHandler extends Handler {
|
|
private AsyncChannel mWsmChannel;
|
|
|
|
WifiStateMachineHandler(android.os.Looper looper) {
|
|
super(looper);
|
|
mWsmChannel = new AsyncChannel();
|
|
mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
switch (msg.what) {
|
|
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
|
|
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
|
|
mWifiStateMachineChannel = mWsmChannel;
|
|
} else {
|
|
Slog.e(TAG, "WifiStateMachine connection failure, error=" + msg.arg1);
|
|
mWifiStateMachineChannel = null;
|
|
}
|
|
break;
|
|
}
|
|
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
|
|
Slog.e(TAG, "WifiStateMachine channel lost, msg.arg1 =" + msg.arg1);
|
|
mWifiStateMachineChannel = null;
|
|
//Re-establish connection to state machine
|
|
mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
|
|
break;
|
|
}
|
|
default: {
|
|
Slog.d(TAG, "WifiStateMachineHandler.handleMessage ignoring msg=" + msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
WifiStateMachineHandler mWifiStateMachineHandler;
|
|
|
|
private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
|
|
|
|
public WifiService(Context context) {
|
|
mContext = context;
|
|
|
|
mInterfaceName = SystemProperties.get("wifi.interface", "wlan0");
|
|
|
|
mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
|
|
mWifiStateMachine.enableRssiPolling(true);
|
|
mBatteryStats = BatteryStatsService.getService();
|
|
mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
|
|
|
|
mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
|
|
mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
|
|
mSettingsStore = new WifiSettingsStore(mContext);
|
|
|
|
HandlerThread wifiThread = new HandlerThread("WifiService");
|
|
wifiThread.start();
|
|
mClientHandler = new ClientHandler(wifiThread.getLooper());
|
|
mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
|
|
mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
|
|
mWifiController.start();
|
|
|
|
mBatchedScanSupported = mContext.getResources().getBoolean(
|
|
R.bool.config_wifi_batched_scan_supported);
|
|
|
|
registerForScanModeChange();
|
|
mContext.registerReceiver(
|
|
new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
if (mSettingsStore.handleAirplaneModeToggled()) {
|
|
mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
|
|
}
|
|
}
|
|
},
|
|
new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
|
|
|
|
// Adding optimizations of only receiving broadcasts when wifi is enabled
|
|
// can result in race conditions when apps toggle wifi in the background
|
|
// without active user involvement. Always receive broadcasts.
|
|
registerForBroadcasts();
|
|
}
|
|
|
|
private WifiController mWifiController;
|
|
|
|
/**
|
|
* Check if Wi-Fi needs to be enabled and start
|
|
* if needed
|
|
*
|
|
* This function is used only at boot time
|
|
*/
|
|
public void checkAndStartWifi() {
|
|
/* Check if wi-fi needs to be enabled */
|
|
boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
|
|
Slog.i(TAG, "WifiService starting up with Wi-Fi " +
|
|
(wifiEnabled ? "enabled" : "disabled"));
|
|
|
|
// If we are already disabled (could be due to airplane mode), avoid changing persist
|
|
// state here
|
|
if (wifiEnabled) setWifiEnabled(wifiEnabled);
|
|
|
|
mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
|
|
makeWifiWatchdogStateMachine(mContext);
|
|
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#pingSupplicant()}
|
|
* @return {@code true} if the operation succeeds, {@code false} otherwise
|
|
*/
|
|
public boolean pingSupplicant() {
|
|
enforceAccessPermission();
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#startScan()}
|
|
*
|
|
* <p>If workSource is null, all blame is given to the calling uid.
|
|
*/
|
|
public void startScan(WorkSource workSource) {
|
|
enforceChangePermission();
|
|
if (workSource != null) {
|
|
enforceWorkSourcePermission();
|
|
// WifiManager currently doesn't use names, so need to clear names out of the
|
|
// supplied WorkSource to allow future WorkSource combining.
|
|
workSource.clearNames();
|
|
}
|
|
mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
|
|
}
|
|
|
|
private class BatchedScanRequest extends DeathRecipient {
|
|
final BatchedScanSettings settings;
|
|
final int uid;
|
|
final int pid;
|
|
final WorkSource workSource;
|
|
|
|
BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
|
|
super(0, null, binder, null);
|
|
this.settings = settings;
|
|
this.uid = getCallingUid();
|
|
this.pid = getCallingPid();
|
|
workSource = ws;
|
|
}
|
|
public void binderDied() {
|
|
stopBatchedScan(settings, uid, pid);
|
|
}
|
|
public String toString() {
|
|
return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
|
|
}
|
|
|
|
public boolean isSameApp(int uid, int pid) {
|
|
return (this.uid == uid && this.pid == pid);
|
|
}
|
|
}
|
|
|
|
private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
|
|
|
|
public boolean isBatchedScanSupported() {
|
|
return mBatchedScanSupported;
|
|
}
|
|
|
|
public void pollBatchedScan() {
|
|
enforceChangePermission();
|
|
if (mBatchedScanSupported == false) return;
|
|
mWifiStateMachine.requestBatchedScanPoll();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#requestBatchedScan()}
|
|
*/
|
|
public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
|
|
WorkSource workSource) {
|
|
enforceChangePermission();
|
|
if (workSource != null) {
|
|
enforceWorkSourcePermission();
|
|
// WifiManager currently doesn't use names, so need to clear names out of the
|
|
// supplied WorkSource to allow future WorkSource combining.
|
|
workSource.clearNames();
|
|
}
|
|
if (mBatchedScanSupported == false) return false;
|
|
requested = new BatchedScanSettings(requested);
|
|
if (requested.isInvalid()) return false;
|
|
BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
|
|
synchronized(mBatchedScanners) {
|
|
mBatchedScanners.add(r);
|
|
resolveBatchedScannersLocked();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
|
|
enforceAccessPermission();
|
|
if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
|
|
int userId = UserHandle.getCallingUserId();
|
|
int uid = Binder.getCallingUid();
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
|
|
!= AppOpsManager.MODE_ALLOWED) {
|
|
return new ArrayList<BatchedScanResult>();
|
|
}
|
|
int currentUser = ActivityManager.getCurrentUser();
|
|
if (userId != currentUser) {
|
|
return new ArrayList<BatchedScanResult>();
|
|
} else {
|
|
return mWifiStateMachine.syncGetBatchedScanResultsList();
|
|
}
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
|
|
public void stopBatchedScan(BatchedScanSettings settings) {
|
|
enforceChangePermission();
|
|
if (mBatchedScanSupported == false) return;
|
|
stopBatchedScan(settings, getCallingUid(), getCallingPid());
|
|
}
|
|
|
|
private void stopBatchedScan(BatchedScanSettings settings, int uid, int pid) {
|
|
ArrayList<BatchedScanRequest> found = new ArrayList<BatchedScanRequest>();
|
|
synchronized(mBatchedScanners) {
|
|
for (BatchedScanRequest r : mBatchedScanners) {
|
|
if (r.isSameApp(uid, pid) && (settings == null || settings.equals(r.settings))) {
|
|
found.add(r);
|
|
if (settings != null) break;
|
|
}
|
|
}
|
|
for (BatchedScanRequest r : found) {
|
|
mBatchedScanners.remove(r);
|
|
}
|
|
if (found.size() != 0) {
|
|
resolveBatchedScannersLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void resolveBatchedScannersLocked() {
|
|
BatchedScanSettings setting = new BatchedScanSettings();
|
|
WorkSource responsibleWorkSource = null;
|
|
int responsibleUid = 0;
|
|
double responsibleCsph = 0; // Channel Scans Per Hour
|
|
|
|
if (mBatchedScanners.size() == 0) {
|
|
mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
|
|
return;
|
|
}
|
|
for (BatchedScanRequest r : mBatchedScanners) {
|
|
BatchedScanSettings s = r.settings;
|
|
|
|
// evaluate responsibility
|
|
int currentChannelCount;
|
|
int currentScanInterval;
|
|
double currentCsph;
|
|
|
|
if (s.channelSet == null || s.channelSet.isEmpty()) {
|
|
// all channels - 11 B and 9 A channels roughly.
|
|
currentChannelCount = 9 + 11;
|
|
} else {
|
|
currentChannelCount = s.channelSet.size();
|
|
// these are rough est - no real need to correct for reg-domain;
|
|
if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
|
|
if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
|
|
|
|
}
|
|
if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
|
|
currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
|
|
} else {
|
|
currentScanInterval = s.scanIntervalSec;
|
|
}
|
|
currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
|
|
|
|
if (currentCsph > responsibleCsph) {
|
|
responsibleUid = r.uid;
|
|
responsibleWorkSource = r.workSource;
|
|
responsibleCsph = currentCsph;
|
|
}
|
|
|
|
if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
|
|
s.maxScansPerBatch < setting.maxScansPerBatch) {
|
|
setting.maxScansPerBatch = s.maxScansPerBatch;
|
|
}
|
|
if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
|
|
(setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
|
|
s.maxApPerScan > setting.maxApPerScan)) {
|
|
setting.maxApPerScan = s.maxApPerScan;
|
|
}
|
|
if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
|
|
s.scanIntervalSec < setting.scanIntervalSec) {
|
|
setting.scanIntervalSec = s.scanIntervalSec;
|
|
}
|
|
if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
|
|
(setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
|
|
s.maxApForDistance > setting.maxApForDistance)) {
|
|
setting.maxApForDistance = s.maxApForDistance;
|
|
}
|
|
if (s.channelSet != null && s.channelSet.size() != 0) {
|
|
if (setting.channelSet == null || setting.channelSet.size() != 0) {
|
|
if (setting.channelSet == null) setting.channelSet = new ArrayList<String>();
|
|
for (String i : s.channelSet) {
|
|
if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
|
|
}
|
|
} // else, ignore the constraint - we already use all channels
|
|
} else {
|
|
if (setting.channelSet == null || setting.channelSet.size() != 0) {
|
|
setting.channelSet = new ArrayList<String>();
|
|
}
|
|
}
|
|
}
|
|
|
|
setting.constrain();
|
|
mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
|
|
responsibleWorkSource);
|
|
}
|
|
|
|
private void enforceAccessPermission() {
|
|
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
|
|
"WifiService");
|
|
}
|
|
|
|
private void enforceChangePermission() {
|
|
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
|
|
"WifiService");
|
|
|
|
}
|
|
|
|
private void enforceWorkSourcePermission() {
|
|
mContext.enforceCallingPermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
|
|
"WifiService");
|
|
|
|
}
|
|
|
|
private void enforceMulticastChangePermission() {
|
|
mContext.enforceCallingOrSelfPermission(
|
|
android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
|
|
"WifiService");
|
|
}
|
|
|
|
private void enforceConnectivityInternalPermission() {
|
|
mContext.enforceCallingOrSelfPermission(
|
|
android.Manifest.permission.CONNECTIVITY_INTERNAL,
|
|
"ConnectivityService");
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
|
|
* @param enable {@code true} to enable, {@code false} to disable.
|
|
* @return {@code true} if the enable/disable operation was
|
|
* started or is already in the queue.
|
|
*/
|
|
public synchronized boolean setWifiEnabled(boolean enable) {
|
|
enforceChangePermission();
|
|
Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
|
|
+ ", uid=" + Binder.getCallingUid());
|
|
if (DBG) {
|
|
Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
|
|
}
|
|
|
|
/*
|
|
* Caller might not have WRITE_SECURE_SETTINGS,
|
|
* only CHANGE_WIFI_STATE is enforced
|
|
*/
|
|
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
if (! mSettingsStore.handleWifiToggled(enable)) {
|
|
// Nothing to do if wifi cannot be toggled
|
|
return true;
|
|
}
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
|
|
mWifiController.sendMessage(CMD_WIFI_TOGGLED);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* see {@link WifiManager#getWifiState()}
|
|
* @return One of {@link WifiManager#WIFI_STATE_DISABLED},
|
|
* {@link WifiManager#WIFI_STATE_DISABLING},
|
|
* {@link WifiManager#WIFI_STATE_ENABLED},
|
|
* {@link WifiManager#WIFI_STATE_ENABLING},
|
|
* {@link WifiManager#WIFI_STATE_UNKNOWN}
|
|
*/
|
|
public int getWifiEnabledState() {
|
|
enforceAccessPermission();
|
|
return mWifiStateMachine.syncGetWifiState();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
|
|
* @param wifiConfig SSID, security and channel details as
|
|
* part of WifiConfiguration
|
|
* @param enabled true to enable and false to disable
|
|
*/
|
|
public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
|
|
enforceChangePermission();
|
|
// null wifiConfig is a meaningful input for CMD_SET_AP
|
|
if (wifiConfig == null || wifiConfig.isValid()) {
|
|
mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
|
|
} else {
|
|
Slog.e(TAG, "Invalid WifiConfiguration");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* see {@link WifiManager#getWifiApState()}
|
|
* @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
|
|
* {@link WifiManager#WIFI_AP_STATE_DISABLING},
|
|
* {@link WifiManager#WIFI_AP_STATE_ENABLED},
|
|
* {@link WifiManager#WIFI_AP_STATE_ENABLING},
|
|
* {@link WifiManager#WIFI_AP_STATE_FAILED}
|
|
*/
|
|
public int getWifiApEnabledState() {
|
|
enforceAccessPermission();
|
|
return mWifiStateMachine.syncGetWifiApState();
|
|
}
|
|
|
|
/**
|
|
* see {@link WifiManager#getWifiApConfiguration()}
|
|
* @return soft access point configuration
|
|
*/
|
|
public WifiConfiguration getWifiApConfiguration() {
|
|
enforceAccessPermission();
|
|
return mWifiStateMachine.syncGetWifiApConfiguration();
|
|
}
|
|
|
|
/**
|
|
* see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
|
|
* @param wifiConfig WifiConfiguration details for soft access point
|
|
*/
|
|
public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
|
|
enforceChangePermission();
|
|
if (wifiConfig == null)
|
|
return;
|
|
if (wifiConfig.isValid()) {
|
|
mWifiStateMachine.setWifiApConfiguration(wifiConfig);
|
|
} else {
|
|
Slog.e(TAG, "Invalid WifiConfiguration");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param enable {@code true} to enable, {@code false} to disable.
|
|
* @return {@code true} if the enable/disable operation was
|
|
* started or is already in the queue.
|
|
*/
|
|
public boolean isScanAlwaysAvailable() {
|
|
enforceAccessPermission();
|
|
return mSettingsStore.isScanAlwaysAvailable();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#disconnect()}
|
|
*/
|
|
public void disconnect() {
|
|
enforceChangePermission();
|
|
mWifiStateMachine.disconnectCommand();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#reconnect()}
|
|
*/
|
|
public void reconnect() {
|
|
enforceChangePermission();
|
|
mWifiStateMachine.reconnectCommand();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#reassociate()}
|
|
*/
|
|
public void reassociate() {
|
|
enforceChangePermission();
|
|
mWifiStateMachine.reassociateCommand();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
|
|
* @return the list of configured networks
|
|
*/
|
|
public List<WifiConfiguration> getConfiguredNetworks() {
|
|
enforceAccessPermission();
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
|
|
* @return the supplicant-assigned identifier for the new or updated
|
|
* network if the operation succeeds, or {@code -1} if it fails
|
|
*/
|
|
public int addOrUpdateNetwork(WifiConfiguration config) {
|
|
enforceChangePermission();
|
|
if (config.proxySettings == ProxySettings.PAC) {
|
|
enforceConnectivityInternalPermission();
|
|
}
|
|
if (config.isValid()) {
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return -1;
|
|
}
|
|
} else {
|
|
Slog.e(TAG, "bad network configuration");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link android.net.wifi.WifiManager#removeNetwork(int)}
|
|
* @param netId the integer that identifies the network configuration
|
|
* to the supplicant
|
|
* @return {@code true} if the operation succeeded
|
|
*/
|
|
public boolean removeNetwork(int netId) {
|
|
enforceChangePermission();
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)}
|
|
* @param netId the integer that identifies the network configuration
|
|
* to the supplicant
|
|
* @param disableOthers if true, disable all other networks.
|
|
* @return {@code true} if the operation succeeded
|
|
*/
|
|
public boolean enableNetwork(int netId, boolean disableOthers) {
|
|
enforceChangePermission();
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId,
|
|
disableOthers);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link android.net.wifi.WifiManager#disableNetwork(int)}
|
|
* @param netId the integer that identifies the network configuration
|
|
* to the supplicant
|
|
* @return {@code true} if the operation succeeded
|
|
*/
|
|
public boolean disableNetwork(int netId) {
|
|
enforceChangePermission();
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link android.net.wifi.WifiManager#getConnectionInfo()}
|
|
* @return the Wi-Fi information, contained in {@link WifiInfo}.
|
|
*/
|
|
public WifiInfo getConnectionInfo() {
|
|
enforceAccessPermission();
|
|
/*
|
|
* Make sure we have the latest information, by sending
|
|
* a status request to the supplicant.
|
|
*/
|
|
return mWifiStateMachine.syncRequestConnectionInfo();
|
|
}
|
|
|
|
/**
|
|
* Return the results of the most recent access point scan, in the form of
|
|
* a list of {@link ScanResult} objects.
|
|
* @return the list of results
|
|
*/
|
|
public List<ScanResult> getScanResults(String callingPackage) {
|
|
enforceAccessPermission();
|
|
int userId = UserHandle.getCallingUserId();
|
|
int uid = Binder.getCallingUid();
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
|
|
!= AppOpsManager.MODE_ALLOWED) {
|
|
return new ArrayList<ScanResult>();
|
|
}
|
|
int currentUser = ActivityManager.getCurrentUser();
|
|
if (userId != currentUser) {
|
|
return new ArrayList<ScanResult>();
|
|
} else {
|
|
return mWifiStateMachine.syncGetScanResultsList();
|
|
}
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tell the supplicant to persist the current list of configured networks.
|
|
* @return {@code true} if the operation succeeded
|
|
*
|
|
* TODO: deprecate this
|
|
*/
|
|
public boolean saveConfiguration() {
|
|
boolean result = true;
|
|
enforceChangePermission();
|
|
if (mWifiStateMachineChannel != null) {
|
|
return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
|
|
} else {
|
|
Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the country code
|
|
* @param countryCode ISO 3166 country code.
|
|
* @param persist {@code true} if the setting should be remembered.
|
|
*
|
|
* The persist behavior exists so that wifi can fall back to the last
|
|
* persisted country code on a restart, when the locale information is
|
|
* not available from telephony.
|
|
*/
|
|
public void setCountryCode(String countryCode, boolean persist) {
|
|
Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
|
|
" with persist set to " + persist);
|
|
enforceConnectivityInternalPermission();
|
|
final long token = Binder.clearCallingIdentity();
|
|
try {
|
|
mWifiStateMachine.setCountryCode(countryCode, persist);
|
|
} finally {
|
|
Binder.restoreCallingIdentity(token);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the operational frequency band
|
|
* @param band One of
|
|
* {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
|
|
* {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
|
|
* {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
|
|
* @param persist {@code true} if the setting should be remembered.
|
|
*
|
|
*/
|
|
public void setFrequencyBand(int band, boolean persist) {
|
|
enforceChangePermission();
|
|
if (!isDualBandSupported()) return;
|
|
Slog.i(TAG, "WifiService trying to set frequency band to " + band +
|
|
" with persist set to " + persist);
|
|
final long token = Binder.clearCallingIdentity();
|
|
try {
|
|
mWifiStateMachine.setFrequencyBand(band, persist);
|
|
} finally {
|
|
Binder.restoreCallingIdentity(token);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the operational frequency band
|
|
*/
|
|
public int getFrequencyBand() {
|
|
enforceAccessPermission();
|
|
return mWifiStateMachine.getFrequencyBand();
|
|
}
|
|
|
|
public boolean isDualBandSupported() {
|
|
//TODO: Should move towards adding a driver API that checks at runtime
|
|
return mContext.getResources().getBoolean(
|
|
com.android.internal.R.bool.config_wifi_dual_band_support);
|
|
}
|
|
|
|
/**
|
|
* Return the DHCP-assigned addresses from the last successful DHCP request,
|
|
* if any.
|
|
* @return the DHCP information
|
|
* @deprecated
|
|
*/
|
|
public DhcpInfo getDhcpInfo() {
|
|
enforceAccessPermission();
|
|
DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
|
|
if (dhcpResults.linkProperties == null) return null;
|
|
|
|
DhcpInfo info = new DhcpInfo();
|
|
for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) {
|
|
InetAddress addr = la.getAddress();
|
|
if (addr instanceof Inet4Address) {
|
|
info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr);
|
|
break;
|
|
}
|
|
}
|
|
for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) {
|
|
if (r.isDefaultRoute()) {
|
|
InetAddress gateway = r.getGateway();
|
|
if (gateway instanceof Inet4Address) {
|
|
info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway);
|
|
}
|
|
} else if (r.hasGateway() == false) {
|
|
LinkAddress dest = r.getDestination();
|
|
if (dest.getAddress() instanceof Inet4Address) {
|
|
info.netmask = NetworkUtils.prefixLengthToNetmaskInt(
|
|
dest.getNetworkPrefixLength());
|
|
}
|
|
}
|
|
}
|
|
int dnsFound = 0;
|
|
for (InetAddress dns : dhcpResults.linkProperties.getDnses()) {
|
|
if (dns instanceof Inet4Address) {
|
|
if (dnsFound == 0) {
|
|
info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
|
|
} else {
|
|
info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
|
|
}
|
|
if (++dnsFound > 1) break;
|
|
}
|
|
}
|
|
InetAddress serverAddress = dhcpResults.serverAddress;
|
|
if (serverAddress instanceof Inet4Address) {
|
|
info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress);
|
|
}
|
|
info.leaseDuration = dhcpResults.leaseDuration;
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#startWifi}
|
|
*
|
|
*/
|
|
public void startWifi() {
|
|
enforceConnectivityInternalPermission();
|
|
/* TODO: may be add permissions for access only to connectivity service
|
|
* TODO: if a start issued, keep wifi alive until a stop issued irrespective
|
|
* of WifiLock & device idle status unless wifi enabled status is toggled
|
|
*/
|
|
|
|
mWifiStateMachine.setDriverStart(true);
|
|
mWifiStateMachine.reconnectCommand();
|
|
}
|
|
|
|
public void captivePortalCheckComplete() {
|
|
enforceConnectivityInternalPermission();
|
|
mWifiStateMachine.captivePortalCheckComplete();
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#stopWifi}
|
|
*
|
|
*/
|
|
public void stopWifi() {
|
|
enforceConnectivityInternalPermission();
|
|
/*
|
|
* TODO: if a stop is issued, wifi is brought up only by startWifi
|
|
* unless wifi enabled status is toggled
|
|
*/
|
|
mWifiStateMachine.setDriverStart(false);
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#addToBlacklist}
|
|
*
|
|
*/
|
|
public void addToBlacklist(String bssid) {
|
|
enforceChangePermission();
|
|
|
|
mWifiStateMachine.addToBlacklist(bssid);
|
|
}
|
|
|
|
/**
|
|
* see {@link android.net.wifi.WifiManager#clearBlacklist}
|
|
*
|
|
*/
|
|
public void clearBlacklist() {
|
|
enforceChangePermission();
|
|
|
|
mWifiStateMachine.clearBlacklist();
|
|
}
|
|
|
|
/**
|
|
* enable TDLS for the local NIC to remote NIC
|
|
* The APPs don't know the remote MAC address to identify NIC though,
|
|
* so we need to do additional work to find it from remote IP address
|
|
*/
|
|
|
|
class TdlsTaskParams {
|
|
public String remoteIpAddress;
|
|
public boolean enable;
|
|
}
|
|
|
|
class TdlsTask extends AsyncTask<TdlsTaskParams, Integer, Integer> {
|
|
@Override
|
|
protected Integer doInBackground(TdlsTaskParams... params) {
|
|
|
|
// Retrieve parameters for the call
|
|
TdlsTaskParams param = params[0];
|
|
String remoteIpAddress = param.remoteIpAddress.trim();
|
|
boolean enable = param.enable;
|
|
|
|
// Get MAC address of Remote IP
|
|
String macAddress = null;
|
|
|
|
BufferedReader reader = null;
|
|
|
|
try {
|
|
reader = new BufferedReader(new FileReader("/proc/net/arp"));
|
|
|
|
// Skip over the line bearing colum titles
|
|
String line = reader.readLine();
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
String[] tokens = line.split("[ ]+");
|
|
if (tokens.length < 6) {
|
|
continue;
|
|
}
|
|
|
|
// ARP column format is
|
|
// Address HWType HWAddress Flags Mask IFace
|
|
String ip = tokens[0];
|
|
String mac = tokens[3];
|
|
|
|
if (remoteIpAddress.equals(ip)) {
|
|
macAddress = mac;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (macAddress == null) {
|
|
Slog.w(TAG, "Did not find remoteAddress {" + remoteIpAddress + "} in " +
|
|
"/proc/net/arp");
|
|
} else {
|
|
enableTdlsWithMacAddress(macAddress, enable);
|
|
}
|
|
|
|
} catch (FileNotFoundException e) {
|
|
Slog.e(TAG, "Could not open /proc/net/arp to lookup mac address");
|
|
} catch (IOException e) {
|
|
Slog.e(TAG, "Could not read /proc/net/arp to lookup mac address");
|
|
} finally {
|
|
try {
|
|
if (reader != null) {
|
|
reader.close();
|
|
}
|
|
}
|
|
catch (IOException e) {
|
|
// Do nothing
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public void enableTdls(String remoteAddress, boolean enable) {
|
|
TdlsTaskParams params = new TdlsTaskParams();
|
|
params.remoteIpAddress = remoteAddress;
|
|
params.enable = enable;
|
|
new TdlsTask().execute(params);
|
|
}
|
|
|
|
|
|
public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
|
|
mWifiStateMachine.enableTdls(remoteMacAddress, enable);
|
|
}
|
|
|
|
/**
|
|
* Get a reference to handler. This is used by a client to establish
|
|
* an AsyncChannel communication with WifiService
|
|
*/
|
|
public Messenger getWifiServiceMessenger() {
|
|
enforceAccessPermission();
|
|
enforceChangePermission();
|
|
return new Messenger(mClientHandler);
|
|
}
|
|
|
|
/** Get a reference to WifiStateMachine handler for AsyncChannel communication */
|
|
public Messenger getWifiStateMachineMessenger() {
|
|
enforceAccessPermission();
|
|
enforceChangePermission();
|
|
return mWifiStateMachine.getMessenger();
|
|
}
|
|
|
|
/**
|
|
* Get the IP and proxy configuration file
|
|
*/
|
|
public String getConfigFile() {
|
|
enforceAccessPermission();
|
|
return mWifiStateMachine.getConfigFile();
|
|
}
|
|
|
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
if (action.equals(Intent.ACTION_SCREEN_ON)) {
|
|
mWifiController.sendMessage(CMD_SCREEN_ON);
|
|
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
|
|
mWifiController.sendMessage(CMD_USER_PRESENT);
|
|
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
|
|
mWifiController.sendMessage(CMD_SCREEN_OFF);
|
|
} else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
|
|
int pluggedType = intent.getIntExtra("plugged", 0);
|
|
mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
|
|
} else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
|
|
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
|
|
BluetoothAdapter.STATE_DISCONNECTED);
|
|
mWifiStateMachine.sendBluetoothAdapterStateChange(state);
|
|
} else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
|
|
boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
|
|
mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Observes settings changes to scan always mode.
|
|
*/
|
|
private void registerForScanModeChange() {
|
|
ContentObserver contentObserver = new ContentObserver(null) {
|
|
@Override
|
|
public void onChange(boolean selfChange) {
|
|
mSettingsStore.handleWifiScanAlwaysAvailableToggled();
|
|
mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
|
|
}
|
|
};
|
|
|
|
mContext.getContentResolver().registerContentObserver(
|
|
Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
|
|
false, contentObserver);
|
|
}
|
|
|
|
private void registerForBroadcasts() {
|
|
IntentFilter intentFilter = new IntentFilter();
|
|
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
|
intentFilter.addAction(Intent.ACTION_USER_PRESENT);
|
|
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
|
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
|
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
|
|
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
|
|
intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
|
|
mContext.registerReceiver(mReceiver, intentFilter);
|
|
}
|
|
|
|
@Override
|
|
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
|
|
!= PackageManager.PERMISSION_GRANTED) {
|
|
pw.println("Permission Denial: can't dump WifiService from from pid="
|
|
+ Binder.getCallingPid()
|
|
+ ", uid=" + Binder.getCallingUid());
|
|
return;
|
|
}
|
|
pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
|
|
pw.println("Stay-awake conditions: " +
|
|
Settings.Global.getInt(mContext.getContentResolver(),
|
|
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
|
|
pw.println("mMulticastEnabled " + mMulticastEnabled);
|
|
pw.println("mMulticastDisabled " + mMulticastDisabled);
|
|
mWifiController.dump(fd, pw, args);
|
|
mSettingsStore.dump(fd, pw, args);
|
|
mNotificationController.dump(fd, pw, args);
|
|
mTrafficPoller.dump(fd, pw, args);
|
|
|
|
pw.println("Latest scan results:");
|
|
List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
|
|
if (scanResults != null && scanResults.size() != 0) {
|
|
pw.println(" BSSID Frequency RSSI Flags SSID");
|
|
for (ScanResult r : scanResults) {
|
|
pw.printf(" %17s %9d %5d %-16s %s%n",
|
|
r.BSSID,
|
|
r.frequency,
|
|
r.level,
|
|
r.capabilities,
|
|
r.SSID == null ? "" : r.SSID);
|
|
}
|
|
}
|
|
pw.println();
|
|
pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
|
|
mFullHighPerfLocksAcquired + " full high perf, " +
|
|
mScanLocksAcquired + " scan");
|
|
pw.println("Locks released: " + mFullLocksReleased + " full, " +
|
|
mFullHighPerfLocksReleased + " full high perf, " +
|
|
mScanLocksReleased + " scan");
|
|
pw.println();
|
|
pw.println("Locks held:");
|
|
mLocks.dump(pw);
|
|
|
|
mWifiWatchdogStateMachine.dump(fd, pw, args);
|
|
pw.println();
|
|
mWifiStateMachine.dump(fd, pw, args);
|
|
pw.println();
|
|
}
|
|
|
|
private class WifiLock extends DeathRecipient {
|
|
WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
|
|
super(lockMode, tag, binder, ws);
|
|
}
|
|
|
|
public void binderDied() {
|
|
synchronized (mLocks) {
|
|
releaseWifiLockLocked(mBinder);
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return "WifiLock{" + mTag + " type=" + mMode + " binder=" + mBinder + "}";
|
|
}
|
|
}
|
|
|
|
class LockList {
|
|
private List<WifiLock> mList;
|
|
|
|
private LockList() {
|
|
mList = new ArrayList<WifiLock>();
|
|
}
|
|
|
|
synchronized boolean hasLocks() {
|
|
return !mList.isEmpty();
|
|
}
|
|
|
|
synchronized int getStrongestLockMode() {
|
|
if (mList.isEmpty()) {
|
|
return WifiManager.WIFI_MODE_FULL;
|
|
}
|
|
|
|
if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
|
|
return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
|
|
}
|
|
|
|
if (mFullLocksAcquired > mFullLocksReleased) {
|
|
return WifiManager.WIFI_MODE_FULL;
|
|
}
|
|
|
|
return WifiManager.WIFI_MODE_SCAN_ONLY;
|
|
}
|
|
|
|
synchronized void updateWorkSource(WorkSource ws) {
|
|
for (int i = 0; i < mLocks.mList.size(); i++) {
|
|
ws.add(mLocks.mList.get(i).mWorkSource);
|
|
}
|
|
}
|
|
|
|
private void addLock(WifiLock lock) {
|
|
if (findLockByBinder(lock.mBinder) < 0) {
|
|
mList.add(lock);
|
|
}
|
|
}
|
|
|
|
private WifiLock removeLock(IBinder binder) {
|
|
int index = findLockByBinder(binder);
|
|
if (index >= 0) {
|
|
WifiLock ret = mList.remove(index);
|
|
ret.unlinkDeathRecipient();
|
|
return ret;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private int findLockByBinder(IBinder binder) {
|
|
int size = mList.size();
|
|
for (int i = size - 1; i >= 0; i--) {
|
|
if (mList.get(i).mBinder == binder)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private void dump(PrintWriter pw) {
|
|
for (WifiLock l : mList) {
|
|
pw.print(" ");
|
|
pw.println(l);
|
|
}
|
|
}
|
|
}
|
|
|
|
void enforceWakeSourcePermission(int uid, int pid) {
|
|
if (uid == android.os.Process.myUid()) {
|
|
return;
|
|
}
|
|
mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
|
|
pid, uid, null);
|
|
}
|
|
|
|
public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
|
|
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
|
|
if (lockMode != WifiManager.WIFI_MODE_FULL &&
|
|
lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
|
|
lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
|
|
Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
|
|
if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
|
|
return false;
|
|
}
|
|
if (ws != null && ws.size() == 0) {
|
|
ws = null;
|
|
}
|
|
if (ws != null) {
|
|
enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
|
|
}
|
|
if (ws == null) {
|
|
ws = new WorkSource(Binder.getCallingUid());
|
|
}
|
|
WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
|
|
synchronized (mLocks) {
|
|
return acquireWifiLockLocked(wifiLock);
|
|
}
|
|
}
|
|
|
|
private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
|
|
switch(wifiLock.mMode) {
|
|
case WifiManager.WIFI_MODE_FULL:
|
|
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
|
|
case WifiManager.WIFI_MODE_SCAN_ONLY:
|
|
mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
|
|
switch(wifiLock.mMode) {
|
|
case WifiManager.WIFI_MODE_FULL:
|
|
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
|
|
case WifiManager.WIFI_MODE_SCAN_ONLY:
|
|
mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private boolean acquireWifiLockLocked(WifiLock wifiLock) {
|
|
if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
|
|
|
|
mLocks.addLock(wifiLock);
|
|
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
noteAcquireWifiLock(wifiLock);
|
|
switch(wifiLock.mMode) {
|
|
case WifiManager.WIFI_MODE_FULL:
|
|
++mFullLocksAcquired;
|
|
break;
|
|
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
|
|
++mFullHighPerfLocksAcquired;
|
|
break;
|
|
|
|
case WifiManager.WIFI_MODE_SCAN_ONLY:
|
|
++mScanLocksAcquired;
|
|
break;
|
|
}
|
|
mWifiController.sendMessage(CMD_LOCKS_CHANGED);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
|
|
int uid = Binder.getCallingUid();
|
|
int pid = Binder.getCallingPid();
|
|
if (ws != null && ws.size() == 0) {
|
|
ws = null;
|
|
}
|
|
if (ws != null) {
|
|
enforceWakeSourcePermission(uid, pid);
|
|
}
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
synchronized (mLocks) {
|
|
int index = mLocks.findLockByBinder(lock);
|
|
if (index < 0) {
|
|
throw new IllegalArgumentException("Wifi lock not active");
|
|
}
|
|
WifiLock wl = mLocks.mList.get(index);
|
|
noteReleaseWifiLock(wl);
|
|
wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
|
|
noteAcquireWifiLock(wl);
|
|
}
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
public boolean releaseWifiLock(IBinder lock) {
|
|
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
|
|
synchronized (mLocks) {
|
|
return releaseWifiLockLocked(lock);
|
|
}
|
|
}
|
|
|
|
private boolean releaseWifiLockLocked(IBinder lock) {
|
|
boolean hadLock;
|
|
|
|
WifiLock wifiLock = mLocks.removeLock(lock);
|
|
|
|
if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock);
|
|
|
|
hadLock = (wifiLock != null);
|
|
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
if (hadLock) {
|
|
noteReleaseWifiLock(wifiLock);
|
|
switch(wifiLock.mMode) {
|
|
case WifiManager.WIFI_MODE_FULL:
|
|
++mFullLocksReleased;
|
|
break;
|
|
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
|
|
++mFullHighPerfLocksReleased;
|
|
break;
|
|
case WifiManager.WIFI_MODE_SCAN_ONLY:
|
|
++mScanLocksReleased;
|
|
break;
|
|
}
|
|
mWifiController.sendMessage(CMD_LOCKS_CHANGED);
|
|
}
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
|
|
return hadLock;
|
|
}
|
|
|
|
private abstract class DeathRecipient
|
|
implements IBinder.DeathRecipient {
|
|
String mTag;
|
|
int mMode;
|
|
IBinder mBinder;
|
|
WorkSource mWorkSource;
|
|
|
|
DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
|
|
super();
|
|
mTag = tag;
|
|
mMode = mode;
|
|
mBinder = binder;
|
|
mWorkSource = ws;
|
|
try {
|
|
mBinder.linkToDeath(this, 0);
|
|
} catch (RemoteException e) {
|
|
binderDied();
|
|
}
|
|
}
|
|
|
|
void unlinkDeathRecipient() {
|
|
mBinder.unlinkToDeath(this, 0);
|
|
}
|
|
}
|
|
|
|
private class Multicaster extends DeathRecipient {
|
|
Multicaster(String tag, IBinder binder) {
|
|
super(Binder.getCallingUid(), tag, binder, null);
|
|
}
|
|
|
|
public void binderDied() {
|
|
Slog.e(TAG, "Multicaster binderDied");
|
|
synchronized (mMulticasters) {
|
|
int i = mMulticasters.indexOf(this);
|
|
if (i != -1) {
|
|
removeMulticasterLocked(i, mMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
public String toString() {
|
|
return "Multicaster{" + mTag + " binder=" + mBinder + "}";
|
|
}
|
|
|
|
public int getUid() {
|
|
return mMode;
|
|
}
|
|
}
|
|
|
|
public void initializeMulticastFiltering() {
|
|
enforceMulticastChangePermission();
|
|
|
|
synchronized (mMulticasters) {
|
|
// if anybody had requested filters be off, leave off
|
|
if (mMulticasters.size() != 0) {
|
|
return;
|
|
} else {
|
|
mWifiStateMachine.startFilteringMulticastV4Packets();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void acquireMulticastLock(IBinder binder, String tag) {
|
|
enforceMulticastChangePermission();
|
|
|
|
synchronized (mMulticasters) {
|
|
mMulticastEnabled++;
|
|
mMulticasters.add(new Multicaster(tag, binder));
|
|
// Note that we could call stopFilteringMulticastV4Packets only when
|
|
// our new size == 1 (first call), but this function won't
|
|
// be called often and by making the stopPacket call each
|
|
// time we're less fragile and self-healing.
|
|
mWifiStateMachine.stopFilteringMulticastV4Packets();
|
|
}
|
|
|
|
int uid = Binder.getCallingUid();
|
|
final long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
mBatteryStats.noteWifiMulticastEnabled(uid);
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
public void releaseMulticastLock() {
|
|
enforceMulticastChangePermission();
|
|
|
|
int uid = Binder.getCallingUid();
|
|
synchronized (mMulticasters) {
|
|
mMulticastDisabled++;
|
|
int size = mMulticasters.size();
|
|
for (int i = size - 1; i >= 0; i--) {
|
|
Multicaster m = mMulticasters.get(i);
|
|
if ((m != null) && (m.getUid() == uid)) {
|
|
removeMulticasterLocked(i, uid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void removeMulticasterLocked(int i, int uid)
|
|
{
|
|
Multicaster removed = mMulticasters.remove(i);
|
|
|
|
if (removed != null) {
|
|
removed.unlinkDeathRecipient();
|
|
}
|
|
if (mMulticasters.size() == 0) {
|
|
mWifiStateMachine.startFilteringMulticastV4Packets();
|
|
}
|
|
|
|
final long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
mBatteryStats.noteWifiMulticastDisabled(uid);
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
public boolean isMulticastEnabled() {
|
|
enforceAccessPermission();
|
|
|
|
synchronized (mMulticasters) {
|
|
return (mMulticasters.size() > 0);
|
|
}
|
|
}
|
|
}
|