summaryrefslogtreecommitdiffstats
path: root/services/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com')
-rw-r--r--services/java/com/android/server/AlarmManagerService.java575
-rw-r--r--services/java/com/android/server/BatteryService.java33
-rw-r--r--services/java/com/android/server/BootReceiver.java7
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java2
-rw-r--r--services/java/com/android/server/LocalServices.java58
-rw-r--r--services/java/com/android/server/SystemServer.java98
-rw-r--r--services/java/com/android/server/SystemService.java151
-rw-r--r--services/java/com/android/server/SystemServiceManager.java141
-rw-r--r--services/java/com/android/server/UiModeManagerService.java286
-rw-r--r--services/java/com/android/server/Watchdog.java10
-rw-r--r--services/java/com/android/server/am/ActivityStack.java19
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java9
-rw-r--r--services/java/com/android/server/devicepolicy/DevicePolicyManagerService.java329
-rw-r--r--services/java/com/android/server/lights/Light.java41
-rw-r--r--services/java/com/android/server/lights/LightsManager.java31
-rw-r--r--services/java/com/android/server/lights/LightsService.java (renamed from services/java/com/android/server/LightsService.java)94
-rw-r--r--services/java/com/android/server/media/MediaRouterService.java8
-rw-r--r--services/java/com/android/server/media/RemoteDisplayProviderProxy.java7
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java6
-rw-r--r--services/java/com/android/server/net/NetworkStatsCollection.java6
-rw-r--r--services/java/com/android/server/notification/NotificationDelegate.java27
-rw-r--r--services/java/com/android/server/notification/NotificationManagerInternal.java8
-rw-r--r--services/java/com/android/server/notification/NotificationManagerService.java (renamed from services/java/com/android/server/NotificationManagerService.java)1231
-rwxr-xr-xservices/java/com/android/server/pm/PackageManagerService.java55
-rw-r--r--services/java/com/android/server/power/DisplayPowerController.java18
-rw-r--r--services/java/com/android/server/power/DisplayPowerState.java6
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java29
-rw-r--r--services/java/com/android/server/statusbar/StatusBarManagerInternal.java29
-rw-r--r--services/java/com/android/server/statusbar/StatusBarManagerService.java (renamed from services/java/com/android/server/StatusBarManagerService.java)236
-rw-r--r--services/java/com/android/server/storage/DeviceStorageMonitorInternal.java24
-rw-r--r--services/java/com/android/server/storage/DeviceStorageMonitorService.java (renamed from services/java/com/android/server/DeviceStorageMonitorService.java)226
-rw-r--r--services/java/com/android/server/twilight/TwilightListener.java21
-rw-r--r--services/java/com/android/server/twilight/TwilightManager.java24
-rw-r--r--services/java/com/android/server/twilight/TwilightService.java (renamed from services/java/com/android/server/TwilightService.java)249
-rw-r--r--services/java/com/android/server/twilight/TwilightState.java112
-rw-r--r--services/java/com/android/server/usb/UsbDeviceManager.java8
-rw-r--r--services/java/com/android/server/wifi/NetworkUpdateResult.java70
-rw-r--r--services/java/com/android/server/wifi/StateChangeResult.java41
-rw-r--r--services/java/com/android/server/wifi/SupplicantStateTracker.java363
-rw-r--r--services/java/com/android/server/wifi/WifiApConfigStore.java212
-rw-r--r--services/java/com/android/server/wifi/WifiConfigStore.java2001
-rw-r--r--services/java/com/android/server/wifi/WifiController.java1
-rw-r--r--services/java/com/android/server/wifi/WifiMonitor.java940
-rw-r--r--services/java/com/android/server/wifi/WifiNative.java972
-rw-r--r--services/java/com/android/server/wifi/WifiNotificationController.java1
-rw-r--r--services/java/com/android/server/wifi/WifiService.java2
-rw-r--r--services/java/com/android/server/wifi/WifiStateMachine.java4419
-rw-r--r--services/java/com/android/server/wifi/WifiWatchdogStateMachine.java1210
-rw-r--r--services/java/com/android/server/wifi/p2p/WifiP2pService.java2959
49 files changed, 15655 insertions, 1750 deletions
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 5ae9a6d..1fb164d 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -31,6 +31,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -64,54 +65,51 @@ import static android.app.AlarmManager.ELAPSED_REALTIME;
import com.android.internal.util.LocalLog;
-class AlarmManagerService extends IAlarmManager.Stub {
+class AlarmManagerService extends SystemService {
// The threshold for how long an alarm can be late before we print a
// warning message. The time duration is in milliseconds.
private static final long LATE_ALARM_THRESHOLD = 10 * 1000;
private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
private static final int RTC_MASK = 1 << RTC;
- private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP;
+ private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP;
private static final int ELAPSED_REALTIME_MASK = 1 << ELAPSED_REALTIME;
- private static final int TIME_CHANGED_MASK = 1 << 16;
- private static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK;
+ static final int TIME_CHANGED_MASK = 1 << 16;
+ static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK;
// Mask for testing whether a given alarm type is wakeup vs non-wakeup
- private static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup
-
- private static final String TAG = "AlarmManager";
- private static final String ClockReceiver_TAG = "ClockReceiver";
- private static final boolean localLOGV = false;
- private static final boolean DEBUG_BATCH = localLOGV || false;
- private static final boolean DEBUG_VALIDATE = localLOGV || false;
- private static final int ALARM_EVENT = 1;
- private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
+ static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup
+
+ static final String TAG = "AlarmManager";
+ static final String ClockReceiver_TAG = "ClockReceiver";
+ static final boolean localLOGV = false;
+ static final boolean DEBUG_BATCH = localLOGV || false;
+ static final boolean DEBUG_VALIDATE = localLOGV || false;
+ static final int ALARM_EVENT = 1;
+ static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
- private static final Intent mBackgroundIntent
+ static final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
- private static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
+ static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
- private static final boolean WAKEUP_STATS = false;
+ static final boolean WAKEUP_STATS = false;
- private final Context mContext;
+ final LocalLog mLog = new LocalLog(TAG);
- private final LocalLog mLog = new LocalLog(TAG);
+ final Object mLock = new Object();
- private Object mLock = new Object();
-
- private int mDescriptor;
+ int mDescriptor;
private long mNextWakeup;
private long mNextNonWakeup;
- private int mBroadcastRefCount = 0;
- private PowerManager.WakeLock mWakeLock;
- private ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
- private final AlarmThread mWaitThread = new AlarmThread();
- private final AlarmHandler mHandler = new AlarmHandler();
- private ClockReceiver mClockReceiver;
+ int mBroadcastRefCount = 0;
+ PowerManager.WakeLock mWakeLock;
+ ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
+ final AlarmHandler mHandler = new AlarmHandler();
+ ClockReceiver mClockReceiver;
private UninstallReceiver mUninstallReceiver;
- private final ResultReceiver mResultReceiver = new ResultReceiver();
- private final PendingIntent mTimeTickSender;
- private final PendingIntent mDateChangeSender;
+ final ResultReceiver mResultReceiver = new ResultReceiver();
+ PendingIntent mTimeTickSender;
+ PendingIntent mDateChangeSender;
class WakeupEvent {
public long when;
@@ -125,8 +123,8 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
- private final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>();
- private final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day
+ final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>();
+ final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day
static final class Batch {
long start; // These endpoints are always in ELAPSED
@@ -317,9 +315,9 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
// minimum recurrence period or alarm futurity for us to be able to fuzz it
- private static final long MIN_FUZZABLE_INTERVAL = 10000;
- private static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
- private final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
+ static final long MIN_FUZZABLE_INTERVAL = 10000;
+ static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
+ final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
static long convertToElapsed(long when, int type) {
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
@@ -403,7 +401,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
- private static final class InFlight extends Intent {
+ static final class InFlight extends Intent {
final PendingIntent mPendingIntent;
final WorkSource mWorkSource;
final Pair<String, ComponentName> mTarget;
@@ -427,7 +425,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
- private static final class FilterStats {
+ static final class FilterStats {
final BroadcastStats mBroadcastStats;
final Pair<String, ComponentName> mTarget;
@@ -443,7 +441,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
- private static final class BroadcastStats {
+ static final class BroadcastStats {
final String mPackageName;
long aggregateTime;
@@ -459,47 +457,48 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
- private final HashMap<String, BroadcastStats> mBroadcastStats
+ final HashMap<String, BroadcastStats> mBroadcastStats
= new HashMap<String, BroadcastStats>();
- public AlarmManagerService(Context context) {
- mContext = context;
+ @Override
+ public void onStart() {
mDescriptor = init();
mNextWakeup = mNextNonWakeup = 0;
// We have to set current TimeZone info to kernel
// because kernel doesn't keep this after reboot
- String tz = SystemProperties.get(TIMEZONE_PROPERTY);
- if (tz != null) {
- setTimeZone(tz);
- }
+ setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mTimeTickSender = PendingIntent.getBroadcastAsUser(context, 0,
+ mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
new Intent(Intent.ACTION_TIME_TICK).addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND), 0,
UserHandle.ALL);
Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- mDateChangeSender = PendingIntent.getBroadcastAsUser(context, 0, intent,
+ mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
// now that we have initied the driver schedule the alarm
- mClockReceiver= new ClockReceiver();
+ mClockReceiver = new ClockReceiver();
mClockReceiver.scheduleTimeTickEvent();
mClockReceiver.scheduleDateChangedEvent();
mUninstallReceiver = new UninstallReceiver();
if (mDescriptor != -1) {
- mWaitThread.start();
+ AlarmThread waitThread = new AlarmThread();
+ waitThread.start();
} else {
Slog.w(TAG, "Failed to open alarm driver. Falling back to a handler.");
}
+
+ publishBinderService(Context.ALARM_SERVICE, mService);
}
-
+
+ @Override
protected void finalize() throws Throwable {
try {
close(mDescriptor);
@@ -508,19 +507,51 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
- @Override
- public void set(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, WorkSource workSource) {
- if (workSource != null) {
- mContext.enforceCallingPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS,
- "AlarmManager.set");
+ void setTimeZoneImpl(String tz) {
+ if (TextUtils.isEmpty(tz)) {
+ return;
+ }
+
+ TimeZone zone = TimeZone.getTimeZone(tz);
+ // Prevent reentrant calls from stepping on each other when writing
+ // the time zone property
+ boolean timeZoneWasChanged = false;
+ synchronized (this) {
+ String current = SystemProperties.get(TIMEZONE_PROPERTY);
+ if (current == null || !current.equals(zone.getID())) {
+ if (localLOGV) {
+ Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
+ }
+ timeZoneWasChanged = true;
+ SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
+ }
+
+ // Update the kernel timezone information
+ // Kernel tracks time offsets as 'minutes west of GMT'
+ int gmtOffset = zone.getOffset(System.currentTimeMillis());
+ setKernelTimezone(mDescriptor, -(gmtOffset / 60000));
}
- set(type, triggerAtTime, windowLength, interval, operation, false, workSource);
+ TimeZone.setDefault(null);
+
+ if (timeZoneWasChanged) {
+ Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time-zone", zone.getID());
+ getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
}
- public void set(int type, long triggerAtTime, long windowLength, long interval,
+ void removeImpl(PendingIntent operation) {
+ if (operation == null) {
+ return;
+ }
+ synchronized (mLock) {
+ removeLocked(operation);
+ }
+ }
+
+ void setImpl(int type, long triggerAtTime, long windowLength, long interval,
PendingIntent operation, boolean isStandalone, WorkSource workSource) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
@@ -606,231 +637,64 @@ class AlarmManagerService extends IAlarmManager.Stub {
rescheduleKernelAlarmsLocked();
}
- private void logBatchesLocked() {
- ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
- PrintWriter pw = new PrintWriter(bs);
- final long nowRTC = System.currentTimeMillis();
- final long nowELAPSED = SystemClock.elapsedRealtime();
- final int NZ = mAlarmBatches.size();
- for (int iz = 0; iz < NZ; iz++) {
- Batch bz = mAlarmBatches.get(iz);
- pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz);
- dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC);
- pw.flush();
- Slog.v(TAG, bs.toString());
- bs.reset();
- }
- }
-
- private boolean validateConsistencyLocked() {
- if (DEBUG_VALIDATE) {
- long lastTime = Long.MIN_VALUE;
- final int N = mAlarmBatches.size();
- for (int i = 0; i < N; i++) {
- Batch b = mAlarmBatches.get(i);
- if (b.start >= lastTime) {
- // duplicate start times are okay because of standalone batches
- lastTime = b.start;
- } else {
- Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order");
- logBatchesLocked();
- return false;
- }
- }
- }
- return true;
- }
-
- private Batch findFirstWakeupBatchLocked() {
- final int N = mAlarmBatches.size();
- for (int i = 0; i < N; i++) {
- Batch b = mAlarmBatches.get(i);
- if (b.hasWakeups()) {
- return b;
+ private final IBinder mService = new IAlarmManager.Stub() {
+ @Override
+ public void set(int type, long triggerAtTime, long windowLength, long interval,
+ PendingIntent operation, WorkSource workSource) {
+ if (workSource != null) {
+ getContext().enforceCallingPermission(
+ android.Manifest.permission.UPDATE_DEVICE_STATS,
+ "AlarmManager.set");
}
- }
- return null;
- }
- private void rescheduleKernelAlarmsLocked() {
- // Schedule the next upcoming wakeup alarm. If there is a deliverable batch
- // prior to that which contains no wakeups, we schedule that as well.
- if (mAlarmBatches.size() > 0) {
- final Batch firstWakeup = findFirstWakeupBatchLocked();
- final Batch firstBatch = mAlarmBatches.get(0);
- if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
- mNextWakeup = firstWakeup.start;
- setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
- }
- if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
- mNextNonWakeup = firstBatch.start;
- setLocked(ELAPSED_REALTIME, firstBatch.start);
- }
+ setImpl(type, triggerAtTime, windowLength, interval, operation,
+ false, workSource);
}
- }
-
- public void setTime(long millis) {
- mContext.enforceCallingOrSelfPermission(
- "android.permission.SET_TIME",
- "setTime");
-
- SystemClock.setCurrentTimeMillis(millis);
- }
-
- public void setTimeZone(String tz) {
- mContext.enforceCallingOrSelfPermission(
- "android.permission.SET_TIME_ZONE",
- "setTimeZone");
-
- long oldId = Binder.clearCallingIdentity();
- try {
- if (TextUtils.isEmpty(tz)) return;
- TimeZone zone = TimeZone.getTimeZone(tz);
- // Prevent reentrant calls from stepping on each other when writing
- // the time zone property
- boolean timeZoneWasChanged = false;
- synchronized (this) {
- String current = SystemProperties.get(TIMEZONE_PROPERTY);
- if (current == null || !current.equals(zone.getID())) {
- if (localLOGV) {
- Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
- }
- timeZoneWasChanged = true;
- SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
- }
-
- // Update the kernel timezone information
- // Kernel tracks time offsets as 'minutes west of GMT'
- int gmtOffset = zone.getOffset(System.currentTimeMillis());
- setKernelTimezone(mDescriptor, -(gmtOffset / 60000));
- }
-
- TimeZone.setDefault(null);
- if (timeZoneWasChanged) {
- Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time-zone", zone.getID());
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- }
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- }
-
- public void remove(PendingIntent operation) {
- if (operation == null) {
- return;
- }
- synchronized (mLock) {
- removeLocked(operation);
- }
- }
-
- public void removeLocked(PendingIntent operation) {
- boolean didRemove = false;
- for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
- Batch b = mAlarmBatches.get(i);
- didRemove |= b.remove(operation);
- if (b.size() == 0) {
- mAlarmBatches.remove(i);
- }
- }
+ @Override
+ public void setTime(long millis) {
+ getContext().enforceCallingOrSelfPermission(
+ "android.permission.SET_TIME",
+ "setTime");
- if (didRemove) {
- if (DEBUG_BATCH) {
- Slog.v(TAG, "remove(operation) changed bounds; rebatching");
- }
- rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
+ SystemClock.setCurrentTimeMillis(millis);
}
- }
- public void removeLocked(String packageName) {
- boolean didRemove = false;
- for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
- Batch b = mAlarmBatches.get(i);
- didRemove |= b.remove(packageName);
- if (b.size() == 0) {
- mAlarmBatches.remove(i);
+ @Override
+ public void setTimeZone(String tz) {
+ getContext().enforceCallingOrSelfPermission(
+ "android.permission.SET_TIME_ZONE",
+ "setTimeZone");
+
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ setTimeZoneImpl(tz);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
- if (didRemove) {
- if (DEBUG_BATCH) {
- Slog.v(TAG, "remove(package) changed bounds; rebatching");
- }
- rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
- }
- }
+ @Override
+ public void remove(PendingIntent operation) {
+ removeImpl(operation);
- public void removeUserLocked(int userHandle) {
- boolean didRemove = false;
- for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
- Batch b = mAlarmBatches.get(i);
- didRemove |= b.remove(userHandle);
- if (b.size() == 0) {
- mAlarmBatches.remove(i);
- }
}
- if (didRemove) {
- if (DEBUG_BATCH) {
- Slog.v(TAG, "remove(user) changed bounds; rebatching");
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump AlarmManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
}
- rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
- }
- }
- public boolean lookForPackageLocked(String packageName) {
- for (int i = 0; i < mAlarmBatches.size(); i++) {
- Batch b = mAlarmBatches.get(i);
- if (b.hasPackage(packageName)) {
- return true;
- }
+ dumpImpl(pw);
}
- return false;
- }
+ };
- private void setLocked(int type, long when)
- {
- if (mDescriptor != -1)
- {
- // The kernel never triggers alarms with negative wakeup times
- // so we ensure they are positive.
- long alarmSeconds, alarmNanoseconds;
- if (when < 0) {
- alarmSeconds = 0;
- alarmNanoseconds = 0;
- } else {
- alarmSeconds = when / 1000;
- alarmNanoseconds = (when % 1000) * 1000 * 1000;
- }
-
- set(mDescriptor, type, alarmSeconds, alarmNanoseconds);
- }
- else
- {
- Message msg = Message.obtain();
- msg.what = ALARM_EVENT;
-
- mHandler.removeMessages(ALARM_EVENT);
- mHandler.sendMessageAtTime(msg, when);
- }
- }
-
- @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 AlarmManager from from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- return;
- }
-
+ void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
final long nowRTC = System.currentTimeMillis();
@@ -980,6 +844,159 @@ class AlarmManagerService extends IAlarmManager.Stub {
}
}
+ private void logBatchesLocked() {
+ ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
+ PrintWriter pw = new PrintWriter(bs);
+ final long nowRTC = System.currentTimeMillis();
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ final int NZ = mAlarmBatches.size();
+ for (int iz = 0; iz < NZ; iz++) {
+ Batch bz = mAlarmBatches.get(iz);
+ pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz);
+ dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC);
+ pw.flush();
+ Slog.v(TAG, bs.toString());
+ bs.reset();
+ }
+ }
+
+ private boolean validateConsistencyLocked() {
+ if (DEBUG_VALIDATE) {
+ long lastTime = Long.MIN_VALUE;
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (b.start >= lastTime) {
+ // duplicate start times are okay because of standalone batches
+ lastTime = b.start;
+ } else {
+ Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order");
+ logBatchesLocked();
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private Batch findFirstWakeupBatchLocked() {
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (b.hasWakeups()) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ void rescheduleKernelAlarmsLocked() {
+ // Schedule the next upcoming wakeup alarm. If there is a deliverable batch
+ // prior to that which contains no wakeups, we schedule that as well.
+ if (mAlarmBatches.size() > 0) {
+ final Batch firstWakeup = findFirstWakeupBatchLocked();
+ final Batch firstBatch = mAlarmBatches.get(0);
+ if (firstWakeup != null && mNextWakeup != firstWakeup.start) {
+ mNextWakeup = firstWakeup.start;
+ setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start);
+ }
+ if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) {
+ mNextNonWakeup = firstBatch.start;
+ setLocked(ELAPSED_REALTIME, firstBatch.start);
+ }
+ }
+ }
+
+ private void removeLocked(PendingIntent operation) {
+ boolean didRemove = false;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ didRemove |= b.remove(operation);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
+ }
+ }
+
+ if (didRemove) {
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "remove(operation) changed bounds; rebatching");
+ }
+ rebatchAllAlarmsLocked(true);
+ rescheduleKernelAlarmsLocked();
+ }
+ }
+
+ void removeLocked(String packageName) {
+ boolean didRemove = false;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ didRemove |= b.remove(packageName);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
+ }
+ }
+
+ if (didRemove) {
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "remove(package) changed bounds; rebatching");
+ }
+ rebatchAllAlarmsLocked(true);
+ rescheduleKernelAlarmsLocked();
+ }
+ }
+
+ void removeUserLocked(int userHandle) {
+ boolean didRemove = false;
+ for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
+ Batch b = mAlarmBatches.get(i);
+ didRemove |= b.remove(userHandle);
+ if (b.size() == 0) {
+ mAlarmBatches.remove(i);
+ }
+ }
+
+ if (didRemove) {
+ if (DEBUG_BATCH) {
+ Slog.v(TAG, "remove(user) changed bounds; rebatching");
+ }
+ rebatchAllAlarmsLocked(true);
+ rescheduleKernelAlarmsLocked();
+ }
+ }
+
+ boolean lookForPackageLocked(String packageName) {
+ for (int i = 0; i < mAlarmBatches.size(); i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (b.hasPackage(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void setLocked(int type, long when) {
+ if (mDescriptor != -1) {
+ // The kernel never triggers alarms with negative wakeup times
+ // so we ensure they are positive.
+ long alarmSeconds, alarmNanoseconds;
+ if (when < 0) {
+ alarmSeconds = 0;
+ alarmNanoseconds = 0;
+ } else {
+ alarmSeconds = when / 1000;
+ alarmNanoseconds = (when % 1000) * 1000 * 1000;
+ }
+
+ set(mDescriptor, type, alarmSeconds, alarmNanoseconds);
+ } else {
+ Message msg = Message.obtain();
+ msg.what = ALARM_EVENT;
+
+ mHandler.removeMessages(ALARM_EVENT);
+ mHandler.sendMessageAtTime(msg, when);
+ }
+ }
+
private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
String prefix, String label, long now) {
for (int i=list.size()-1; i>=0; i--) {
@@ -1020,7 +1037,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
private native int waitForAlarm(int fd);
private native int setKernelTimezone(int fd, int minuteswest);
- private void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) {
+ void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) {
// batches are temporally sorted, so we need only pull from the
// start of the list until we either empty it or hit a batch
// that is not yet deliverable
@@ -1166,13 +1183,13 @@ class AlarmManagerService extends IAlarmManager.Stub {
if (DEBUG_BATCH) {
Slog.v(TAG, "Time changed notification from kernel; rebatching");
}
- remove(mTimeTickSender);
+ removeImpl(mTimeTickSender);
rebatchAllAlarms();
mClockReceiver.scheduleTimeTickEvent();
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
}
synchronized (mLock) {
@@ -1206,7 +1223,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
Alarm alarm = triggerList.get(i);
try {
if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
- alarm.operation.send(mContext, 0,
+ alarm.operation.send(getContext(), 0,
mBackgroundIntent.putExtra(
Intent.EXTRA_ALARM_COUNT, alarm.count),
mResultReceiver, mHandler);
@@ -1248,7 +1265,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
// is a repeating alarm, so toss the hoser.
- remove(alarm.operation);
+ removeImpl(alarm.operation);
}
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
@@ -1310,7 +1327,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
// is a repeating alarm, so toss the hoser.
- remove(alarm.operation);
+ removeImpl(alarm.operation);
}
}
}
@@ -1323,7 +1340,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_DATE_CHANGED);
- mContext.registerReceiver(this, filter);
+ getContext().registerReceiver(this, filter);
}
@Override
@@ -1354,7 +1371,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
final long tickEventDelay = nextTime - currentTime;
final WorkSource workSource = null; // Let system take blame for time tick events.
- set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
+ setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
0, mTimeTickSender, true, workSource);
}
@@ -1368,7 +1385,7 @@ class AlarmManagerService extends IAlarmManager.Stub {
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- set(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource);
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource);
}
}
@@ -1379,12 +1396,12 @@ class AlarmManagerService extends IAlarmManager.Stub {
filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
filter.addDataScheme("package");
- mContext.registerReceiver(this, filter);
+ getContext().registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
sdFilter.addAction(Intent.ACTION_USER_STOPPED);
- mContext.registerReceiver(this, sdFilter);
+ getContext().registerReceiver(this, sdFilter);
}
@Override
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java
index b234a4e..6e72e24 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -19,6 +19,8 @@ package com.android.server;
import android.os.BatteryStats;
import com.android.internal.app.IBatteryStats;
import com.android.server.am.BatteryStatsService;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
import android.app.ActivityManagerNative;
import android.content.ContentResolver;
@@ -134,13 +136,10 @@ public final class BatteryService extends Binder {
private boolean mSentLowBatteryBroadcast = false;
- private BatteryListener mBatteryPropertiesListener;
- private IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
-
- public BatteryService(Context context, LightsService lights) {
+ public BatteryService(Context context, LightsManager lightsManager) {
mContext = context;
mHandler = new Handler(true /*async*/);
- mLed = new Led(context, lights);
+ mLed = new Led(context, lightsManager);
mBatteryStats = BatteryStatsService.getService();
mCriticalBatteryLevel = mContext.getResources().getInteger(
@@ -158,13 +157,11 @@ public final class BatteryService extends Binder {
"DEVPATH=/devices/virtual/switch/invalid_charger");
}
- mBatteryPropertiesListener = new BatteryListener();
-
IBinder b = ServiceManager.getService("batteryproperties");
- mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(b);
-
+ final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
+ IBatteryPropertiesRegistrar.Stub.asInterface(b);
try {
- mBatteryPropertiesRegistrar.registerListener(mBatteryPropertiesListener);
+ batteryPropertiesRegistrar.registerListener(new BatteryListener());
} catch (RemoteException e) {
// Should never happen.
}
@@ -677,7 +674,7 @@ public final class BatteryService extends Binder {
};
private final class Led {
- private final LightsService.Light mBatteryLight;
+ private final Light mBatteryLight;
private final int mBatteryLowARGB;
private final int mBatteryMediumARGB;
@@ -685,8 +682,8 @@ public final class BatteryService extends Binder {
private final int mBatteryLedOn;
private final int mBatteryLedOff;
- public Led(Context context, LightsService lights) {
- mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
+ public Led(Context context, LightsManager lights) {
+ mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY);
mBatteryLowARGB = context.getResources().getInteger(
com.android.internal.R.integer.config_notificationsBatteryLowARGB);
@@ -712,7 +709,7 @@ public final class BatteryService extends Binder {
mBatteryLight.setColor(mBatteryLowARGB);
} else {
// Flash red when battery is low and not charging
- mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED,
+ mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED,
mBatteryLedOn, mBatteryLedOff);
}
} else if (status == BatteryManager.BATTERY_STATUS_CHARGING
@@ -732,8 +729,14 @@ public final class BatteryService extends Binder {
}
private final class BatteryListener extends IBatteryPropertiesListener.Stub {
+ @Override
public void batteryPropertiesChanged(BatteryProperties props) {
- BatteryService.this.update(props);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ BatteryService.this.update(props);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index da1b254..bce85ce 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -120,6 +120,8 @@ public class BootReceiver extends BroadcastReceiver {
// Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
addFileToDropBox(db, prefs, headers, "/proc/last_kmsg",
-LOG_SIZE, "SYSTEM_LAST_KMSG");
+ addFileToDropBox(db, prefs, headers, "/sys/fs/pstore/console-ramoops",
+ -LOG_SIZE, "SYSTEM_LAST_KMSG");
addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
-LOG_SIZE, "SYSTEM_RECOVERY_LOG");
addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
@@ -184,6 +186,11 @@ public class BootReceiver extends BroadcastReceiver {
File file = new File("/proc/last_kmsg");
long fileTime = file.lastModified();
+ if (fileTime <= 0) {
+ file = new File("/sys/fs/pstore/console-ramoops");
+ fileTime = file.lastModified();
+ }
+
if (fileTime <= 0) return; // File does not exist
if (prefs != null) {
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index f061149..0b3eb12 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -30,7 +30,7 @@ import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
-import com.android.server.EventLogTags;
+import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParser;
diff --git a/services/java/com/android/server/LocalServices.java b/services/java/com/android/server/LocalServices.java
new file mode 100644
index 0000000..deff79d
--- /dev/null
+++ b/services/java/com/android/server/LocalServices.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.util.ArrayMap;
+
+/**
+ * This class is used in a similar way as ServiceManager, except the services registered here
+ * are not Binder objects and are only available in the same process.
+ *
+ * Once all services are converted to the SystemService interface, this class can be absorbed
+ * into SystemServiceManager.
+ */
+public final class LocalServices {
+ private LocalServices() {}
+
+ private static final ArrayMap<Class<?>, Object> sLocalServiceObjects =
+ new ArrayMap<Class<?>, Object>();
+
+ /**
+ * Returns a local service instance that implements the specified interface.
+ *
+ * @param type The type of service.
+ * @return The service object.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getService(Class<T> type) {
+ synchronized (sLocalServiceObjects) {
+ return (T) sLocalServiceObjects.get(type);
+ }
+ }
+
+ /**
+ * Adds a service instance of the specified interface to the global registry of local services.
+ */
+ public static <T> void addService(Class<T> type, T service) {
+ synchronized (sLocalServiceObjects) {
+ if (sLocalServiceObjects.containsKey(type)) {
+ throw new IllegalStateException("Overriding service registration");
+ }
+ sLocalServiceObjects.put(type, service);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3a1c747..38f4c78 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -17,6 +17,8 @@
package com.android.server;
import android.app.ActivityManagerNative;
+import android.app.IAlarmManager;
+import android.app.INotificationManager;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -26,7 +28,6 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.AudioService;
-import android.net.wifi.p2p.WifiP2pService;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
@@ -59,9 +60,12 @@ import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.input.InputManagerService;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.notification.NotificationManagerService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.Installer;
import com.android.server.pm.PackageManagerService;
@@ -70,9 +74,14 @@ import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.print.PrintManagerService;
import com.android.server.search.SearchManagerService;
+import com.android.server.statusbar.StatusBarManagerService;
+import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightService;
import com.android.server.usb.UsbService;
import com.android.server.wallpaper.WallpaperManagerService;
import com.android.server.wifi.WifiService;
+import com.android.server.wifi.p2p.WifiP2pService;
import com.android.server.wm.WindowManagerService;
import dalvik.system.VMRuntime;
@@ -131,12 +140,12 @@ class ServerThread {
Installer installer = null;
AccountManagerService accountManager = null;
ContentService contentService = null;
- LightsService lights = null;
+ LightsManager lights = null;
PowerManagerService power = null;
DisplayManagerService display = null;
BatteryService battery = null;
VibratorService vibrator = null;
- AlarmManagerService alarm = null;
+ IAlarmManager alarm = null;
MountService mountService = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
@@ -152,8 +161,7 @@ class ServerThread {
DockObserver dock = null;
UsbService usb = null;
SerialService serial = null;
- TwilightService twilight = null;
- UiModeManagerService uiMode = null;
+ TwilightManager twilight = null;
RecognitionManagerService recognition = null;
NetworkTimeUpdateService networkTimeUpdater = null;
CommonTimeManagementService commonTimeMgmtService = null;
@@ -203,6 +211,8 @@ class ServerThread {
Slog.e("System", "************ Failure starting bootstrap service", e);
}
+ final SystemServiceManager systemServiceManager = new SystemServiceManager(context);
+
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -278,8 +288,8 @@ class ServerThread {
Slog.i(TAG, "System Content Providers");
ActivityManagerService.installSystemProviders();
- Slog.i(TAG, "Lights Service");
- lights = new LightsService(context);
+ systemServiceManager.startService(LightsService.class);
+ lights = LocalServices.getService(LightsManager.class);
Slog.i(TAG, "Battery Service");
battery = new BatteryService(context, lights);
@@ -295,17 +305,16 @@ class ServerThread {
// only initialize the power service after we have started the
// lights service, content providers and the battery service.
- power.init(context, lights, ActivityManagerService.self(), battery,
+ power.init(context, lights, battery,
BatteryStatsService.getService(),
ActivityManagerService.self().getAppOpsService(), display);
- Slog.i(TAG, "Alarm Manager");
- alarm = new AlarmManagerService(context);
- ServiceManager.addService(Context.ALARM_SERVICE, alarm);
+ systemServiceManager.startService(AlarmManagerService.class);
+ alarm = IAlarmManager.Stub.asInterface(
+ ServiceManager.getService(Context.ALARM_SERVICE));
Slog.i(TAG, "Init Watchdog");
- Watchdog.getInstance().init(context, battery, power, alarm,
- ActivityManagerService.self());
+ Watchdog.getInstance().init(context, ActivityManagerService.self());
Watchdog.getInstance().addThread(wmHandler, "WindowManager thread");
Slog.i(TAG, "Input Manager");
@@ -350,9 +359,9 @@ class ServerThread {
DevicePolicyManagerService devicePolicy = null;
StatusBarManagerService statusBar = null;
+ INotificationManager notification = null;
InputMethodManagerService imm = null;
AppWidgetService appWidget = null;
- NotificationManagerService notification = null;
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
@@ -401,7 +410,7 @@ class ServerThread {
ActivityManagerNative.getDefault().showBootMessage(
context.getResources().getText(
com.android.internal.R.string.android_upgrading_starting_apps),
- false);
+ false);
} catch (RemoteException e) {
}
@@ -571,22 +580,12 @@ class ServerThread {
reportWtf("making Content Service ready", e);
}
- try {
- Slog.i(TAG, "Notification Manager");
- notification = new NotificationManagerService(context, statusBar, lights);
- ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
- networkPolicy.bindNotificationManager(notification);
- } catch (Throwable e) {
- reportWtf("starting Notification Manager", e);
- }
+ systemServiceManager.startService(NotificationManagerService.class);
+ notification = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ networkPolicy.bindNotificationManager(notification);
- try {
- Slog.i(TAG, "Device Storage Monitor");
- ServiceManager.addService(DeviceStorageMonitorService.SERVICE,
- new DeviceStorageMonitorService(context));
- } catch (Throwable e) {
- reportWtf("starting DeviceStorageMonitor service", e);
- }
+ systemServiceManager.startService(DeviceStorageMonitorService.class);
if (!disableLocation) {
try {
@@ -685,20 +684,10 @@ class ServerThread {
}
}
- try {
- Slog.i(TAG, "Twilight Service");
- twilight = new TwilightService(context);
- } catch (Throwable e) {
- reportWtf("starting TwilightService", e);
- }
+ systemServiceManager.startService(TwilightService.class);
+ twilight = LocalServices.getService(TwilightManager.class);
- try {
- Slog.i(TAG, "UI Mode Manager Service");
- // Listen for UI mode changes
- uiMode = new UiModeManagerService(context, twilight);
- } catch (Throwable e) {
- reportWtf("starting UiModeManagerService", e);
- }
+ systemServiceManager.startService(UiModeManagerService.class);
if (!disableNonCoreServices) {
try {
@@ -858,13 +847,7 @@ class ServerThread {
}
}
- if (notification != null) {
- try {
- notification.systemReady();
- } catch (Throwable e) {
- reportWtf("making Notification Service ready", e);
- }
- }
+ systemServiceManager.startBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
try {
wm.systemReady();
@@ -913,8 +896,6 @@ class ServerThread {
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
final UsbService usbF = usb;
- final TwilightService twilightF = twilight;
- final UiModeManagerService uiModeF = uiMode;
final AppWidgetService appWidgetF = appWidget;
final WallpaperManagerService wallpaperF = wallpaper;
final InputMethodManagerService immF = imm;
@@ -992,16 +973,6 @@ class ServerThread {
reportWtf("making USB Service ready", e);
}
try {
- if (twilightF != null) twilightF.systemReady();
- } catch (Throwable e) {
- reportWtf("makin Twilight Service ready", e);
- }
- try {
- if (uiModeF != null) uiModeF.systemReady();
- } catch (Throwable e) {
- reportWtf("making UI Mode Service ready", e);
- }
- try {
if (recognitionF != null) recognitionF.systemReady();
} catch (Throwable e) {
reportWtf("making Recognition Service ready", e);
@@ -1010,6 +981,7 @@ class ServerThread {
// It is now okay to let the various system services start their
// third party code...
+ systemServiceManager.startBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
try {
if (appWidgetF != null) appWidgetF.systemRunning(safeMode);
@@ -1086,6 +1058,8 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("Notifying MediaRouterService running", e);
}
+
+ systemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETE);
}
});
diff --git a/services/java/com/android/server/SystemService.java b/services/java/com/android/server/SystemService.java
new file mode 100644
index 0000000..37afb4f
--- /dev/null
+++ b/services/java/com/android/server/SystemService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * System services respond to lifecycle events that help the services know what
+ */
+public abstract class SystemService {
+ /*
+ * Boot Phases
+ */
+ public static final int PHASE_SYSTEM_SERVICES_READY = 500;
+ public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600;
+ public static final int PHASE_BOOT_COMPLETE = 1000;
+
+ private SystemServiceManager mManager;
+ private Context mContext;
+
+ final void init(Context context, SystemServiceManager manager) {
+ mContext = context;
+ mManager = manager;
+ onCreate(context);
+ }
+
+ /**
+ * Services are not yet available. This is a good place to do setup work that does
+ * not require other services.
+ *
+ * @param context The system context.
+ */
+ public void onCreate(Context context) {}
+
+ /**
+ * Called when the dependencies listed in the @Service class-annotation are available
+ * and after the chosen start phase.
+ * When this method returns, the service should be published.
+ */
+ public abstract void onStart();
+
+ /**
+ * Called on each phase of the boot process. Phases before the service's start phase
+ * (as defined in the @Service annotation) are never received.
+ *
+ * @param phase The current boot phase.
+ */
+ public void onBootPhase(int phase) {}
+
+ /**
+ * Publish the service so it is accessible to other services and apps.
+ */
+ protected final void publishBinderService(String name, IBinder service) {
+ ServiceManager.addService(name, service);
+ }
+
+ /**
+ * Get a binder service by its name.
+ */
+ protected final IBinder getBinderService(String name) {
+ return ServiceManager.getService(name);
+ }
+
+ /**
+ * Publish the service so it is only accessible to the system process.
+ */
+ protected final <T> void publishLocalService(Class<T> type, T service) {
+ LocalServices.addService(type, service);
+ }
+
+ /**
+ * Get a local service by interface.
+ */
+ protected final <T> T getLocalService(Class<T> type) {
+ return LocalServices.getService(type);
+ }
+
+ public final Context getContext() {
+ return mContext;
+ }
+
+// /**
+// * Called when a new user has been created. If your service deals with multiple users, this
+// * method should be overridden.
+// *
+// * @param userHandle The user that was created.
+// */
+// public void onUserCreated(int userHandle) {
+// }
+//
+// /**
+// * Called when an existing user has started a new session. If your service deals with multiple
+// * users, this method should be overridden.
+// *
+// * @param userHandle The user who started a new session.
+// */
+// public void onUserStarted(int userHandle) {
+// }
+//
+// /**
+// * Called when a background user session has entered the foreground. If your service deals with
+// * multiple users, this method should be overridden.
+// *
+// * @param userHandle The user who's session entered the foreground.
+// */
+// public void onUserForeground(int userHandle) {
+// }
+//
+// /**
+// * Called when a foreground user session has entered the background. If your service deals with
+// * multiple users, this method should be overridden;
+// *
+// * @param userHandle The user who's session entered the background.
+// */
+// public void onUserBackground(int userHandle) {
+// }
+//
+// /**
+// * Called when a user's active session has stopped. If your service deals with multiple users,
+// * this method should be overridden.
+// *
+// * @param userHandle The user who's session has stopped.
+// */
+// public void onUserStopped(int userHandle) {
+// }
+//
+// /**
+// * Called when a user has been removed from the system. If your service deals with multiple
+// * users, this method should be overridden.
+// *
+// * @param userHandle The user who has been removed.
+// */
+// public void onUserRemoved(int userHandle) {
+// }
+}
diff --git a/services/java/com/android/server/SystemServiceManager.java b/services/java/com/android/server/SystemServiceManager.java
new file mode 100644
index 0000000..648975a
--- /dev/null
+++ b/services/java/com/android/server/SystemServiceManager.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Manages creating, starting, and other lifecycle events of system services.
+ */
+public class SystemServiceManager {
+ private static final String TAG = "SystemServiceManager";
+
+ private final Context mContext;
+
+ // Services that should receive lifecycle events.
+ private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
+
+ private int mCurrentPhase = -1;
+
+ public SystemServiceManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates and starts a system service. The class must be a subclass of
+ * {@link com.android.server.SystemService}.
+ *
+ * @param serviceClass A Java class that implements the SystemService interface.
+ * @throws RuntimeException if the service fails to start.
+ */
+ public void startService(Class<?> serviceClass) {
+ final SystemService serviceInstance = createInstance(serviceClass);
+ try {
+ Slog.i(TAG, "Creating " + serviceClass.getSimpleName());
+ serviceInstance.init(mContext, this);
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed to create service " + serviceClass.getName(), e);
+ }
+
+ mServices.add(serviceInstance);
+
+ try {
+ Slog.i(TAG, "Starting " + serviceClass.getSimpleName());
+ serviceInstance.onStart();
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed to start service " + serviceClass.getName(), e);
+ }
+ }
+
+ /**
+ * Starts the specified boot phase for all system services that have been started up to
+ * this point.
+ *
+ * @param phase The boot phase to start.
+ */
+ public void startBootPhase(final int phase) {
+ if (phase <= mCurrentPhase) {
+ throw new IllegalArgumentException("Next phase must be larger than previous");
+ }
+ mCurrentPhase = phase;
+
+ Slog.i(TAG, "Starting phase " + mCurrentPhase);
+
+ final int serviceLen = mServices.size();
+ for (int i = 0; i < serviceLen; i++) {
+ final SystemService service = mServices.get(i);
+ try {
+ service.onBootPhase(mCurrentPhase);
+ } catch (Throwable e) {
+ reportWtf("Service " + service.getClass().getName() +
+ " threw an Exception processing boot phase " + mCurrentPhase, e);
+ }
+ }
+ }
+
+ /**
+ * Outputs the state of this manager to the System log.
+ */
+ public void dump() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Current phase: ").append(mCurrentPhase).append("\n");
+ builder.append("Services:\n");
+ final int startedLen = mServices.size();
+ for (int i = 0; i < startedLen; i++) {
+ final SystemService service = mServices.get(i);
+ builder.append("\t")
+ .append(service.getClass().getSimpleName())
+ .append("\n");
+ }
+
+ Slog.e(TAG, builder.toString());
+ }
+
+ private SystemService createInstance(Class<?> clazz) {
+ // Make sure it's a type we expect
+ if (!SystemService.class.isAssignableFrom(clazz)) {
+ reportWtf("Class " + clazz.getName() + " does not extend " +
+ SystemService.class.getName());
+ }
+
+ try {
+ return (SystemService) clazz.newInstance();
+ } catch (InstantiationException e) {
+ reportWtf("Class " + clazz.getName() + " is abstract", e);
+ } catch (IllegalAccessException e) {
+ reportWtf("Class " + clazz.getName() +
+ " must have a public no-arg constructor", e);
+ }
+ return null;
+ }
+
+ private static void reportWtf(String message) {
+ reportWtf(message, null);
+ }
+
+ private static void reportWtf(String message, Throwable e) {
+ Slog.i(TAG, "******************************");
+ Log.wtf(TAG, message, e);
+
+ // Make sure we die
+ throw new RuntimeException(message, e);
+ }
+}
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index 062be01..de912dc 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -34,9 +34,9 @@ import android.content.res.Configuration;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.dreams.Sandman;
@@ -47,9 +47,11 @@ import java.io.PrintWriter;
import com.android.internal.R;
import com.android.internal.app.DisableCarModeActivity;
-import com.android.server.TwilightService.TwilightState;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
-final class UiModeManagerService extends IUiModeManager.Stub {
+final class UiModeManagerService extends SystemService {
private static final String TAG = UiModeManager.class.getSimpleName();
private static final boolean LOG = false;
@@ -57,40 +59,36 @@ final class UiModeManagerService extends IUiModeManager.Stub {
private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true;
private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
- private final Context mContext;
- private final TwilightService mTwilightService;
- private final Handler mHandler = new Handler();
-
final Object mLock = new Object();
-
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ int mNightMode = UiModeManager.MODE_NIGHT_NO;
- private int mNightMode = UiModeManager.MODE_NIGHT_NO;
private boolean mCarModeEnabled = false;
private boolean mCharging = false;
- private final int mDefaultUiModeType;
- private final boolean mCarModeKeepsScreenOn;
- private final boolean mDeskModeKeepsScreenOn;
- private final boolean mTelevision;
-
+ private int mDefaultUiModeType;
+ private boolean mCarModeKeepsScreenOn;
+ private boolean mDeskModeKeepsScreenOn;
+ private boolean mTelevision;
private boolean mComputedNightMode;
- private int mCurUiMode = 0;
- private int mSetUiMode = 0;
+ int mCurUiMode = 0;
+ private int mSetUiMode = 0;
private boolean mHoldingConfiguration = false;
+
private Configuration mConfiguration = new Configuration();
+ boolean mSystemReady;
- private boolean mSystemReady;
+ private final Handler mHandler = new Handler();
+ private TwilightManager mTwilightManager;
private NotificationManager mNotificationManager;
-
private StatusBarManager mStatusBarManager;
- private final PowerManager mPowerManager;
- private final PowerManager.WakeLock mWakeLock;
+ private PowerManager.WakeLock mWakeLock;
- static Intent buildHomeIntent(String category) {
+ private static Intent buildHomeIntent(String category) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(category);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -142,28 +140,26 @@ final class UiModeManagerService extends IUiModeManager.Stub {
}
};
- private final TwilightService.TwilightListener mTwilightListener =
- new TwilightService.TwilightListener() {
+ private final TwilightListener mTwilightListener = new TwilightListener() {
@Override
public void onTwilightStateChanged() {
updateTwilight();
}
};
- public UiModeManagerService(Context context, TwilightService twilight) {
- mContext = context;
- mTwilightService = twilight;
-
- ServiceManager.addService(Context.UI_MODE_SERVICE, this);
-
- mContext.registerReceiver(mDockModeReceiver,
+ @Override
+ public void onStart() {
+ final Context context = getContext();
+ mTwilightManager = getLocalService(TwilightManager.class);
+ final PowerManager powerManager =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+
+ context.registerReceiver(mDockModeReceiver,
new IntentFilter(Intent.ACTION_DOCK_EVENT));
- mContext.registerReceiver(mBatteryReceiver,
+ context.registerReceiver(mBatteryReceiver,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
-
mConfiguration.setToDefaults();
mDefaultUiModeType = context.getResources().getInteger(
@@ -175,101 +171,139 @@ final class UiModeManagerService extends IUiModeManager.Stub {
mTelevision = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEVISION);
- mNightMode = Settings.Secure.getInt(mContext.getContentResolver(),
+ mNightMode = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
- mTwilightService.registerListener(mTwilightListener, mHandler);
+ mTwilightManager.registerListener(mTwilightListener, mHandler);
+
+ publishBinderService(Context.UI_MODE_SERVICE, mService);
}
- @Override // Binder call
- public void disableCarMode(int flags) {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- setCarModeLocked(false);
- if (mSystemReady) {
- updateLocked(0, flags);
+ private final IBinder mService = new IUiModeManager.Stub() {
+ @Override
+ public void enableCarMode(int flags) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setCarModeLocked(true);
+ if (mSystemReady) {
+ updateLocked(flags, 0);
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
- }
- @Override // Binder call
- public void enableCarMode(int flags) {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- setCarModeLocked(true);
- if (mSystemReady) {
- updateLocked(flags, 0);
+ @Override
+ public void disableCarMode(int flags) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setCarModeLocked(false);
+ if (mSystemReady) {
+ updateLocked(0, flags);
+ }
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
- }
- @Override // Binder call
- public int getCurrentModeType() {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mLock) {
- return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+ @Override
+ public int getCurrentModeType() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
- }
- @Override // Binder call
- public void setNightMode(int mode) {
- switch (mode) {
- case UiModeManager.MODE_NIGHT_NO:
- case UiModeManager.MODE_NIGHT_YES:
- case UiModeManager.MODE_NIGHT_AUTO:
- break;
- default:
- throw new IllegalArgumentException("Unknown mode: " + mode);
+ @Override
+ public void setNightMode(int mode) {
+ switch (mode) {
+ case UiModeManager.MODE_NIGHT_NO:
+ case UiModeManager.MODE_NIGHT_YES:
+ case UiModeManager.MODE_NIGHT_AUTO:
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown mode: " + mode);
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (isDoingNightModeLocked() && mNightMode != mode) {
+ Settings.Secure.putInt(getContext().getContentResolver(),
+ Settings.Secure.UI_NIGHT_MODE, mode);
+ mNightMode = mode;
+ updateLocked(0, 0);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
- final long ident = Binder.clearCallingIdentity();
- try {
+ @Override
+ public int getNightMode() {
synchronized (mLock) {
- if (isDoingNightModeLocked() && mNightMode != mode) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.UI_NIGHT_MODE, mode);
- mNightMode = mode;
- updateLocked(0, 0);
- }
+ return mNightMode;
}
- } finally {
- Binder.restoreCallingIdentity(ident);
}
- }
- @Override // Binder call
- public int getNightMode() {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ pw.println("Permission Denial: can't dump uimode service from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ dumpImpl(pw);
+ }
+ };
+
+ void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
- return mNightMode;
+ pw.println("Current UI Mode Service state:");
+ pw.print(" mDockState="); pw.print(mDockState);
+ pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
+ pw.print(" mNightMode="); pw.print(mNightMode);
+ pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
+ pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
+ pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
+ pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
+ pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration);
+ pw.print(" mSystemReady="); pw.println(mSystemReady);
+ pw.print(" mTwilightService.getCurrentState()=");
+ pw.println(mTwilightManager.getCurrentState());
}
}
- void systemReady() {
- synchronized (mLock) {
- mSystemReady = true;
- mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
- updateComputedNightModeLocked();
- updateLocked(0, 0);
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ synchronized (mLock) {
+ mSystemReady = true;
+ mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
+ updateComputedNightModeLocked();
+ updateLocked(0, 0);
+ }
}
}
- private boolean isDoingNightModeLocked() {
+ boolean isDoingNightModeLocked() {
return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
}
- private void setCarModeLocked(boolean enabled) {
+ void setCarModeLocked(boolean enabled) {
if (mCarModeEnabled != enabled) {
mCarModeEnabled = enabled;
}
@@ -344,7 +378,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
}
}
- private void updateLocked(int enableFlags, int disableFlags) {
+ void updateLocked(int enableFlags, int disableFlags) {
String action = null;
String oldAction = null;
if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
@@ -359,7 +393,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
adjustStatusBarCarModeLocked();
if (oldAction != null) {
- mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
}
mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
action = UiModeManager.ACTION_ENTER_CAR_MODE;
@@ -367,7 +401,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
} else if (isDeskDockState(mDockState)) {
if (!isDeskDockState(mLastBroadcastState)) {
if (oldAction != null) {
- mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+ getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
}
mLastBroadcastState = mDockState;
action = UiModeManager.ACTION_ENTER_DESK_MODE;
@@ -393,7 +427,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
Intent intent = new Intent(action);
intent.putExtra("enableFlags", enableFlags);
intent.putExtra("disableFlags", disableFlags);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+ getContext().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
mResultReceiver, null, Activity.RESULT_OK, null, null);
// Attempting to make this transition a little more clean, we are going
@@ -491,7 +525,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
// activity manager take care of both the start and config
// change.
Intent homeIntent = buildHomeIntent(category);
- if (Sandman.shouldStartDockApp(mContext, homeIntent)) {
+ if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
try {
int result = ActivityManagerNative.getDefault().startActivityWithConfig(
null, null, homeIntent, null, null, null, 0, 0,
@@ -513,14 +547,15 @@ final class UiModeManagerService extends IUiModeManager.Stub {
// If we did not start a dock app, then start dreaming if supported.
if (category != null && !dockAppStarted) {
- Sandman.startDreamWhenDockedIfAppropriate(mContext);
+ Sandman.startDreamWhenDockedIfAppropriate(getContext());
}
}
private void adjustStatusBarCarModeLocked() {
+ final Context context = getContext();
if (mStatusBarManager == null) {
mStatusBarManager = (StatusBarManager)
- mContext.getSystemService(Context.STATUS_BAR_SERVICE);
+ context.getSystemService(Context.STATUS_BAR_SERVICE);
}
// Fear not: StatusBarManagerService manages a list of requests to disable
@@ -536,12 +571,12 @@ final class UiModeManagerService extends IUiModeManager.Stub {
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
}
if (mNotificationManager != null) {
if (mCarModeEnabled) {
- Intent carModeOffIntent = new Intent(mContext, DisableCarModeActivity.class);
+ Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class);
Notification n = new Notification();
n.icon = R.drawable.stat_notify_car_mode;
@@ -549,10 +584,10 @@ final class UiModeManagerService extends IUiModeManager.Stub {
n.flags = Notification.FLAG_ONGOING_EVENT;
n.when = 0;
n.setLatestEventInfo(
- mContext,
- mContext.getString(R.string.car_mode_disable_notification_title),
- mContext.getString(R.string.car_mode_disable_notification_message),
- PendingIntent.getActivityAsUser(mContext, 0, carModeOffIntent, 0,
+ context,
+ context.getString(R.string.car_mode_disable_notification_title),
+ context.getString(R.string.car_mode_disable_notification_message),
+ PendingIntent.getActivityAsUser(context, 0, carModeOffIntent, 0,
null, UserHandle.CURRENT));
mNotificationManager.notifyAsUser(null,
R.string.car_mode_disable_notification_title, n, UserHandle.ALL);
@@ -563,7 +598,7 @@ final class UiModeManagerService extends IUiModeManager.Stub {
}
}
- private void updateTwilight() {
+ void updateTwilight() {
synchronized (mLock) {
if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
updateComputedNightModeLocked();
@@ -573,36 +608,11 @@ final class UiModeManagerService extends IUiModeManager.Stub {
}
private void updateComputedNightModeLocked() {
- TwilightState state = mTwilightService.getCurrentState();
+ TwilightState state = mTwilightManager.getCurrentState();
if (state != null) {
mComputedNightMode = state.isNight();
}
}
- @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 uimode service from from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- return;
- }
-
- synchronized (mLock) {
- pw.println("Current UI Mode Service state:");
- pw.print(" mDockState="); pw.print(mDockState);
- pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
- pw.print(" mNightMode="); pw.print(mNightMode);
- pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
- pw.print(" mComputedNightMode="); pw.println(mComputedNightMode);
- pw.print(" mCurUiMode=0x"); pw.print(Integer.toHexString(mCurUiMode));
- pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
- pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration);
- pw.print(" mSystemReady="); pw.println(mSystemReady);
- pw.print(" mTwilightService.getCurrentState()=");
- pw.println(mTwilightService.getCurrentState());
- }
- }
}
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index 3e90078..e0d6505 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -76,9 +76,6 @@ public class Watchdog extends Thread {
final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>();
final HandlerChecker mMonitorChecker;
ContentResolver mResolver;
- BatteryService mBattery;
- PowerManagerService mPower;
- AlarmManagerService mAlarm;
ActivityManagerService mActivity;
int mPhonePid;
@@ -230,13 +227,8 @@ public class Watchdog extends Thread {
"i/o thread", DEFAULT_TIMEOUT));
}
- public void init(Context context, BatteryService battery,
- PowerManagerService power, AlarmManagerService alarm,
- ActivityManagerService activity) {
+ public void init(Context context, ActivityManagerService activity) {
mResolver = context.getContentResolver();
- mBattery = battery;
- mPower = power;
- mAlarm = alarm;
mActivity = activity;
context.registerReceiver(new RebootRequestReceiver(),
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 2e914aa..c3ecf5f 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -39,7 +39,6 @@ import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
import android.os.Trace;
import android.util.Log;
import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.util.Objects;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.wm.AppTransition;
@@ -84,6 +83,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
/**
* State and management of a single stack of activities.
@@ -1769,6 +1769,21 @@ final class ActivityStack {
r.putInHistory();
r.frontOfTask = newTask;
+ if (!r.frontOfTask) {
+ // It is possible that the current frontOfTask activity is finishing. Check for that.
+ ArrayList<ActivityRecord> activities = task.mActivities;
+ for (int activityNdx = 0; activityNdx < activities.size(); ++activityNdx) {
+ final ActivityRecord activity = activities.get(activityNdx);
+ if (activity.finishing) {
+ continue;
+ }
+ if (activity == r) {
+ // All activities before r are finishing.
+ r.frontOfTask = true;
+ }
+ break;
+ }
+ }
if (!isHomeStack() || numActivities() > 0) {
// We want to show the starting preview window if we are
// switching to a new task, or the next activity's process is
@@ -2377,7 +2392,7 @@ final class ActivityStack {
ArrayList<ActivityRecord> activities = r.task.mActivities;
for (int index = activities.indexOf(r); index >= 0; --index) {
ActivityRecord cur = activities.get(index);
- if (!Objects.equal(cur.taskAffinity, r.taskAffinity)) {
+ if (!Objects.equals(cur.taskAffinity, r.taskAffinity)) {
break;
}
finishActivityLocked(cur, Activity.RESULT_CANCELED, null, "request-affinity", true);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 80e6e94..cb04835 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -18,7 +18,8 @@ package com.android.server.am;
import com.android.internal.app.ProcessStats;
import com.android.internal.os.BatteryStatsImpl;
-import com.android.server.NotificationManagerService;
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
import android.app.INotificationManager;
import android.app.Notification;
@@ -427,8 +428,8 @@ final class ServiceRecord extends Binder {
final Notification localForegroundNoti = foregroundNoti;
ams.mHandler.post(new Runnable() {
public void run() {
- NotificationManagerService nm =
- (NotificationManagerService) NotificationManager.getService();
+ NotificationManagerInternal nm = LocalServices.getService(
+ NotificationManagerInternal.class);
if (nm == null) {
return;
}
@@ -479,7 +480,7 @@ final class ServiceRecord extends Binder {
throw new RuntimeException("icon must be non-zero");
}
int[] outId = new int[1];
- nm.enqueueNotificationInternal(localPackageName, localPackageName,
+ nm.enqueueNotification(localPackageName, localPackageName,
appUid, appPid, null, localForegroundId, localForegroundNoti,
outId, userId);
} catch (RuntimeException e) {
diff --git a/services/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 36ce3a4..186fbe1 100644
--- a/services/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -108,7 +108,7 @@ import java.util.Set;
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
- private static final String TAG = "DevicePolicyManagerService";
+ private static final String LOG_TAG = "DevicePolicyManagerService";
private static final String DEVICE_POLICIES_XML = "device_policies.xml";
@@ -177,7 +177,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
getSendingUserId());
if (Intent.ACTION_BOOT_COMPLETED.equals(action)
|| ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
- if (DBG) Slog.v(TAG, "Sending password expiration notifications for action "
+ if (DBG) Slog.v(LOG_TAG, "Sending password expiration notifications for action "
+ action + " for user " + userHandle);
mHandler.post(new Runnable() {
public void run() {
@@ -209,6 +209,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
};
static class ActiveAdmin {
+ private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
+ private static final String TAG_DISABLE_CAMERA = "disable-camera";
+ private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested";
+ private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date";
+ private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout";
+ private static final String TAG_GLOBAL_PROXY_EXCLUSION_LIST = "global-proxy-exclusion-list";
+ private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
+ private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
+ private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
+ private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
+ private static final String TAG_MIN_PASSWORD_NONLETTER = "min-password-nonletter";
+ private static final String TAG_MIN_PASSWORD_SYMBOLS = "min-password-symbols";
+ private static final String TAG_MIN_PASSWORD_NUMERIC = "min-password-numeric";
+ private static final String TAG_MIN_PASSWORD_LETTERS = "min-password-letters";
+ private static final String TAG_MIN_PASSWORD_LOWERCASE = "min-password-lowercase";
+ private static final String TAG_MIN_PASSWORD_UPPERCASE = "min-password-uppercase";
+ private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
+ private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
+ private static final String ATTR_VALUE = "value";
+ private static final String TAG_PASSWORD_QUALITY = "password-quality";
+ private static final String TAG_POLICIES = "policies";
+
final DeviceAdminInfo info;
int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -272,103 +294,103 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
void writeToXml(XmlSerializer out)
throws IllegalArgumentException, IllegalStateException, IOException {
- out.startTag(null, "policies");
+ out.startTag(null, TAG_POLICIES);
info.writePoliciesToXml(out);
- out.endTag(null, "policies");
+ out.endTag(null, TAG_POLICIES);
if (passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- out.startTag(null, "password-quality");
- out.attribute(null, "value", Integer.toString(passwordQuality));
- out.endTag(null, "password-quality");
+ out.startTag(null, TAG_PASSWORD_QUALITY);
+ out.attribute(null, ATTR_VALUE, Integer.toString(passwordQuality));
+ out.endTag(null, TAG_PASSWORD_QUALITY);
if (minimumPasswordLength != DEF_MINIMUM_PASSWORD_LENGTH) {
- out.startTag(null, "min-password-length");
- out.attribute(null, "value", Integer.toString(minimumPasswordLength));
- out.endTag(null, "min-password-length");
+ out.startTag(null, TAG_MIN_PASSWORD_LENGTH);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLength));
+ out.endTag(null, TAG_MIN_PASSWORD_LENGTH);
}
if(passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
- out.startTag(null, "password-history-length");
- out.attribute(null, "value", Integer.toString(passwordHistoryLength));
- out.endTag(null, "password-history-length");
+ out.startTag(null, TAG_PASSWORD_HISTORY_LENGTH);
+ out.attribute(null, ATTR_VALUE, Integer.toString(passwordHistoryLength));
+ out.endTag(null, TAG_PASSWORD_HISTORY_LENGTH);
}
if (minimumPasswordUpperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
- out.startTag(null, "min-password-uppercase");
- out.attribute(null, "value", Integer.toString(minimumPasswordUpperCase));
- out.endTag(null, "min-password-uppercase");
+ out.startTag(null, TAG_MIN_PASSWORD_UPPERCASE);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordUpperCase));
+ out.endTag(null, TAG_MIN_PASSWORD_UPPERCASE);
}
if (minimumPasswordLowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
- out.startTag(null, "min-password-lowercase");
- out.attribute(null, "value", Integer.toString(minimumPasswordLowerCase));
- out.endTag(null, "min-password-lowercase");
+ out.startTag(null, TAG_MIN_PASSWORD_LOWERCASE);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLowerCase));
+ out.endTag(null, TAG_MIN_PASSWORD_LOWERCASE);
}
if (minimumPasswordLetters != DEF_MINIMUM_PASSWORD_LETTERS) {
- out.startTag(null, "min-password-letters");
- out.attribute(null, "value", Integer.toString(minimumPasswordLetters));
- out.endTag(null, "min-password-letters");
+ out.startTag(null, TAG_MIN_PASSWORD_LETTERS);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLetters));
+ out.endTag(null, TAG_MIN_PASSWORD_LETTERS);
}
if (minimumPasswordNumeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
- out.startTag(null, "min-password-numeric");
- out.attribute(null, "value", Integer.toString(minimumPasswordNumeric));
- out.endTag(null, "min-password-numeric");
+ out.startTag(null, TAG_MIN_PASSWORD_NUMERIC);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordNumeric));
+ out.endTag(null, TAG_MIN_PASSWORD_NUMERIC);
}
if (minimumPasswordSymbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
- out.startTag(null, "min-password-symbols");
- out.attribute(null, "value", Integer.toString(minimumPasswordSymbols));
- out.endTag(null, "min-password-symbols");
+ out.startTag(null, TAG_MIN_PASSWORD_SYMBOLS);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordSymbols));
+ out.endTag(null, TAG_MIN_PASSWORD_SYMBOLS);
}
if (minimumPasswordNonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
- out.startTag(null, "min-password-nonletter");
- out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter));
- out.endTag(null, "min-password-nonletter");
+ out.startTag(null, TAG_MIN_PASSWORD_NONLETTER);
+ out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordNonLetter));
+ out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
}
}
if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
- out.startTag(null, "max-time-to-unlock");
- out.attribute(null, "value", Long.toString(maximumTimeToUnlock));
- out.endTag(null, "max-time-to-unlock");
+ out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
+ out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
+ out.endTag(null, TAG_MAX_TIME_TO_UNLOCK);
}
if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
- out.startTag(null, "max-failed-password-wipe");
- out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe));
- out.endTag(null, "max-failed-password-wipe");
+ out.startTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
+ out.attribute(null, ATTR_VALUE, Integer.toString(maximumFailedPasswordsForWipe));
+ out.endTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
}
if (specifiesGlobalProxy) {
- out.startTag(null, "specifies-global-proxy");
- out.attribute(null, "value", Boolean.toString(specifiesGlobalProxy));
- out.endTag(null, "specifies_global_proxy");
+ out.startTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(specifiesGlobalProxy));
+ out.endTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
if (globalProxySpec != null) {
- out.startTag(null, "global-proxy-spec");
- out.attribute(null, "value", globalProxySpec);
- out.endTag(null, "global-proxy-spec");
+ out.startTag(null, TAG_GLOBAL_PROXY_SPEC);
+ out.attribute(null, ATTR_VALUE, globalProxySpec);
+ out.endTag(null, TAG_GLOBAL_PROXY_SPEC);
}
if (globalProxyExclusionList != null) {
- out.startTag(null, "global-proxy-exclusion-list");
- out.attribute(null, "value", globalProxyExclusionList);
- out.endTag(null, "global-proxy-exclusion-list");
+ out.startTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
+ out.attribute(null, ATTR_VALUE, globalProxyExclusionList);
+ out.endTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
}
}
if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
- out.startTag(null, "password-expiration-timeout");
- out.attribute(null, "value", Long.toString(passwordExpirationTimeout));
- out.endTag(null, "password-expiration-timeout");
+ out.startTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
+ out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationTimeout));
+ out.endTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
}
if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
- out.startTag(null, "password-expiration-date");
- out.attribute(null, "value", Long.toString(passwordExpirationDate));
- out.endTag(null, "password-expiration-date");
+ out.startTag(null, TAG_PASSWORD_EXPIRATION_DATE);
+ out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationDate));
+ out.endTag(null, TAG_PASSWORD_EXPIRATION_DATE);
}
if (encryptionRequested) {
- out.startTag(null, "encryption-requested");
- out.attribute(null, "value", Boolean.toString(encryptionRequested));
- out.endTag(null, "encryption-requested");
+ out.startTag(null, TAG_ENCRYPTION_REQUESTED);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(encryptionRequested));
+ out.endTag(null, TAG_ENCRYPTION_REQUESTED);
}
if (disableCamera) {
- out.startTag(null, "disable-camera");
- out.attribute(null, "value", Boolean.toString(disableCamera));
- out.endTag(null, "disable-camera");
+ out.startTag(null, TAG_DISABLE_CAMERA);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(disableCamera));
+ out.endTag(null, TAG_DISABLE_CAMERA);
}
if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
- out.startTag(null, "disable-keyguard-features");
- out.attribute(null, "value", Integer.toString(disabledKeyguardFeatures));
- out.endTag(null, "disable-keyguard-features");
+ out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
+ out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures));
+ out.endTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
}
}
@@ -382,67 +404,67 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
continue;
}
String tag = parser.getName();
- if ("policies".equals(tag)) {
+ if (TAG_POLICIES.equals(tag)) {
info.readPoliciesFromXml(parser);
- } else if ("password-quality".equals(tag)) {
+ } else if (TAG_PASSWORD_QUALITY.equals(tag)) {
passwordQuality = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-length".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
minimumPasswordLength = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("password-history-length".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
passwordHistoryLength = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-uppercase".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
minimumPasswordUpperCase = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-lowercase".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
minimumPasswordLowerCase = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-letters".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
minimumPasswordLetters = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-numeric".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
minimumPasswordNumeric = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-symbols".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
minimumPasswordSymbols = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("min-password-nonletter".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
minimumPasswordNonLetter = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("max-time-to-unlock".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
- parser.getAttributeValue(null, "value"));
- } else if ("max-failed-password-wipe".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_MAX_FAILED_PASSWORD_WIPE.equals(tag)) {
maximumFailedPasswordsForWipe = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
- } else if ("specifies-global-proxy".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_SPECIFIES_GLOBAL_PROXY.equals(tag)) {
specifiesGlobalProxy = Boolean.parseBoolean(
- parser.getAttributeValue(null, "value"));
- } else if ("global-proxy-spec".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_GLOBAL_PROXY_SPEC.equals(tag)) {
globalProxySpec =
- parser.getAttributeValue(null, "value");
- } else if ("global-proxy-exclusion-list".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE);
+ } else if (TAG_GLOBAL_PROXY_EXCLUSION_LIST.equals(tag)) {
globalProxyExclusionList =
- parser.getAttributeValue(null, "value");
- } else if ("password-expiration-timeout".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE);
+ } else if (TAG_PASSWORD_EXPIRATION_TIMEOUT.equals(tag)) {
passwordExpirationTimeout = Long.parseLong(
- parser.getAttributeValue(null, "value"));
- } else if ("password-expiration-date".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_PASSWORD_EXPIRATION_DATE.equals(tag)) {
passwordExpirationDate = Long.parseLong(
- parser.getAttributeValue(null, "value"));
- } else if ("encryption-requested".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_ENCRYPTION_REQUESTED.equals(tag)) {
encryptionRequested = Boolean.parseBoolean(
- parser.getAttributeValue(null, "value"));
- } else if ("disable-camera".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_DISABLE_CAMERA.equals(tag)) {
disableCamera = Boolean.parseBoolean(
- parser.getAttributeValue(null, "value"));
- } else if ("disable-keyguard-features".equals(tag)) {
+ parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
disabledKeyguardFeatures = Integer.parseInt(
- parser.getAttributeValue(null, "value"));
+ parser.getAttributeValue(null, ATTR_VALUE));
} else {
- Slog.w(TAG, "Unknown admin tag: " + tag);
+ Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
}
XmlUtils.skipCurrentTag(parser);
}
@@ -504,7 +526,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void handlePackagesChanged(int userHandle) {
boolean removed = false;
- if (DBG) Slog.d(TAG, "Handling package changes for user " + userHandle);
+ if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
DevicePolicyData policy = getUserData(userHandle);
IPackageManager pm = AppGlobals.getPackageManager();
for (int i = policy.mAdminList.size() - 1; i >= 0; i--) {
@@ -576,7 +598,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
void removeUserData(int userHandle) {
synchronized (this) {
if (userHandle == UserHandle.USER_OWNER) {
- Slog.w(TAG, "Tried to remove device policy file for user 0! Ignoring.");
+ Slog.w(LOG_TAG, "Tried to remove device policy file for user 0! Ignoring.");
return;
}
DevicePolicyData policy = mUserData.get(userHandle);
@@ -586,7 +608,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
File policyFile = new File(Environment.getUserSystemDirectory(userHandle),
DEVICE_POLICIES_XML);
policyFile.delete();
- Slog.i(TAG, "Removed device policy file " + policyFile.getAbsolutePath());
+ Slog.i(LOG_TAG, "Removed device policy file " + policyFile.getAbsolutePath());
}
}
@@ -783,10 +805,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
return new DeviceAdminInfo(mContext, infos.get(0));
} catch (XmlPullParserException e) {
- Slog.w(TAG, "Bad device admin requested for user=" + userHandle + ": " + adminName, e);
+ Slog.w(LOG_TAG, "Bad device admin requested for user=" + userHandle + ": " + adminName,
+ e);
return null;
} catch (IOException e) {
- Slog.w(TAG, "Bad device admin requested for user=" + userHandle + ": " + adminName, e);
+ Slog.w(LOG_TAG, "Bad device admin requested for user=" + userHandle + ": " + adminName,
+ e);
return null;
}
}
@@ -913,7 +937,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ComponentName.unflattenFromString(name), userHandle);
if (DBG && (UserHandle.getUserId(dai.getActivityInfo().applicationInfo.uid)
!= userHandle)) {
- Slog.w(TAG, "findAdmin returned an incorrect uid "
+ Slog.w(LOG_TAG, "findAdmin returned an incorrect uid "
+ dai.getActivityInfo().applicationInfo.uid + " for user "
+ userHandle);
}
@@ -924,7 +948,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.mAdminList.add(ap);
}
} catch (RuntimeException e) {
- Slog.w(TAG, "Failed loading admin " + name, e);
+ Slog.w(LOG_TAG, "Failed loading admin " + name, e);
}
} else if ("failed-password-attempts".equals(tag)) {
policy.mFailedPasswordAttempts = Integer.parseInt(
@@ -953,22 +977,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
parser.getAttributeValue(null, "nonletter"));
XmlUtils.skipCurrentTag(parser);
} else {
- Slog.w(TAG, "Unknown tag: " + tag);
+ Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
}
}
} catch (NullPointerException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
+ Slog.w(LOG_TAG, "failed parsing " + file + " " + e);
} catch (NumberFormatException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
+ Slog.w(LOG_TAG, "failed parsing " + file + " " + e);
} catch (XmlPullParserException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
+ Slog.w(LOG_TAG, "failed parsing " + file + " " + e);
} catch (FileNotFoundException e) {
// Don't be noisy, this is normal if we haven't defined any policies.
} catch (IOException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
+ Slog.w(LOG_TAG, "failed parsing " + file + " " + e);
} catch (IndexOutOfBoundsException e) {
- Slog.w(TAG, "failed parsing " + file + " " + e);
+ Slog.w(LOG_TAG, "failed parsing " + file + " " + e);
}
try {
if (stream != null) {
@@ -984,7 +1008,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// never normally happen.
LockPatternUtils utils = new LockPatternUtils(mContext);
if (utils.getActivePasswordQuality() < policy.mActivePasswordQuality) {
- Slog.w(TAG, "Active password quality 0x"
+ Slog.w(LOG_TAG, "Active password quality 0x"
+ Integer.toHexString(policy.mActivePasswordQuality)
+ " does not match actual quality 0x"
+ Integer.toHexString(utils.getActivePasswordQuality()));
@@ -1028,7 +1052,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
if (!haveOwner) {
- Slog.w(TAG, "Previous password owner " + policy.mPasswordOwner
+ Slog.w(LOG_TAG, "Previous password owner " + policy.mPasswordOwner
+ " no longer active; disabling");
policy.mPasswordOwner = -1;
}
@@ -1048,7 +1072,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
long token = Binder.clearCallingIdentity();
try {
String value = cameraDisabled ? "1" : "0";
- if (DBG) Slog.v(TAG, "Change in camera state ["
+ if (DBG) Slog.v(LOG_TAG, "Change in camera state ["
+ SYSTEM_PROP_DISABLE_CAMERA + "] = " + value);
SystemProperties.set(SYSTEM_PROP_DISABLE_CAMERA, value);
} finally {
@@ -1164,7 +1188,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
- if (!refreshing && getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null) {
+ if (!refreshing
+ && getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null) {
throw new IllegalArgumentException("Admin is already added");
}
ActiveAdmin newAdmin = new ActiveAdmin(info);
@@ -1434,7 +1459,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ap.passwordExpirationDate = expiration;
ap.passwordExpirationTimeout = timeout;
if (timeout > 0L) {
- Slog.w(TAG, "setPasswordExpiration(): password will expire on "
+ Slog.w(LOG_TAG, "setPasswordExpiration(): password will expire on "
+ DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)
.format(new Date(expiration)));
}
@@ -1780,11 +1805,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
return policy.mActivePasswordUpperCase >= getPasswordMinimumUpperCase(null, userHandle)
- && policy.mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null, userHandle)
- && policy.mActivePasswordLetters >= getPasswordMinimumLetters(null, userHandle)
- && policy.mActivePasswordNumeric >= getPasswordMinimumNumeric(null, userHandle)
- && policy.mActivePasswordSymbols >= getPasswordMinimumSymbols(null, userHandle)
- && policy.mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null, userHandle);
+ && policy.mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null, userHandle)
+ && policy.mActivePasswordLetters >= getPasswordMinimumLetters(null, userHandle)
+ && policy.mActivePasswordNumeric >= getPasswordMinimumNumeric(null, userHandle)
+ && policy.mActivePasswordSymbols >= getPasswordMinimumSymbols(null, userHandle)
+ && policy.mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null, userHandle);
}
}
@@ -1862,7 +1887,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int realQuality = LockPatternUtils.computePasswordQuality(password);
if (realQuality < quality
&& quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
- Slog.w(TAG, "resetPassword: password quality 0x"
+ Slog.w(LOG_TAG, "resetPassword: password quality 0x"
+ Integer.toHexString(realQuality)
+ " does not meet required quality 0x"
+ Integer.toHexString(quality));
@@ -1872,7 +1897,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
int length = getPasswordMinimumLength(null, userHandle);
if (password.length() < length) {
- Slog.w(TAG, "resetPassword: password length " + password.length()
+ Slog.w(LOG_TAG, "resetPassword: password length " + password.length()
+ " does not meet required length " + length);
return false;
}
@@ -1901,40 +1926,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
int neededLetters = getPasswordMinimumLetters(null, userHandle);
if(letters < neededLetters) {
- Slog.w(TAG, "resetPassword: number of letters " + letters
+ Slog.w(LOG_TAG, "resetPassword: number of letters " + letters
+ " does not meet required number of letters " + neededLetters);
return false;
}
int neededNumbers = getPasswordMinimumNumeric(null, userHandle);
if (numbers < neededNumbers) {
- Slog.w(TAG, "resetPassword: number of numerical digits " + numbers
+ Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + numbers
+ " does not meet required number of numerical digits "
+ neededNumbers);
return false;
}
int neededLowerCase = getPasswordMinimumLowerCase(null, userHandle);
if (lowercase < neededLowerCase) {
- Slog.w(TAG, "resetPassword: number of lowercase letters " + lowercase
+ Slog.w(LOG_TAG, "resetPassword: number of lowercase letters " + lowercase
+ " does not meet required number of lowercase letters "
+ neededLowerCase);
return false;
}
int neededUpperCase = getPasswordMinimumUpperCase(null, userHandle);
if (uppercase < neededUpperCase) {
- Slog.w(TAG, "resetPassword: number of uppercase letters " + uppercase
+ Slog.w(LOG_TAG, "resetPassword: number of uppercase letters " + uppercase
+ " does not meet required number of uppercase letters "
+ neededUpperCase);
return false;
}
int neededSymbols = getPasswordMinimumSymbols(null, userHandle);
if (symbols < neededSymbols) {
- Slog.w(TAG, "resetPassword: number of special symbols " + symbols
+ Slog.w(LOG_TAG, "resetPassword: number of special symbols " + symbols
+ " does not meet required number of special symbols " + neededSymbols);
return false;
}
int neededNonLetter = getPasswordMinimumNonLetter(null, userHandle);
if (nonletter < neededNonLetter) {
- Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter
+ Slog.w(LOG_TAG, "resetPassword: number of non-letter characters " + nonletter
+ " does not meet required number of non-letter characters "
+ neededNonLetter);
return false;
@@ -1945,7 +1970,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int callingUid = Binder.getCallingUid();
DevicePolicyData policy = getUserData(userHandle);
if (policy.mPasswordOwner >= 0 && policy.mPasswordOwner != callingUid) {
- Slog.w(TAG, "resetPassword: already set by another uid and not entered by user");
+ Slog.w(LOG_TAG, "resetPassword: already set by another uid and not entered by user");
return false;
}
@@ -2011,7 +2036,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
getIPowerManager().setMaximumScreenOffTimeoutFromDeviceAdmin((int)timeMs);
} catch (RemoteException e) {
- Slog.w(TAG, "Failure talking with power manager", e);
+ Slog.w(LOG_TAG, "Failure talking with power manager", e);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2086,10 +2111,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
X509Certificate cert = parseCert(certBuffer);
pemCert = Credentials.convertToPem(cert);
} catch (CertificateException ce) {
- Log.e(TAG, "Problem converting cert", ce);
+ Log.e(LOG_TAG, "Problem converting cert", ce);
return false;
} catch (IOException ioe) {
- Log.e(TAG, "Problem reading cert", ioe);
+ Log.e(LOG_TAG, "Problem reading cert", ioe);
return false;
}
try {
@@ -2104,7 +2129,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
} catch (InterruptedException e1) {
- Log.w(TAG, "installCaCertsToKeyChain(): ", e1);
+ Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
Thread.currentThread().interrupt();
}
return false;
@@ -2125,10 +2150,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
X509Certificate cert = parseCert(certBuffer);
alias = certStore.getCertificateAlias(cert);
} catch (CertificateException ce) {
- Log.e(TAG, "Problem creating X509Certificate", ce);
+ Log.e(LOG_TAG, "Problem creating X509Certificate", ce);
return;
} catch (IOException ioe) {
- Log.e(TAG, "Problem reading certificate", ioe);
+ Log.e(LOG_TAG, "Problem reading certificate", ioe);
return;
}
try {
@@ -2137,13 +2162,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
service.deleteCaCertificate(alias);
} catch (RemoteException e) {
- Log.e(TAG, "from CaCertUninstaller: ", e);
+ Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
} finally {
keyChainConnection.close();
keyChainConnection = null;
}
} catch (InterruptedException ie) {
- Log.w(TAG, "CaCertUninstaller: ", ie);
+ Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
Thread.currentThread().interrupt();
}
}
@@ -2164,7 +2189,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
RecoverySystem.rebootWipeUserData(mContext);
} catch (IOException e) {
- Slog.w(TAG, "Failed requesting data wipe", e);
+ Slog.w(LOG_TAG, "Failed requesting data wipe", e);
}
}
}
@@ -2255,8 +2280,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (p.mActivePasswordQuality != quality || p.mActivePasswordLength != length
|| p.mFailedPasswordAttempts != 0 || p.mActivePasswordLetters != letters
|| p.mActivePasswordUpperCase != uppercase
- || p.mActivePasswordLowerCase != lowercase || p.mActivePasswordNumeric != numbers
- || p.mActivePasswordSymbols != symbols || p.mActivePasswordNonLetter != nonletter) {
+ || p.mActivePasswordLowerCase != lowercase
+ || p.mActivePasswordNumeric != numbers
+ || p.mActivePasswordSymbols != symbols
+ || p.mActivePasswordNonLetter != nonletter) {
long ident = Binder.clearCallingIdentity();
try {
p.mActivePasswordQuality = quality;
@@ -2378,7 +2405,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// If the user is not the owner, don't set the global proxy. Fail silently.
if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
- Slog.w(TAG, "Only the owner is allowed to set the global proxy. User "
+ Slog.w(LOG_TAG, "Only the owner is allowed to set the global proxy. User "
+ userHandle + " is not permitted.");
return null;
}
@@ -2459,7 +2486,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ProxyProperties proxyProperties = new ProxyProperties(data[0], proxyPort, exclusionList);
if (!proxyProperties.isValid()) {
- Slog.e(TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ Slog.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
return;
}
Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
@@ -2485,7 +2512,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Only owner can set storage encryption
if (userHandle != UserHandle.USER_OWNER
|| UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
- Slog.w(TAG, "Only owner is allowed to set storage encryption. User "
+ Slog.w(LOG_TAG, "Only owner is allowed to set storage encryption. User "
+ UserHandle.getCallingUserId() + " is not permitted.");
return 0;
}
@@ -2871,7 +2898,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
} catch (NameNotFoundException nnfe) {
- Slog.w(TAG, "Device Owner package " + packageName + " not installed.");
+ Slog.w(LOG_TAG, "Device Owner package " + packageName + " not installed.");
}
return false;
}
@@ -2896,9 +2923,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mOwnerName = parser.getAttributeValue(null, ATTR_NAME);
input.close();
} catch (XmlPullParserException xppe) {
- Slog.e(TAG, "Error parsing device-owner file\n" + xppe);
+ Slog.e(LOG_TAG, "Error parsing device-owner file\n" + xppe);
} catch (IOException ioe) {
- Slog.e(TAG, "IO Exception when reading device-owner file\n" + ioe);
+ Slog.e(LOG_TAG, "IO Exception when reading device-owner file\n" + ioe);
}
}
@@ -2926,7 +2953,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.flush();
file.finishWrite(output);
} catch (IOException ioe) {
- Slog.e(TAG, "IO Exception when writing device-owner file\n" + ioe);
+ Slog.e(LOG_TAG, "IO Exception when writing device-owner file\n" + ioe);
}
}
}
diff --git a/services/java/com/android/server/lights/Light.java b/services/java/com/android/server/lights/Light.java
new file mode 100644
index 0000000..b496b4c
--- /dev/null
+++ b/services/java/com/android/server/lights/Light.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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.lights;
+
+public abstract class Light {
+ public static final int LIGHT_FLASH_NONE = 0;
+ public static final int LIGHT_FLASH_TIMED = 1;
+ public static final int LIGHT_FLASH_HARDWARE = 2;
+
+ /**
+ * Light brightness is managed by a user setting.
+ */
+ public static final int BRIGHTNESS_MODE_USER = 0;
+
+ /**
+ * Light brightness is managed by a light sensor.
+ */
+ public static final int BRIGHTNESS_MODE_SENSOR = 1;
+
+ public abstract void setBrightness(int brightness);
+ public abstract void setBrightness(int brightness, int brightnessMode);
+ public abstract void setColor(int color);
+ public abstract void setFlashing(int color, int mode, int onMS, int offMS);
+ public abstract void pulse();
+ public abstract void pulse(int color, int onMS);
+ public abstract void turnOff();
+} \ No newline at end of file
diff --git a/services/java/com/android/server/lights/LightsManager.java b/services/java/com/android/server/lights/LightsManager.java
new file mode 100644
index 0000000..2f20509
--- /dev/null
+++ b/services/java/com/android/server/lights/LightsManager.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013 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.lights;
+
+public abstract class LightsManager {
+ public static final int LIGHT_ID_BACKLIGHT = 0;
+ public static final int LIGHT_ID_KEYBOARD = 1;
+ public static final int LIGHT_ID_BUTTONS = 2;
+ public static final int LIGHT_ID_BATTERY = 3;
+ public static final int LIGHT_ID_NOTIFICATIONS = 4;
+ public static final int LIGHT_ID_ATTENTION = 5;
+ public static final int LIGHT_ID_BLUETOOTH = 6;
+ public static final int LIGHT_ID_WIFI = 7;
+ public static final int LIGHT_ID_COUNT = 8;
+
+ public abstract Light getLight(int id);
+}
diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/lights/LightsService.java
index 89bfcac..d814785 100644
--- a/services/java/com/android/server/LightsService.java
+++ b/services/java/com/android/server/lights/LightsService.java
@@ -14,59 +14,38 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.lights;
+
+import com.android.server.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IHardwareService;
-import android.os.ServiceManager;
import android.os.Message;
import android.util.Slog;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-public class LightsService {
- private static final String TAG = "LightsService";
- private static final boolean DEBUG = false;
-
- public static final int LIGHT_ID_BACKLIGHT = 0;
- public static final int LIGHT_ID_KEYBOARD = 1;
- public static final int LIGHT_ID_BUTTONS = 2;
- public static final int LIGHT_ID_BATTERY = 3;
- public static final int LIGHT_ID_NOTIFICATIONS = 4;
- public static final int LIGHT_ID_ATTENTION = 5;
- public static final int LIGHT_ID_BLUETOOTH = 6;
- public static final int LIGHT_ID_WIFI = 7;
- public static final int LIGHT_ID_COUNT = 8;
-
- public static final int LIGHT_FLASH_NONE = 0;
- public static final int LIGHT_FLASH_TIMED = 1;
- public static final int LIGHT_FLASH_HARDWARE = 2;
-
- /**
- * Light brightness is managed by a user setting.
- */
- public static final int BRIGHTNESS_MODE_USER = 0;
-
- /**
- * Light brightness is managed by a light sensor.
- */
- public static final int BRIGHTNESS_MODE_SENSOR = 1;
+public class LightsService extends SystemService {
+ static final String TAG = "LightsService";
+ static final boolean DEBUG = false;
- private final Light mLights[] = new Light[LIGHT_ID_COUNT];
+ final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT];
- public final class Light {
+ private final class LightImpl extends Light {
- private Light(int id) {
+ private LightImpl(int id) {
mId = id;
}
+ @Override
public void setBrightness(int brightness) {
setBrightness(brightness, BRIGHTNESS_MODE_USER);
}
+ @Override
public void setBrightness(int brightness, int brightnessMode) {
synchronized (this) {
int color = brightness & 0x000000ff;
@@ -75,23 +54,26 @@ public class LightsService {
}
}
+ @Override
public void setColor(int color) {
synchronized (this) {
setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, 0);
}
}
+ @Override
public void setFlashing(int color, int mode, int onMS, int offMS) {
synchronized (this) {
setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER);
}
}
-
+ @Override
public void pulse() {
pulse(0x00ffffff, 7);
}
+ @Override
public void pulse(int color, int onMS) {
synchronized (this) {
if (mColor == 0 && !mFlashing) {
@@ -101,6 +83,7 @@ public class LightsService {
}
}
+ @Override
public void turnOff() {
synchronized (this) {
setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, 0);
@@ -153,9 +136,10 @@ public class LightsService {
}
public void setFlashlightEnabled(boolean on) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
+ final Context context = getContext();
+ if (context.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
!= PackageManager.PERMISSION_GRANTED &&
- mContext.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
+ context.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission");
}
@@ -172,31 +156,41 @@ public class LightsService {
}
};
- LightsService(Context context) {
-
+ @Override
+ public void onCreate(Context context) {
mNativePointer = init_native();
- mContext = context;
-
- ServiceManager.addService("hardware", mLegacyFlashlightHack);
- for (int i = 0; i < LIGHT_ID_COUNT; i++) {
- mLights[i] = new Light(i);
+ for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) {
+ mLights[i] = new LightImpl(i);
}
}
+ @Override
+ public void onStart() {
+ publishBinderService("hardware", mLegacyFlashlightHack);
+ publishLocalService(LightsManager.class, mService);
+ }
+
+ private final LightsManager mService = new LightsManager() {
+ @Override
+ public com.android.server.lights.Light getLight(int id) {
+ if (id < LIGHT_ID_COUNT) {
+ return mLights[id];
+ } else {
+ return null;
+ }
+ }
+ };
+
protected void finalize() throws Throwable {
finalize_native(mNativePointer);
super.finalize();
}
- public Light getLight(int id) {
- return mLights[id];
- }
-
private Handler mH = new Handler() {
@Override
public void handleMessage(Message msg) {
- Light light = (Light)msg.obj;
+ LightImpl light = (LightImpl)msg.obj;
light.stopFlashing();
}
};
@@ -204,10 +198,8 @@ public class LightsService {
private static native int init_native();
private static native void finalize_native(int ptr);
- private static native void setLight_native(int ptr, int light, int color, int mode,
+ static native void setLight_native(int ptr, int light, int color, int mode,
int onMS, int offMS, int brightnessMode);
- private final Context mContext;
-
- private int mNativePointer;
+ int mNativePointer;
}
diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java
index a31695b..f91ea8c 100644
--- a/services/java/com/android/server/media/MediaRouterService.java
+++ b/services/java/com/android/server/media/MediaRouterService.java
@@ -16,7 +16,6 @@
package com.android.server.media;
-import com.android.internal.util.Objects;
import com.android.server.Watchdog;
import android.Manifest;
@@ -52,6 +51,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Provides a mechanism for discovering media routes and manages media playback
@@ -384,7 +384,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
if (clientRecord != null) {
final String oldRouteId = clientRecord.mSelectedRouteId;
- if (!Objects.equal(routeId, oldRouteId)) {
+ if (!Objects.equals(routeId, oldRouteId)) {
if (DEBUG) {
Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
+ ", oldRouteId=" + oldRouteId
@@ -1257,12 +1257,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub
mDescriptor = descriptor;
if (descriptor != null) {
final String name = computeName(descriptor);
- if (!Objects.equal(mMutableInfo.name, name)) {
+ if (!Objects.equals(mMutableInfo.name, name)) {
mMutableInfo.name = name;
changed = true;
}
final String description = computeDescription(descriptor);
- if (!Objects.equal(mMutableInfo.description, description)) {
+ if (!Objects.equals(mMutableInfo.description, description)) {
mMutableInfo.description = description;
changed = true;
}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
index b248ee0..a5fe9f2 100644
--- a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
+++ b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -16,8 +16,6 @@
package com.android.server.media;
-import com.android.internal.util.Objects;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +33,7 @@ import android.util.Slog;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.Objects;
/**
* Maintains a connection to a particular remote display provider service.
@@ -101,7 +100,7 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
}
public void setSelectedDisplay(String id) {
- if (!Objects.equal(mSelectedDisplayId, id)) {
+ if (!Objects.equals(mSelectedDisplayId, id)) {
if (mConnectionReady && mSelectedDisplayId != null) {
mActiveConnection.disconnect(mSelectedDisplayId);
}
@@ -293,7 +292,7 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
}
private void setDisplayState(RemoteDisplayState state) {
- if (!Objects.equal(mDisplayState, state)) {
+ if (!Objects.equals(mDisplayState, state)) {
mDisplayState = state;
if (!mScheduledDisplayStateChangedCallback) {
mScheduledDisplayStateChangedCallback = true;
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index d568b11..eb7cc4c 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -133,7 +133,6 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Objects;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
@@ -155,6 +154,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import libcore.io.IoUtils;
@@ -688,7 +688,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// mobile templates are relevant when SIM is ready and
// subscriberId matches.
if (tele.getSimState() == SIM_STATE_READY) {
- return Objects.equal(tele.getSubscriberId(), template.getSubscriberId());
+ return Objects.equals(tele.getSubscriberId(), template.getSubscriberId());
} else {
return false;
}
@@ -946,7 +946,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// TODO: offer more granular control over radio states once
// 4965893 is available.
if (tele.getSimState() == SIM_STATE_READY
- && Objects.equal(tele.getSubscriberId(), template.getSubscriberId())) {
+ && Objects.equals(tele.getSubscriberId(), template.getSubscriberId())) {
setPolicyDataEnable(TYPE_MOBILE, enabled);
setPolicyDataEnable(TYPE_WIMAX, enabled);
}
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
index 3169035..475482f 100644
--- a/services/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -34,7 +34,6 @@ import android.util.AtomicFile;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FileRotator;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Objects;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -50,6 +49,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import libcore.io.IoUtils;
@@ -508,7 +508,7 @@ public class NetworkStatsCollection implements FileRotator.Reader {
this.uid = uid;
this.set = set;
this.tag = tag;
- hashCode = Objects.hashCode(ident, uid, set, tag);
+ hashCode = Objects.hash(ident, uid, set, tag);
}
@Override
@@ -521,7 +521,7 @@ public class NetworkStatsCollection implements FileRotator.Reader {
if (obj instanceof Key) {
final Key key = (Key) obj;
return uid == key.uid && set == key.set && tag == key.tag
- && Objects.equal(ident, key.ident);
+ && Objects.equals(ident, key.ident);
}
return false;
}
diff --git a/services/java/com/android/server/notification/NotificationDelegate.java b/services/java/com/android/server/notification/NotificationDelegate.java
new file mode 100644
index 0000000..df2aaca
--- /dev/null
+++ b/services/java/com/android/server/notification/NotificationDelegate.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2013, 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.notification;
+
+public interface NotificationDelegate {
+ void onSetDisabled(int status);
+ void onClearAll();
+ void onNotificationClick(String pkg, String tag, int id);
+ void onNotificationClear(String pkg, String tag, int id);
+ void onNotificationError(String pkg, String tag, int id,
+ int uid, int initialPid, String message);
+ void onPanelRevealed();
+}
diff --git a/services/java/com/android/server/notification/NotificationManagerInternal.java b/services/java/com/android/server/notification/NotificationManagerInternal.java
new file mode 100644
index 0000000..92ffdcc
--- /dev/null
+++ b/services/java/com/android/server/notification/NotificationManagerInternal.java
@@ -0,0 +1,8 @@
+package com.android.server.notification;
+
+import android.app.Notification;
+
+public interface NotificationManagerInternal {
+ void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
+ String tag, int id, Notification notification, int[] idReceived, int userId);
+}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/notification/NotificationManagerService.java
index 0438675..db4cf31d 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/notification/NotificationManagerService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.notification;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -47,7 +47,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.media.AudioManager;
-import android.media.IAudioService;
import android.media.IRingtonePlayer;
import android.net.Uri;
import android.os.Binder;
@@ -56,9 +55,7 @@ import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.INotificationListener;
@@ -78,6 +75,12 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.notification.NotificationScorer;
+import com.android.server.EventLogTags;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.SystemService;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -99,66 +102,61 @@ import java.util.Set;
import libcore.io.IoUtils;
-
/** {@hide} */
-public class NotificationManagerService extends INotificationManager.Stub
-{
- private static final String TAG = "NotificationService";
- private static final boolean DBG = false;
+public class NotificationManagerService extends SystemService {
+ static final String TAG = "NotificationService";
+ static final boolean DBG = false;
- private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
+ static final int MAX_PACKAGE_NOTIFICATIONS = 50;
// message codes
- private static final int MESSAGE_TIMEOUT = 2;
+ static final int MESSAGE_TIMEOUT = 2;
- private static final int LONG_DELAY = 3500; // 3.5 seconds
- private static final int SHORT_DELAY = 2000; // 2 seconds
+ static final int LONG_DELAY = 3500; // 3.5 seconds
+ static final int SHORT_DELAY = 2000; // 2 seconds
- private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
- private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+ static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+ static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
- private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
- private static final boolean SCORE_ONGOING_HIGHER = false;
+ static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
+ static final boolean SCORE_ONGOING_HIGHER = false;
- private static final int JUNK_SCORE = -1000;
- private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
- private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
+ static final int JUNK_SCORE = -1000;
+ static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
+ static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
// Notifications with scores below this will not interrupt the user, either via LED or
// sound or vibration
- private static final int SCORE_INTERRUPTION_THRESHOLD =
+ static final int SCORE_INTERRUPTION_THRESHOLD =
Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
- private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
- private static final boolean ENABLE_BLOCKED_TOASTS = true;
+ static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
+ static final boolean ENABLE_BLOCKED_TOASTS = true;
- private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
+ static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
- final Context mContext;
- final IActivityManager mAm;
- final UserManager mUserManager;
- final IBinder mForegroundToken = new Binder();
+ private IActivityManager mAm;
+ AudioManager mAudioManager;
+ StatusBarManagerInternal mStatusBar;
+ Vibrator mVibrator;
+ final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
- private StatusBarManagerService mStatusBar;
- private LightsService.Light mNotificationLight;
- private LightsService.Light mAttentionLight;
+ private Light mNotificationLight;
+ Light mAttentionLight;
private int mDefaultNotificationColor;
private int mDefaultNotificationLedOn;
- private int mDefaultNotificationLedOff;
+ private int mDefaultNotificationLedOff;
private long[] mDefaultVibrationPattern;
- private long[] mFallbackVibrationPattern;
-
- private boolean mSystemReady;
- private int mDisabledNotifications;
- private NotificationRecord mSoundNotification;
- private NotificationRecord mVibrateNotification;
+ private long[] mFallbackVibrationPattern;
+ boolean mSystemReady;
- private IAudioService mAudioService;
- private Vibrator mVibrator;
+ int mDisabledNotifications;
+ NotificationRecord mSoundNotification;
+ NotificationRecord mVibrateNotification;
// for enabling and disabling notification pulse behavior
private boolean mScreenOn = true;
@@ -166,15 +164,15 @@ public class NotificationManagerService extends INotificationManager.Stub
private boolean mNotificationPulseEnabled;
// used as a mutex for access to all active notifications & listeners
- private final ArrayList<NotificationRecord> mNotificationList =
+ final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
- private ArrayList<ToastRecord> mToastQueue;
+ final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
- private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
- private NotificationRecord mLedNotification;
+ ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
+ NotificationRecord mLedNotification;
- private final AppOpsManager mAppOps;
+ private AppOpsManager mAppOps;
// contains connections to all connected listeners, including app services
// and system listeners
@@ -202,9 +200,9 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
- private final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
+ final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
- private class NotificationListenerInfo implements DeathRecipient {
+ private class NotificationListenerInfo implements IBinder.DeathRecipient {
INotificationListener listener;
ComponentName component;
int userid;
@@ -262,7 +260,7 @@ public class NotificationManagerService extends INotificationManager.Stub
public void binderDied() {
if (connection == null) {
// This is not a service; it won't be recreated. We can give up this connection.
- unregisterListener(this.listener, this.userid);
+ unregisterListenerImpl(this.listener, this.userid);
}
}
@@ -400,12 +398,14 @@ public class NotificationManagerService extends INotificationManager.Stub
tag = parser.getName();
if (type == START_TAG) {
if (TAG_BODY.equals(tag)) {
- version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
+ version = Integer.parseInt(
+ parser.getAttributeValue(null, ATTR_VERSION));
} else if (TAG_BLOCKED_PKGS.equals(tag)) {
while ((type = parser.next()) != END_DOCUMENT) {
tag = parser.getName();
if (TAG_PACKAGE.equals(tag)) {
- mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
+ mBlockedPackages.add(
+ parser.getAttributeValue(null, ATTR_NAME));
} else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
break;
}
@@ -428,15 +428,6 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- /**
- * Use this when you just want to know if notifications are OK for this package.
- */
- public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
- checkCallerIsSystem();
- return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
- == AppOpsManager.MODE_ALLOWED);
- }
-
/** Use this when you actually want to post a notification or toast.
*
* Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
@@ -450,21 +441,6 @@ public class NotificationManagerService extends INotificationManager.Stub
return true;
}
- public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
- checkCallerIsSystem();
-
- Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
-
- mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
- enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
-
- // Now, cancel any outstanding notifications that are part of a just-disabled app
- if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
- cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
- }
- }
-
-
private static String idDebugString(Context baseContext, String packageName, int id) {
Context c = null;
@@ -490,57 +466,6 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- /**
- * System-only API for getting a list of current (i.e. not cleared) notifications.
- *
- * Requires ACCESS_NOTIFICATIONS which is signature|system.
- */
- @Override
- public StatusBarNotification[] getActiveNotifications(String callingPkg) {
- // enforce() will ensure the calling uid has the correct permission
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
- "NotificationManagerService.getActiveNotifications");
-
- StatusBarNotification[] tmp = null;
- int uid = Binder.getCallingUid();
-
- // noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
- == AppOpsManager.MODE_ALLOWED) {
- synchronized (mNotificationList) {
- tmp = new StatusBarNotification[mNotificationList.size()];
- final int N = mNotificationList.size();
- for (int i=0; i<N; i++) {
- tmp[i] = mNotificationList.get(i).sbn;
- }
- }
- }
- return tmp;
- }
-
- /**
- * System-only API for getting a list of recent (cleared, no longer shown) notifications.
- *
- * Requires ACCESS_NOTIFICATIONS which is signature|system.
- */
- @Override
- public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
- // enforce() will ensure the calling uid has the correct permission
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
- "NotificationManagerService.getHistoricalNotifications");
-
- StatusBarNotification[] tmp = null;
- int uid = Binder.getCallingUid();
-
- // noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
- == AppOpsManager.MODE_ALLOWED) {
- synchronized (mArchive) {
- tmp = mArchive.getArray(count);
- }
- }
- return tmp;
- }
/**
* Remove notification access for any services that no longer exist.
@@ -548,12 +473,12 @@ public class NotificationManagerService extends INotificationManager.Stub
void disableNonexistentListeners() {
int currentUser = ActivityManager.getCurrentUser();
String flatIn = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
+ getContext().getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
currentUser);
if (!TextUtils.isEmpty(flatIn)) {
if (DBG) Slog.v(TAG, "flat before: " + flatIn);
- PackageManager pm = mContext.getPackageManager();
+ PackageManager pm = getContext().getPackageManager();
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
new Intent(NotificationListenerService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
@@ -589,7 +514,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
if (DBG) Slog.v(TAG, "flat after: " + flatOut);
if (!flatIn.equals(flatOut)) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(getContext().getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
flatOut, currentUser);
}
@@ -603,7 +528,7 @@ public class NotificationManagerService extends INotificationManager.Stub
void rebindListenerServices() {
final int currentUser = ActivityManager.getCurrentUser();
String flat = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
+ getContext().getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
currentUser);
@@ -652,28 +577,6 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- /**
- * Register a listener binder directly with the notification manager.
- *
- * Only works with system callers. Apps should extend
- * {@link android.service.notification.NotificationListenerService}.
- */
- @Override
- public void registerListener(final INotificationListener listener,
- final ComponentName component, final int userid) {
- checkCallerIsSystem();
-
- synchronized (mNotificationList) {
- try {
- NotificationListenerInfo info
- = new NotificationListenerInfo(listener, component, userid, true);
- listener.asBinder().linkToDeath(info, 0);
- mListeners.add(info);
- } catch (RemoteException e) {
- // already dead
- }
- }
- }
/**
* Version of registerListener that takes the name of a
@@ -703,7 +606,7 @@ public class NotificationManagerService extends INotificationManager.Stub
if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
mListeners.remove(i);
if (info.connection != null) {
- mContext.unbindService(info.connection);
+ getContext().unbindService(info.connection);
}
}
}
@@ -713,21 +616,25 @@ public class NotificationManagerService extends INotificationManager.Stub
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
R.string.notification_listener_binding_label);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));
+
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ getContext(), 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0);
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
try {
if (DBG) Slog.v(TAG, "binding: " + intent);
- if (!mContext.bindServiceAsUser(intent,
+ if (!getContext().bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
- NotificationListenerInfo info = new NotificationListenerInfo(
+ NotificationListenerInfo info
+ = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
@@ -756,28 +663,6 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- /**
- * Remove a listener binder directly
- */
- @Override
- public void unregisterListener(INotificationListener listener, int userid) {
- // no need to check permissions; if your listener binder is in the list,
- // that's proof that you had permission to add it in the first place
-
- synchronized (mNotificationList) {
- final int N = mListeners.size();
- for (int i=N-1; i>=0; i--) {
- final NotificationListenerInfo info = mListeners.get(i);
- if (info.listener.asBinder() == listener.asBinder()
- && info.userid == userid) {
- mListeners.remove(i);
- if (info.connection != null) {
- mContext.unbindService(info.connection);
- }
- }
- }
- }
- }
/**
* Remove a listener service for the given user by ComponentName
@@ -794,7 +679,7 @@ public class NotificationManagerService extends INotificationManager.Stub
mListeners.remove(i);
if (info.connection != null) {
try {
- mContext.unbindService(info.connection);
+ getContext().unbindService(info.connection);
} catch (IllegalArgumentException ex) {
// something happened to the service: we think we have a connection
// but it's bogus.
@@ -809,7 +694,7 @@ public class NotificationManagerService extends INotificationManager.Stub
/**
* asynchronously notify all listeners about a new notification
*/
- private void notifyPostedLocked(NotificationRecord n) {
+ void notifyPostedLocked(NotificationRecord n) {
// make a copy in case changes are made to the underlying Notification object
final StatusBarNotification sbn = n.sbn.clone();
for (final NotificationListenerInfo info : mListeners) {
@@ -824,7 +709,7 @@ public class NotificationManagerService extends INotificationManager.Stub
/**
* asynchronously notify all listeners about a removed notification
*/
- private void notifyRemovedLocked(NotificationRecord n) {
+ void notifyRemovedLocked(NotificationRecord n) {
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
final StatusBarNotification sbn_light = n.sbn.cloneLight();
@@ -850,66 +735,7 @@ public class NotificationManagerService extends INotificationManager.Stub
throw new SecurityException("Disallowed call from unknown listener: " + listener);
}
- /**
- * Allow an INotificationListener to simulate a "clear all" operation.
- *
- * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
- *
- * @param token The binder for the listener, to check that the caller is allowed
- */
- public void cancelAllNotificationsFromListener(INotificationListener token) {
- NotificationListenerInfo info = checkListenerToken(token);
- long identity = Binder.clearCallingIdentity();
- try {
- cancelAll(info.userid);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- /**
- * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
- *
- * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
- *
- * @param token The binder for the listener, to check that the caller is allowed
- */
- public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id) {
- NotificationListenerInfo info = checkListenerToken(token);
- long identity = Binder.clearCallingIdentity();
- try {
- cancelNotification(pkg, tag, id, 0,
- Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
- true,
- info.userid);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- /**
- * Allow an INotificationListener to request the list of outstanding notifications seen by
- * the current user. Useful when starting up, after which point the listener callbacks should
- * be used.
- *
- * @param token The binder for the listener, to check that the caller is allowed
- */
- public StatusBarNotification[] getActiveNotificationsFromListener(INotificationListener token) {
- NotificationListenerInfo info = checkListenerToken(token);
-
- StatusBarNotification[] result = new StatusBarNotification[0];
- ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>();
- synchronized (mNotificationList) {
- final int N = mNotificationList.size();
- for (int i=0; i<N; i++) {
- StatusBarNotification sbn = mNotificationList.get(i).sbn;
- if (info.enabledAndUserMatches(sbn)) {
- list.add(sbn);
- }
- }
- }
- return list.toArray(result);
- }
// -- end of listener APIs --
@@ -992,8 +818,8 @@ public class NotificationManagerService extends INotificationManager.Stub
return String.format(
"NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)",
System.identityHashCode(this),
- this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), this.sbn.getTag(),
- this.sbn.getScore(), this.sbn.getNotification());
+ this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
+ this.sbn.getTag(), this.sbn.getScore(), this.sbn.getNotification());
}
}
@@ -1031,9 +857,9 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks
- = new StatusBarManagerService.NotificationCallbacks() {
+ private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
+ @Override
public void onSetDisabled(int status) {
synchronized (mNotificationList) {
mDisabledNotifications = status;
@@ -1041,7 +867,7 @@ public class NotificationManagerService extends INotificationManager.Stub
// cancel whatever's going on
long identity = Binder.clearCallingIdentity();
try {
- final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+ final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
player.stopAsync();
}
@@ -1060,12 +886,14 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ @Override
public void onClearAll() {
// XXX to be totally correct, the caller should tell us which user
// this is for.
cancelAll(ActivityManager.getCurrentUser());
}
+ @Override
public void onNotificationClick(String pkg, String tag, int id) {
// XXX to be totally correct, the caller should tell us which user
// this is for.
@@ -1074,6 +902,7 @@ public class NotificationManagerService extends INotificationManager.Stub
ActivityManager.getCurrentUser());
}
+ @Override
public void onNotificationClear(String pkg, String tag, int id) {
// XXX to be totally correct, the caller should tell us which user
// this is for.
@@ -1082,6 +911,7 @@ public class NotificationManagerService extends INotificationManager.Stub
true, ActivityManager.getCurrentUser());
}
+ @Override
public void onPanelRevealed() {
synchronized (mNotificationList) {
// sound
@@ -1089,7 +919,7 @@ public class NotificationManagerService extends INotificationManager.Stub
long identity = Binder.clearCallingIdentity();
try {
- final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+ final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
player.stopAsync();
}
@@ -1114,6 +944,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
+ @Override
public void onNotificationError(String pkg, String tag, int id,
int uid, int initialPid, String message) {
Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
@@ -1168,7 +999,7 @@ public class NotificationManagerService extends INotificationManager.Stub
if (packageChanged) {
// We cancel notifications for packages which have just been disabled
try {
- final int enabled = mContext.getPackageManager()
+ final int enabled = getContext().getPackageManager()
.getApplicationEnabledSetting(pkgName);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -1244,7 +1075,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
void observe() {
- ContentResolver resolver = mContext.getContentResolver();
+ ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
@@ -1257,7 +1088,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public void update(Uri uri) {
- ContentResolver resolver = mContext.getContentResolver();
+ ContentResolver resolver = getContext().getContentResolver();
if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
boolean pulseEnabled = Settings.System.getInt(resolver,
Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
@@ -1287,28 +1118,24 @@ public class NotificationManagerService extends INotificationManager.Stub
return out;
}
- NotificationManagerService(Context context, StatusBarManagerService statusBar,
- LightsService lights)
- {
- super();
- mContext = context;
- mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
+ @Override
+ public void onStart() {
mAm = ActivityManagerNative.getDefault();
- mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
- mToastQueue = new ArrayList<ToastRecord>();
- mHandler = new WorkerHandler();
+ mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+ mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
- mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ mHandler = new WorkerHandler();
importOldBlockDb();
- mStatusBar = statusBar;
- statusBar.setNotificationCallbacks(mNotificationCallbacks);
+ mStatusBar = getLocalService(StatusBarManagerInternal.class);
+ mStatusBar.setNotificationDelegate(mNotificationDelegate);
- mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
- mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
+ final LightsManager lights = getLocalService(LightsManager.class);
+ mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
+ mAttentionLight = lights.getLight(LightsManager.LIGHT_ID_ATTENTION);
- Resources resources = mContext.getResources();
+ Resources resources = getContext().getResources();
mDefaultNotificationColor = resources.getColor(
R.color.config_defaultNotificationColor);
mDefaultNotificationLedOn = resources.getInteger(
@@ -1330,7 +1157,7 @@ public class NotificationManagerService extends INotificationManager.Stub
// After that, including subsequent boots, init with notifications turned on.
// This works on the first boot because the setup wizard will toggle this
// flag at least once and we'll go back to 0 after that.
- if (0 == Settings.Global.getInt(mContext.getContentResolver(),
+ if (0 == Settings.Global.getInt(getContext().getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0)) {
mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
}
@@ -1343,7 +1170,7 @@ public class NotificationManagerService extends INotificationManager.Stub
filter.addAction(Intent.ACTION_USER_PRESENT);
filter.addAction(Intent.ACTION_USER_STOPPED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mIntentReceiver, filter);
+ getContext().registerReceiver(mIntentReceiver, filter);
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -1351,9 +1178,9 @@ public class NotificationManagerService extends INotificationManager.Stub
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
pkgFilter.addDataScheme("package");
- mContext.registerReceiver(mIntentReceiver, pkgFilter);
+ getContext().registerReceiver(mIntentReceiver, pkgFilter);
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- mContext.registerReceiver(mIntentReceiver, sdFilter);
+ getContext().registerReceiver(mIntentReceiver, sdFilter);
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
@@ -1363,9 +1190,9 @@ public class NotificationManagerService extends INotificationManager.Stub
R.array.config_notificationScorers);
for (String scorerName : notificationScorerNames) {
try {
- Class<?> scorerClass = mContext.getClassLoader().loadClass(scorerName);
+ Class<?> scorerClass = getContext().getClassLoader().loadClass(scorerName);
NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance();
- scorer.initialize(mContext);
+ scorer.initialize(getContext());
mScorers.add(scorer);
} catch (ClassNotFoundException e) {
Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e);
@@ -1375,6 +1202,9 @@ public class NotificationManagerService extends INotificationManager.Stub
Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e);
}
}
+
+ publishBinderService(Context.NOTIFICATION_SERVICE, mService);
+ publishLocalService(NotificationManagerInternal.class, mInternalService);
}
/**
@@ -1383,12 +1213,12 @@ public class NotificationManagerService extends INotificationManager.Stub
private void importOldBlockDb() {
loadBlockDb();
- PackageManager pm = mContext.getPackageManager();
+ PackageManager pm = getContext().getPackageManager();
for (String pkg : mBlockedPackages) {
PackageInfo info = null;
try {
info = pm.getPackageInfo(pkg, 0);
- setNotificationsEnabledForPackage(pkg, info.applicationInfo.uid, false);
+ setNotificationsEnabledForPackageImpl(pkg, info.applicationInfo.uid, false);
} catch (NameNotFoundException e) {
// forget you
}
@@ -1399,244 +1229,421 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- void systemReady() {
- mAudioService = IAudioService.Stub.asInterface(
- ServiceManager.getService(Context.AUDIO_SERVICE));
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ // no beeping until we're basically done booting
+ mSystemReady = true;
- // no beeping until we're basically done booting
- mSystemReady = true;
+ // Grab our optional AudioService
+ mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
- // make sure our listener services are properly bound
- rebindListenerServices();
+ // make sure our listener services are properly bound
+ rebindListenerServices();
+ }
}
- // Toasts
- // ============================================================================
- public void enqueueToast(String pkg, ITransientNotification callback, int duration)
- {
- if (DBG) Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration);
+ void setNotificationsEnabledForPackageImpl(String pkg, int uid, boolean enabled) {
+ Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
+
+ mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+ enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
- if (pkg == null || callback == null) {
- Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
- return ;
+ // Now, cancel any outstanding notifications that are part of a just-disabled app
+ if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
+ cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
}
+ }
- final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
+ private final IBinder mService = new INotificationManager.Stub() {
+ // Toasts
+ // ============================================================================
- if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
- if (!isSystemToast) {
- Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
- return;
+ @Override
+ public void enqueueToast(String pkg, ITransientNotification callback, int duration)
+ {
+ if (DBG) {
+ Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
+ + " duration=" + duration);
}
- }
- synchronized (mToastQueue) {
- int callingPid = Binder.getCallingPid();
- long callingId = Binder.clearCallingIdentity();
- try {
- ToastRecord record;
- int index = indexOfToastLocked(pkg, callback);
- // If it's already in the queue, we update it in place, we don't
- // move it to the end of the queue.
- if (index >= 0) {
- record = mToastQueue.get(index);
- record.update(duration);
- } else {
- // Limit the number of toasts that any given package except the android
- // package can enqueue. Prevents DOS attacks and deals with leaks.
- if (!isSystemToast) {
- int count = 0;
- final int N = mToastQueue.size();
- for (int i=0; i<N; i++) {
- final ToastRecord r = mToastQueue.get(i);
- if (r.pkg.equals(pkg)) {
- count++;
- if (count >= MAX_PACKAGE_NOTIFICATIONS) {
- Slog.e(TAG, "Package has already posted " + count
- + " toasts. Not showing more. Package=" + pkg);
- return;
+ if (pkg == null || callback == null) {
+ Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
+ return ;
+ }
+
+ final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
+
+ if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
+ if (!isSystemToast) {
+ Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
+ return;
+ }
+ }
+
+ synchronized (mToastQueue) {
+ int callingPid = Binder.getCallingPid();
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ ToastRecord record;
+ int index = indexOfToastLocked(pkg, callback);
+ // If it's already in the queue, we update it in place, we don't
+ // move it to the end of the queue.
+ if (index >= 0) {
+ record = mToastQueue.get(index);
+ record.update(duration);
+ } else {
+ // Limit the number of toasts that any given package except the android
+ // package can enqueue. Prevents DOS attacks and deals with leaks.
+ if (!isSystemToast) {
+ int count = 0;
+ final int N = mToastQueue.size();
+ for (int i=0; i<N; i++) {
+ final ToastRecord r = mToastQueue.get(i);
+ if (r.pkg.equals(pkg)) {
+ count++;
+ if (count >= MAX_PACKAGE_NOTIFICATIONS) {
+ Slog.e(TAG, "Package has already posted " + count
+ + " toasts. Not showing more. Package=" + pkg);
+ return;
+ }
}
- }
+ }
}
+
+ record = new ToastRecord(callingPid, pkg, callback, duration);
+ mToastQueue.add(record);
+ index = mToastQueue.size() - 1;
+ keepProcessAliveLocked(callingPid);
+ }
+ // If it's at index 0, it's the current toast. It doesn't matter if it's
+ // new or just been updated. Call back and tell it to show itself.
+ // If the callback fails, this will remove it from the list, so don't
+ // assume that it's valid after this.
+ if (index == 0) {
+ showNextToastLocked();
}
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+ }
+
+ @Override
+ public void cancelToast(String pkg, ITransientNotification callback) {
+ Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
+
+ if (pkg == null || callback == null) {
+ Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
+ return ;
+ }
- record = new ToastRecord(callingPid, pkg, callback, duration);
- mToastQueue.add(record);
- index = mToastQueue.size() - 1;
- keepProcessAliveLocked(callingPid);
+ synchronized (mToastQueue) {
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ int index = indexOfToastLocked(pkg, callback);
+ if (index >= 0) {
+ cancelToastLocked(index);
+ } else {
+ Slog.w(TAG, "Toast already cancelled. pkg=" + pkg
+ + " callback=" + callback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
}
- // If it's at index 0, it's the current toast. It doesn't matter if it's
- // new or just been updated. Call back and tell it to show itself.
- // If the callback fails, this will remove it from the list, so don't
- // assume that it's valid after this.
- if (index == 0) {
- showNextToastLocked();
+ }
+ }
+
+ @Override
+ public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
+ Notification notification, int[] idOut, int userId) throws RemoteException {
+ enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(),
+ Binder.getCallingPid(), tag, id, notification, idOut, userId);
+ }
+
+ @Override
+ public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
+ checkCallerIsSystemOrSameApp(pkg);
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
+ // Don't allow client applications to cancel foreground service notis.
+ cancelNotification(pkg, tag, id, 0,
+ Binder.getCallingUid() == Process.SYSTEM_UID
+ ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId);
+ }
+
+ @Override
+ public void cancelAllNotifications(String pkg, int userId) {
+ checkCallerIsSystemOrSameApp(pkg);
+
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
+
+ // Calling from user space, don't allow the canceling of actively
+ // running foreground services.
+ cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
+ }
+
+ @Override
+ public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
+ checkCallerIsSystem();
+
+ setNotificationsEnabledForPackageImpl(pkg, uid, enabled);
+ }
+
+ /**
+ * Use this when you just want to know if notifications are OK for this package.
+ */
+ @Override
+ public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
+ checkCallerIsSystem();
+ return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
+ == AppOpsManager.MODE_ALLOWED);
+ }
+
+ /**
+ * System-only API for getting a list of current (i.e. not cleared) notifications.
+ *
+ * Requires ACCESS_NOTIFICATIONS which is signature|system.
+ */
+ @Override
+ public StatusBarNotification[] getActiveNotifications(String callingPkg) {
+ // enforce() will ensure the calling uid has the correct permission
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NOTIFICATIONS,
+ "NotificationManagerService.getActiveNotifications");
+
+ StatusBarNotification[] tmp = null;
+ int uid = Binder.getCallingUid();
+
+ // noteOp will check to make sure the callingPkg matches the uid
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
+ == AppOpsManager.MODE_ALLOWED) {
+ synchronized (mNotificationList) {
+ tmp = new StatusBarNotification[mNotificationList.size()];
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ tmp[i] = mNotificationList.get(i).sbn;
+ }
}
- } finally {
- Binder.restoreCallingIdentity(callingId);
}
+ return tmp;
}
- }
- public void cancelToast(String pkg, ITransientNotification callback) {
- Slog.i(TAG, "cancelToast pkg=" + pkg + " callback=" + callback);
+ /**
+ * System-only API for getting a list of recent (cleared, no longer shown) notifications.
+ *
+ * Requires ACCESS_NOTIFICATIONS which is signature|system.
+ */
+ @Override
+ public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
+ // enforce() will ensure the calling uid has the correct permission
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_NOTIFICATIONS,
+ "NotificationManagerService.getHistoricalNotifications");
+
+ StatusBarNotification[] tmp = null;
+ int uid = Binder.getCallingUid();
+
+ // noteOp will check to make sure the callingPkg matches the uid
+ if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
+ == AppOpsManager.MODE_ALLOWED) {
+ synchronized (mArchive) {
+ tmp = mArchive.getArray(count);
+ }
+ }
+ return tmp;
+ }
- if (pkg == null || callback == null) {
- Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " callback=" + callback);
- return ;
+ /**
+ * Register a listener binder directly with the notification manager.
+ *
+ * Only works with system callers. Apps should extend
+ * {@link android.service.notification.NotificationListenerService}.
+ */
+ @Override
+ public void registerListener(final INotificationListener listener,
+ final ComponentName component, final int userid) {
+ checkCallerIsSystem();
+ registerListenerImpl(listener, component, userid);
}
- synchronized (mToastQueue) {
- long callingId = Binder.clearCallingIdentity();
+ /**
+ * Remove a listener binder directly
+ */
+ @Override
+ public void unregisterListener(INotificationListener listener, int userid) {
+ // no need to check permissions; if your listener binder is in the list,
+ // that's proof that you had permission to add it in the first place
+ unregisterListenerImpl(listener, userid);
+ }
+
+ /**
+ * Allow an INotificationListener to simulate a "clear all" operation.
+ *
+ * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onClearAllNotifications}
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ @Override
+ public void cancelAllNotificationsFromListener(INotificationListener token) {
+ NotificationListenerInfo info = checkListenerToken(token);
+ long identity = Binder.clearCallingIdentity();
try {
- int index = indexOfToastLocked(pkg, callback);
- if (index >= 0) {
- cancelToastLocked(index);
- } else {
- Slog.w(TAG, "Toast already cancelled. pkg=" + pkg + " callback=" + callback);
- }
+ cancelAll(info.userid);
} finally {
- Binder.restoreCallingIdentity(callingId);
+ Binder.restoreCallingIdentity(identity);
}
}
- }
- private void showNextToastLocked() {
- ToastRecord record = mToastQueue.get(0);
- while (record != null) {
- if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
+ /**
+ * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
+ *
+ * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ @Override
+ public void cancelNotificationFromListener(INotificationListener token, String pkg,
+ String tag, int id) {
+ NotificationListenerInfo info = checkListenerToken(token);
+ long identity = Binder.clearCallingIdentity();
try {
- record.callback.show();
- scheduleTimeoutLocked(record);
- return;
- } catch (RemoteException e) {
- Slog.w(TAG, "Object died trying to show notification " + record.callback
- + " in package " + record.pkg);
- // remove it from the list and let the process die
- int index = mToastQueue.indexOf(record);
- if (index >= 0) {
- mToastQueue.remove(index);
- }
- keepProcessAliveLocked(record.pid);
- if (mToastQueue.size() > 0) {
- record = mToastQueue.get(0);
- } else {
- record = null;
+ cancelNotification(pkg, tag, id, 0,
+ Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
+ true,
+ info.userid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Allow an INotificationListener to request the list of outstanding notifications seen by
+ * the current user. Useful when starting up, after which point the listener callbacks
+ * should be used.
+ *
+ * @param token The binder for the listener, to check that the caller is allowed
+ */
+ @Override
+ public StatusBarNotification[] getActiveNotificationsFromListener(
+ INotificationListener token) {
+ NotificationListenerInfo info = checkListenerToken(token);
+
+ StatusBarNotification[] result = new StatusBarNotification[0];
+ ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>();
+ synchronized (mNotificationList) {
+ final int N = mNotificationList.size();
+ for (int i=0; i<N; i++) {
+ StatusBarNotification sbn = mNotificationList.get(i).sbn;
+ if (info.enabledAndUserMatches(sbn)) {
+ list.add(sbn);
+ }
}
}
+ return list.toArray(result);
}
- }
- private void cancelToastLocked(int index) {
- ToastRecord record = mToastQueue.get(index);
- try {
- record.callback.hide();
- } catch (RemoteException e) {
- Slog.w(TAG, "Object died trying to hide notification " + record.callback
- + " in package " + record.pkg);
- // don't worry about this, we're about to remove it from
- // the list anyway
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump NotificationManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ dumpImpl(pw);
}
- mToastQueue.remove(index);
- keepProcessAliveLocked(record.pid);
- if (mToastQueue.size() > 0) {
- // Show the next one. If the callback fails, this will remove
- // it from the list, so don't assume that the list hasn't changed
- // after this point.
- showNextToastLocked();
+ };
+
+ void dumpImpl(PrintWriter pw) {
+ pw.println("Current Notification Manager state:");
+
+ pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size()
+ + ") enabled for current user:");
+ for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
+ pw.println(" " + cmpt);
}
- }
- private void scheduleTimeoutLocked(ToastRecord r)
- {
- mHandler.removeCallbacksAndMessages(r);
- Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
- long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
- mHandler.sendMessageDelayed(m, delay);
- }
+ pw.println(" Live listeners (" + mListeners.size() + "):");
+ for (NotificationListenerInfo info : mListeners) {
+ pw.println(" " + info.component
+ + " (user " + info.userid + "): " + info.listener
+ + (info.isSystem?" SYSTEM":""));
+ }
+
+ int N;
- private void handleTimeout(ToastRecord record)
- {
- if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
synchronized (mToastQueue) {
- int index = indexOfToastLocked(record.pkg, record.callback);
- if (index >= 0) {
- cancelToastLocked(index);
+ N = mToastQueue.size();
+ if (N > 0) {
+ pw.println(" Toast Queue:");
+ for (int i=0; i<N; i++) {
+ mToastQueue.get(i).dump(pw, " ");
+ }
+ pw.println(" ");
}
+
}
- }
- // lock on mToastQueue
- private int indexOfToastLocked(String pkg, ITransientNotification callback)
- {
- IBinder cbak = callback.asBinder();
- ArrayList<ToastRecord> list = mToastQueue;
- int len = list.size();
- for (int i=0; i<len; i++) {
- ToastRecord r = list.get(i);
- if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
- return i;
+ synchronized (mNotificationList) {
+ N = mNotificationList.size();
+ if (N > 0) {
+ pw.println(" Notification List:");
+ for (int i=0; i<N; i++) {
+ mNotificationList.get(i).dump(pw, " ", getContext());
+ }
+ pw.println(" ");
}
- }
- return -1;
- }
- // lock on mToastQueue
- private void keepProcessAliveLocked(int pid)
- {
- int toastCount = 0; // toasts from this pid
- ArrayList<ToastRecord> list = mToastQueue;
- int N = list.size();
- for (int i=0; i<N; i++) {
- ToastRecord r = list.get(i);
- if (r.pid == pid) {
- toastCount++;
+ N = mLights.size();
+ if (N > 0) {
+ pw.println(" Lights List:");
+ for (int i=0; i<N; i++) {
+ pw.println(" " + mLights.get(i));
+ }
+ pw.println(" ");
}
- }
- try {
- mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
- } catch (RemoteException e) {
- // Shouldn't happen.
- }
- }
- private final class WorkerHandler extends Handler
- {
- @Override
- public void handleMessage(Message msg)
- {
- switch (msg.what)
- {
- case MESSAGE_TIMEOUT:
- handleTimeout((ToastRecord)msg.obj);
+ pw.println(" mSoundNotification=" + mSoundNotification);
+ pw.println(" mVibrateNotification=" + mVibrateNotification);
+ pw.println(" mDisabledNotifications=0x"
+ + Integer.toHexString(mDisabledNotifications));
+ pw.println(" mSystemReady=" + mSystemReady);
+ pw.println(" mArchive=" + mArchive.toString());
+ Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
+ int i=0;
+ while (iter.hasNext()) {
+ pw.println(" " + iter.next());
+ if (++i >= 5) {
+ if (iter.hasNext()) pw.println(" ...");
break;
+ }
}
- }
- }
-
- // Notifications
- // ============================================================================
- public void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
- Notification notification, int[] idOut, int userId)
- {
- enqueueNotificationInternal(pkg, basePkg, Binder.getCallingUid(), Binder.getCallingPid(),
- tag, id, notification, idOut, userId);
- }
-
- private final static int clamp(int x, int low, int high) {
- return (x < low) ? low : ((x > high) ? high : x);
+ }
}
- // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
- // uid/pid of another application)
+ /**
+ * The private API only accessible to the system process.
+ */
+ private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
+ @Override
+ public void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
+ String tag, int id, Notification notification, int[] idReceived, int userId) {
+ enqueueNotificationInternal(pkg, basePkg, callingUid, callingPid, tag, id, notification,
+ idReceived, userId);
+ }
+ };
- public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
+ void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
- int[] idOut, int incomingUserId)
- {
+ int[] idOut, int incomingUserId) {
if (DBG) {
- Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
+ Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ + " notification=" + notification);
}
checkCallerIsSystemOrSameApp(pkg);
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
@@ -1787,23 +1794,21 @@ public class NotificationManagerService extends INotificationManager.Stub
if (notification.icon != 0) {
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
- }
- finally {
+ } finally {
Binder.restoreCallingIdentity(identity);
}
} else {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
&& canInterrupt) {
mAttentionLight.pulse();
}
- }
- finally {
+ } finally {
Binder.restoreCallingIdentity(identity);
}
}
@@ -1816,33 +1821,32 @@ public class NotificationManagerService extends INotificationManager.Stub
} else {
Slog.e(TAG, "Not posting notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
- }
- finally {
+ } finally {
Binder.restoreCallingIdentity(identity);
}
notifyRemovedLocked(r);
}
// ATTENTION: in a future release we will bail out here
- // so that we do not play sounds, show lights, etc. for invalid notifications
+ // so that we do not play sounds, show lights, etc. for invalid
+ // notifications
Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+ n.getPackageName());
}
// If we're not supposed to beep, vibrate, etc. then don't.
- if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
+ if (((mDisabledNotifications
+ & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& (r.getUserId() == UserHandle.USER_ALL ||
(r.getUserId() == userId && r.getUserId() == currentUser))
&& canInterrupt
- && mSystemReady) {
-
- final AudioManager audioManager = (AudioManager) mContext
- .getSystemService(Context.AUDIO_SERVICE);
+ && mSystemReady
+ && mAudioManager != null) {
// sound
@@ -1861,7 +1865,7 @@ public class NotificationManagerService extends INotificationManager.Stub
soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
// check to see if the default notification sound is silent
- ContentResolver resolver = mContext.getContentResolver();
+ ContentResolver resolver = getContext().getContentResolver();
hasValidSound = Settings.System.getString(resolver,
Settings.System.NOTIFICATION_SOUND) != null;
} else if (notification.sound != null) {
@@ -1870,7 +1874,8 @@ public class NotificationManagerService extends INotificationManager.Stub
}
if (hasValidSound) {
- boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
+ boolean looping =
+ (notification.flags & Notification.FLAG_INSISTENT) != 0;
int audioStreamType;
if (notification.audioStreamType >= 0) {
audioStreamType = notification.audioStreamType;
@@ -1880,11 +1885,12 @@ public class NotificationManagerService extends INotificationManager.Stub
mSoundNotification = r;
// do not play notifications if stream volume is 0 (typically because
// ringer mode is silent) or if there is a user of exclusive audio focus
- if ((audioManager.getStreamVolume(audioStreamType) != 0)
- && !audioManager.isAudioFocusExclusive()) {
+ if ((mAudioManager.getStreamVolume(audioStreamType) != 0)
+ && !mAudioManager.isAudioFocusExclusive()) {
final long identity = Binder.clearCallingIdentity();
try {
- final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+ final IRingtonePlayer player =
+ mAudioManager.getRingtonePlayer();
if (player != null) {
player.playAsync(soundUri, user, looping, audioStreamType);
}
@@ -1904,7 +1910,7 @@ public class NotificationManagerService extends INotificationManager.Stub
final boolean convertSoundToVibration =
!hasCustomVibrate
&& hasValidSound
- && (audioManager.getRingerMode()
+ && (mAudioManager.getRingerMode()
== AudioManager.RINGER_MODE_VIBRATE);
// The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
@@ -1912,7 +1918,7 @@ public class NotificationManagerService extends INotificationManager.Stub
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
- && !(audioManager.getRingerMode()
+ && !(mAudioManager.getRingerMode()
== AudioManager.RINGER_MODE_SILENT)) {
mVibrateNotification = r;
@@ -1966,8 +1972,158 @@ public class NotificationManagerService extends INotificationManager.Stub
idOut[0] = id;
}
- private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
- AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ void registerListenerImpl(final INotificationListener listener,
+ final ComponentName component, final int userid) {
+ synchronized (mNotificationList) {
+ try {
+ NotificationListenerInfo info
+ = new NotificationListenerInfo(listener, component, userid, true);
+ listener.asBinder().linkToDeath(info, 0);
+ mListeners.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ void unregisterListenerImpl(final INotificationListener listener, final int userid) {
+ synchronized (mNotificationList) {
+ final int N = mListeners.size();
+ for (int i=N-1; i>=0; i--) {
+ final NotificationListenerInfo info = mListeners.get(i);
+ if (info.listener.asBinder() == listener.asBinder()
+ && info.userid == userid) {
+ mListeners.remove(i);
+ if (info.connection != null) {
+ getContext().unbindService(info.connection);
+ }
+ }
+ }
+ }
+ }
+
+ void showNextToastLocked() {
+ ToastRecord record = mToastQueue.get(0);
+ while (record != null) {
+ if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
+ try {
+ record.callback.show();
+ scheduleTimeoutLocked(record);
+ return;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Object died trying to show notification " + record.callback
+ + " in package " + record.pkg);
+ // remove it from the list and let the process die
+ int index = mToastQueue.indexOf(record);
+ if (index >= 0) {
+ mToastQueue.remove(index);
+ }
+ keepProcessAliveLocked(record.pid);
+ if (mToastQueue.size() > 0) {
+ record = mToastQueue.get(0);
+ } else {
+ record = null;
+ }
+ }
+ }
+ }
+
+ void cancelToastLocked(int index) {
+ ToastRecord record = mToastQueue.get(index);
+ try {
+ record.callback.hide();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Object died trying to hide notification " + record.callback
+ + " in package " + record.pkg);
+ // don't worry about this, we're about to remove it from
+ // the list anyway
+ }
+ mToastQueue.remove(index);
+ keepProcessAliveLocked(record.pid);
+ if (mToastQueue.size() > 0) {
+ // Show the next one. If the callback fails, this will remove
+ // it from the list, so don't assume that the list hasn't changed
+ // after this point.
+ showNextToastLocked();
+ }
+ }
+
+ private void scheduleTimeoutLocked(ToastRecord r)
+ {
+ mHandler.removeCallbacksAndMessages(r);
+ Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
+ long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
+ mHandler.sendMessageDelayed(m, delay);
+ }
+
+ private void handleTimeout(ToastRecord record)
+ {
+ if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
+ synchronized (mToastQueue) {
+ int index = indexOfToastLocked(record.pkg, record.callback);
+ if (index >= 0) {
+ cancelToastLocked(index);
+ }
+ }
+ }
+
+ // lock on mToastQueue
+ int indexOfToastLocked(String pkg, ITransientNotification callback)
+ {
+ IBinder cbak = callback.asBinder();
+ ArrayList<ToastRecord> list = mToastQueue;
+ int len = list.size();
+ for (int i=0; i<len; i++) {
+ ToastRecord r = list.get(i);
+ if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ // lock on mToastQueue
+ void keepProcessAliveLocked(int pid)
+ {
+ int toastCount = 0; // toasts from this pid
+ ArrayList<ToastRecord> list = mToastQueue;
+ int N = list.size();
+ for (int i=0; i<N; i++) {
+ ToastRecord r = list.get(i);
+ if (r.pid == pid) {
+ toastCount++;
+ }
+ }
+ try {
+ mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ }
+ }
+
+ private final class WorkerHandler extends Handler
+ {
+ @Override
+ public void handleMessage(Message msg)
+ {
+ switch (msg.what)
+ {
+ case MESSAGE_TIMEOUT:
+ handleTimeout((ToastRecord)msg.obj);
+ break;
+ }
+ }
+ }
+
+
+ // Notifications
+ // ============================================================================
+ static int clamp(int x, int low, int high) {
+ return (x < low) ? low : ((x > high) ? high : x);
+ }
+
+ void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
+ AccessibilityManager manager = AccessibilityManager.getInstance(getContext());
if (!manager.isEnabled()) {
return;
}
@@ -2001,11 +2157,10 @@ public class NotificationManagerService extends INotificationManager.Stub
// status bar
if (r.getNotification().icon != 0) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(r.statusBarKey);
- }
- finally {
+ } finally {
Binder.restoreCallingIdentity(identity);
}
r.statusBarKey = null;
@@ -2017,7 +2172,7 @@ public class NotificationManagerService extends INotificationManager.Stub
mSoundNotification = null;
final long identity = Binder.clearCallingIdentity();
try {
- final IRingtonePlayer player = mAudioService.getRingtonePlayer();
+ final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
player.stopAsync();
}
@@ -2053,7 +2208,7 @@ public class NotificationManagerService extends INotificationManager.Stub
* Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
* and none of the {@code mustNotHaveFlags}.
*/
- private void cancelNotification(final String pkg, final String tag, final int id,
+ void cancelNotification(final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
final int userId) {
// In enqueueNotificationInternal notifications are added by scheduling the
@@ -2146,26 +2301,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
- checkCallerIsSystemOrSameApp(pkg);
- userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
- // Don't allow client applications to cancel foreground service notis.
- cancelNotification(pkg, tag, id, 0,
- Binder.getCallingUid() == Process.SYSTEM_UID
- ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId);
- }
-
- public void cancelAllNotifications(String pkg, int userId) {
- checkCallerIsSystemOrSameApp(pkg);
-
- userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
- // Calling from user space, don't allow the canceling of actively
- // running foreground services.
- cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId);
- }
// Return true if the UID is a system or phone UID and therefore should not have
// any notifications or toasts blocked.
@@ -2225,7 +2361,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
// lock on mNotificationList
- private void updateLightsLocked()
+ void updateLightsLocked()
{
// handle notification lights
if (mLedNotification == null) {
@@ -2251,14 +2387,14 @@ public class NotificationManagerService extends INotificationManager.Stub
}
if (mNotificationPulseEnabled) {
// pulse repeatedly
- mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
+ mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED,
ledOnMS, ledOffMS);
}
}
}
// lock on mNotificationList
- private int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
+ int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
{
ArrayList<NotificationRecord> list = mNotificationList;
final int len = list.size();
@@ -2288,81 +2424,4 @@ public class NotificationManagerService extends INotificationManager.Stub
updateLightsLocked();
}
}
-
- // ======================================================================
- @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 NotificationManager from from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- return;
- }
-
- pw.println("Current Notification Manager state:");
-
- pw.println(" Listeners (" + mEnabledListenersForCurrentUser.size()
- + ") enabled for current user:");
- for (ComponentName cmpt : mEnabledListenersForCurrentUser) {
- pw.println(" " + cmpt);
- }
-
- pw.println(" Live listeners (" + mListeners.size() + "):");
- for (NotificationListenerInfo info : mListeners) {
- pw.println(" " + info.component
- + " (user " + info.userid + "): " + info.listener
- + (info.isSystem?" SYSTEM":""));
- }
-
- int N;
-
- synchronized (mToastQueue) {
- N = mToastQueue.size();
- if (N > 0) {
- pw.println(" Toast Queue:");
- for (int i=0; i<N; i++) {
- mToastQueue.get(i).dump(pw, " ");
- }
- pw.println(" ");
- }
-
- }
-
- synchronized (mNotificationList) {
- N = mNotificationList.size();
- if (N > 0) {
- pw.println(" Notification List:");
- for (int i=0; i<N; i++) {
- mNotificationList.get(i).dump(pw, " ", mContext);
- }
- pw.println(" ");
- }
-
- N = mLights.size();
- if (N > 0) {
- pw.println(" Lights List:");
- for (int i=0; i<N; i++) {
- pw.println(" " + mLights.get(i));
- }
- pw.println(" ");
- }
-
- pw.println(" mSoundNotification=" + mSoundNotification);
- pw.println(" mVibrateNotification=" + mVibrateNotification);
- pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications));
- pw.println(" mSystemReady=" + mSystemReady);
- pw.println(" mArchive=" + mArchive.toString());
- Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
- int i=0;
- while (iter.hasNext()) {
- pw.println(" " + iter.next());
- if (++i >= 5) {
- if (iter.hasNext()) pw.println(" ...");
- break;
- }
- }
-
- }
- }
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 5761f6c..b11ebf5 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -38,10 +38,10 @@ import com.android.internal.content.PackageHelper;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
-import com.android.server.DeviceStorageMonitorService;
import com.android.server.EventLogTags;
import com.android.server.IntentResolver;
+import com.android.server.LocalServices;
import com.android.server.Watchdog;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -92,6 +92,7 @@ import android.content.pm.VerificationParams;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -127,7 +128,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import android.view.Display;
-import android.view.WindowManager;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -162,6 +162,7 @@ import libcore.io.Libcore;
import libcore.io.StructStat;
import com.android.internal.R;
+import com.android.server.storage.DeviceStorageMonitorInternal;
/**
* Keep track of all those .apks everywhere.
@@ -1067,6 +1068,12 @@ public class PackageManagerService extends IPackageManager.Stub {
return res;
}
+ private static void getDefaultDisplayMetrics(Context context, DisplayMetrics metrics) {
+ DisplayManager displayManager = (DisplayManager) context.getSystemService(
+ Context.DISPLAY_SERVICE);
+ displayManager.getDisplay(Display.DEFAULT_DISPLAY).getMetrics(metrics);
+ }
+
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -1114,9 +1121,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mInstaller = installer;
- WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- Display d = wm.getDefaultDisplay();
- d.getMetrics(mMetrics);
+ getDefaultDisplayMetrics(context, mMetrics);
synchronized (mInstallLock) {
// writer
@@ -7339,6 +7344,15 @@ public class PackageManagerService extends IPackageManager.Stub {
return pkgLite.recommendedInstallLocation;
}
+ private long getMemoryLowThreshold() {
+ final DeviceStorageMonitorInternal
+ dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
+ if (dsm == null) {
+ return 0L;
+ }
+ return dsm.getMemoryLowThreshold();
+ }
+
/*
* Invoke remote method to get package information and install
* location values. Override install location based on default
@@ -7356,15 +7370,9 @@ public class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
- final long lowThreshold;
-
- final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
- .getService(DeviceStorageMonitorService.SERVICE);
- if (dsm == null) {
+ final long lowThreshold = getMemoryLowThreshold();
+ if (lowThreshold == 0L) {
Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
- lowThreshold = 0L;
- } else {
- lowThreshold = dsm.getMemoryLowThreshold();
}
try {
@@ -7922,8 +7930,8 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
final long lowThreshold;
- final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
- .getService(DeviceStorageMonitorService.SERVICE);
+ final DeviceStorageMonitorInternal
+ dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
if (dsm == null) {
Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
lowThreshold = 0L;
@@ -9738,10 +9746,10 @@ public class PackageManagerService extends IPackageManager.Stub {
clearExternalStorageDataSync(packageName, userId, true);
if (succeeded) {
// invoke DeviceStorageMonitor's update method to clear any notifications
- DeviceStorageMonitorService dsm = (DeviceStorageMonitorService)
- ServiceManager.getService(DeviceStorageMonitorService.SERVICE);
+ DeviceStorageMonitorInternal
+ dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
if (dsm != null) {
- dsm.updateMemory();
+ dsm.checkMemory();
}
}
if(observer != null) {
@@ -11566,12 +11574,17 @@ public class PackageManagerService extends IPackageManager.Stub {
return true;
}
+ @Override
public boolean isStorageLow() {
final long token = Binder.clearCallingIdentity();
try {
- final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
- .getService(DeviceStorageMonitorService.SERVICE);
- return dsm.isMemoryLow();
+ final DeviceStorageMonitorInternal
+ dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
+ if (dsm != null) {
+ return dsm.isMemoryLow();
+ } else {
+ return false;
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 60d44c7..b8a78e3 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -16,10 +16,11 @@
package com.android.server.power;
-import com.android.server.LightsService;
-import com.android.server.TwilightService;
-import com.android.server.TwilightService.TwilightState;
+import com.android.server.lights.LightsManager;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
import com.android.server.display.DisplayManagerService;
+import com.android.server.twilight.TwilightState;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -179,10 +180,10 @@ final class DisplayPowerController {
private Handler mCallbackHandler;
// The lights service.
- private final LightsService mLights;
+ private final LightsManager mLights;
// The twilight service.
- private final TwilightService mTwilight;
+ private final TwilightManager mTwilight;
// The display manager.
private final DisplayManagerService mDisplayManager;
@@ -351,7 +352,7 @@ final class DisplayPowerController {
* Creates the display power controller.
*/
public DisplayPowerController(Looper looper, Context context, Notifier notifier,
- LightsService lights, TwilightService twilight, SensorManager sensorManager,
+ LightsManager lights, TwilightManager twilight, SensorManager sensorManager,
DisplayManagerService displayManager,
SuspendBlocker displaySuspendBlocker, DisplayBlanker displayBlanker,
Callbacks callbacks, Handler callbackHandler) {
@@ -528,7 +529,7 @@ final class DisplayPowerController {
private void initialize() {
mPowerState = new DisplayPowerState(
new ElectronBeam(mDisplayManager), mDisplayBlanker,
- mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT));
+ mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT));
mElectronBeamOnAnimator = ObjectAnimator.ofFloat(
mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 0.0f, 1.0f);
@@ -1368,8 +1369,7 @@ final class DisplayPowerController {
}
};
- private final TwilightService.TwilightListener mTwilightListener =
- new TwilightService.TwilightListener() {
+ private final TwilightListener mTwilightListener = new TwilightListener() {
@Override
public void onTwilightStateChanged() {
mTwilightChanged = true;
diff --git a/services/java/com/android/server/power/DisplayPowerState.java b/services/java/com/android/server/power/DisplayPowerState.java
index fa318f8..42af4b4 100644
--- a/services/java/com/android/server/power/DisplayPowerState.java
+++ b/services/java/com/android/server/power/DisplayPowerState.java
@@ -16,7 +16,7 @@
package com.android.server.power;
-import com.android.server.LightsService;
+import com.android.server.lights.Light;
import android.os.AsyncTask;
import android.os.Handler;
@@ -56,7 +56,7 @@ final class DisplayPowerState {
private final Choreographer mChoreographer;
private final ElectronBeam mElectronBeam;
private final DisplayBlanker mDisplayBlanker;
- private final LightsService.Light mBacklight;
+ private final Light mBacklight;
private final PhotonicModulator mPhotonicModulator;
private boolean mScreenOn;
@@ -72,7 +72,7 @@ final class DisplayPowerState {
private Runnable mCleanListener;
public DisplayPowerState(ElectronBeam electronBean,
- DisplayBlanker displayBlanker, LightsService.Light backlight) {
+ DisplayBlanker displayBlanker, Light backlight) {
mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mElectronBeam = electronBean;
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index da9548f..13f55e2 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -20,10 +20,11 @@ import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.server.BatteryService;
import com.android.server.EventLogTags;
-import com.android.server.LightsService;
-import com.android.server.TwilightService;
+import com.android.server.lights.LightsService;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+import com.android.server.twilight.TwilightManager;
import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
@@ -168,7 +169,7 @@ public final class PowerManagerService extends IPowerManager.Stub
private static final int DREAM_BATTERY_LEVEL_DRAIN_CUTOFF = 5;
private Context mContext;
- private LightsService mLightsService;
+ private LightsManager mLightsManager;
private BatteryService mBatteryService;
private DisplayManagerService mDisplayManagerService;
private IBatteryStats mBatteryStats;
@@ -181,7 +182,7 @@ public final class PowerManagerService extends IPowerManager.Stub
private WirelessChargerDetector mWirelessChargerDetector;
private SettingsObserver mSettingsObserver;
private DreamManagerService mDreamManager;
- private LightsService.Light mAttentionLight;
+ private Light mAttentionLight;
private final Object mLock = new Object();
@@ -396,11 +397,11 @@ public final class PowerManagerService extends IPowerManager.Stub
* Initialize the power manager.
* Must be called before any other functions within the power manager are called.
*/
- public void init(Context context, LightsService ls,
- ActivityManagerService am, BatteryService bs, IBatteryStats bss,
+ public void init(Context context, LightsManager ls,
+ BatteryService bs, IBatteryStats bss,
IAppOpsService appOps, DisplayManagerService dm) {
mContext = context;
- mLightsService = ls;
+ mLightsManager = ls;
mBatteryService = bs;
mBatteryStats = bss;
mAppOps = appOps;
@@ -427,12 +428,12 @@ public final class PowerManagerService extends IPowerManager.Stub
}
}
- public void systemReady(TwilightService twilight, DreamManagerService dreamManager) {
+ public void systemReady(TwilightManager twilight, DreamManagerService dreamManager) {
synchronized (mLock) {
mSystemReady = true;
mDreamManager = dreamManager;
- PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
@@ -448,7 +449,7 @@ public final class PowerManagerService extends IPowerManager.Stub
// The display power controller runs on the power manager service's
// own handler thread to ensure timely operation.
mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(),
- mContext, mNotifier, mLightsService, twilight, sensorManager,
+ mContext, mNotifier, mLightsManager, twilight, sensorManager,
mDisplayManagerService, mDisplaySuspendBlocker, mDisplayBlanker,
mDisplayPowerControllerCallbacks, mHandler);
@@ -456,7 +457,7 @@ public final class PowerManagerService extends IPowerManager.Stub
createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
- mAttentionLight = mLightsService.getLight(LightsService.LIGHT_ID_ATTENTION);
+ mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
// Register for broadcasts from other components of the system.
IntentFilter filter = new IntentFilter();
@@ -2061,7 +2062,7 @@ public final class PowerManagerService extends IPowerManager.Stub
}
private void setAttentionLightInternal(boolean on, int color) {
- LightsService.Light light;
+ Light light;
synchronized (mLock) {
if (!mSystemReady) {
return;
@@ -2070,7 +2071,7 @@ public final class PowerManagerService extends IPowerManager.Stub
}
// Control light outside of lock.
- light.setFlashing(color, LightsService.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
+ light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
}
/**
diff --git a/services/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/java/com/android/server/statusbar/StatusBarManagerInternal.java
new file mode 100644
index 0000000..4f75189
--- /dev/null
+++ b/services/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2013, 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.statusbar;
+
+import com.android.server.notification.NotificationDelegate;
+
+import android.os.IBinder;
+import android.service.notification.StatusBarNotification;
+
+public interface StatusBarManagerInternal {
+ void setNotificationDelegate(NotificationDelegate delegate);
+ IBinder addNotification(StatusBarNotification notification);
+ void updateNotification(IBinder key, StatusBarNotification notification);
+ void removeNotification(IBinder key);
+}
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/statusbar/StatusBarManagerService.java
index f207c08..2ae467e 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -14,26 +14,28 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.statusbar;
import android.app.StatusBarManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationDelegate;
import com.android.server.wm.WindowManagerService;
import java.io.FileDescriptor;
@@ -51,31 +53,31 @@ import java.util.Map;
public class StatusBarManagerService extends IStatusBarService.Stub
implements WindowManagerService.OnHardKeyboardStatusChangeListener
{
- static final String TAG = "StatusBarManagerService";
- static final boolean SPEW = false;
-
- final Context mContext;
- final WindowManagerService mWindowManager;
- Handler mHandler = new Handler();
- NotificationCallbacks mNotificationCallbacks;
- volatile IStatusBar mBar;
- StatusBarIconList mIcons = new StatusBarIconList();
- HashMap<IBinder,StatusBarNotification> mNotifications
+ private static final String TAG = "StatusBarManagerService";
+ private static final boolean SPEW = false;
+
+ private final Context mContext;
+ private final WindowManagerService mWindowManager;
+ private Handler mHandler = new Handler();
+ private NotificationDelegate mNotificationDelegate;
+ private volatile IStatusBar mBar;
+ private StatusBarIconList mIcons = new StatusBarIconList();
+ private HashMap<IBinder,StatusBarNotification> mNotifications
= new HashMap<IBinder,StatusBarNotification>();
// for disabling the status bar
- final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
- IBinder mSysUiVisToken = new Binder();
- int mDisabled = 0;
+ private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
+ private IBinder mSysUiVisToken = new Binder();
+ private int mDisabled = 0;
- Object mLock = new Object();
+ private Object mLock = new Object();
// encompasses lights-out mode and other flags defined on View
- int mSystemUiVisibility = 0;
- boolean mMenuVisible = false;
- int mImeWindowVis = 0;
- int mImeBackDisposition;
- IBinder mImeToken = null;
- int mCurrentUserId;
+ private int mSystemUiVisibility = 0;
+ private boolean mMenuVisible = false;
+ private int mImeWindowVis = 0;
+ private int mImeBackDisposition;
+ private IBinder mImeToken = null;
+ private int mCurrentUserId;
private class DisableRecord implements IBinder.DeathRecipient {
int userId;
@@ -90,16 +92,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
- public interface NotificationCallbacks {
- void onSetDisabled(int status);
- void onClearAll();
- void onNotificationClick(String pkg, String tag, int id);
- void onNotificationClear(String pkg, String tag, int id);
- void onPanelRevealed();
- void onNotificationError(String pkg, String tag, int id,
- int uid, int initialPid, String message);
- }
-
/**
* Construct the service, add the status bar view to the window manager
*/
@@ -110,15 +102,74 @@ public class StatusBarManagerService extends IStatusBarService.Stub
final Resources res = context.getResources();
mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
- }
- public void setNotificationCallbacks(NotificationCallbacks listener) {
- mNotificationCallbacks = listener;
+ LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
}
+ /**
+ * Private API used by NotificationManagerService.
+ */
+ private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
+ @Override
+ public void setNotificationDelegate(NotificationDelegate delegate) {
+ synchronized (mNotifications) {
+ mNotificationDelegate = delegate;
+ }
+ }
+
+ @Override
+ public IBinder addNotification(StatusBarNotification notification) {
+ synchronized (mNotifications) {
+ IBinder key = new Binder();
+ mNotifications.put(key, notification);
+ if (mBar != null) {
+ try {
+ mBar.addNotification(key, notification);
+ } catch (RemoteException ex) {
+ }
+ }
+ return key;
+ }
+ }
+
+ @Override
+ public void updateNotification(IBinder key, StatusBarNotification notification) {
+ synchronized (mNotifications) {
+ if (!mNotifications.containsKey(key)) {
+ throw new IllegalArgumentException("updateNotification key not found: " + key);
+ }
+ mNotifications.put(key, notification);
+ if (mBar != null) {
+ try {
+ mBar.updateNotification(key, notification);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeNotification(IBinder key) {
+ synchronized (mNotifications) {
+ final StatusBarNotification n = mNotifications.remove(key);
+ if (n == null) {
+ Slog.e(TAG, "removeNotification key not found: " + key);
+ return;
+ }
+ if (mBar != null) {
+ try {
+ mBar.removeNotification(key);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+ };
+
// ================================================================================
// From IStatusBarService
// ================================================================================
+ @Override
public void expandNotificationsPanel() {
enforceExpandStatusBar();
@@ -130,6 +181,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void collapsePanels() {
enforceExpandStatusBar();
@@ -141,6 +193,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void expandSettingsPanel() {
enforceExpandStatusBar();
@@ -152,6 +205,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void disable(int what, IBinder token, String pkg) {
disableInternal(mCurrentUserId, what, token, pkg);
}
@@ -177,7 +231,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
mDisabled = net;
mHandler.post(new Runnable() {
public void run() {
- mNotificationCallbacks.onSetDisabled(net);
+ mNotificationDelegate.onSetDisabled(net);
}
});
if (mBar != null) {
@@ -189,6 +243,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
String contentDescription) {
enforceStatusBar();
@@ -214,6 +269,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void setIconVisibility(String slot, boolean visible) {
enforceStatusBar();
@@ -241,6 +297,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void removeIcon(String slot) {
enforceStatusBar();
@@ -265,6 +322,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
* Hide or show the on-screen Menu key. Only call this from the window manager, typically in
* response to a window with FLAG_NEEDS_MENU_KEY set.
*/
+ @Override
public void topAppWindowChanged(final boolean menuVisible) {
enforceStatusBar();
@@ -285,6 +343,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) {
enforceStatusBar();
@@ -312,6 +371,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void setSystemUiVisibility(int vis, int mask) {
// also allows calls from window manager which is in this process.
enforceStatusBarService();
@@ -344,6 +404,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ @Override
public void setHardKeyboardEnabled(final boolean enabled) {
mHandler.post(new Runnable() {
public void run() {
@@ -426,6 +487,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
// ================================================================================
// Callbacks from the status bar service.
// ================================================================================
+ @Override
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
int switches[], List<IBinder> binders) {
@@ -458,86 +520,64 @@ public class StatusBarManagerService extends IStatusBarService.Stub
* The status bar service should call this each time the user brings the panel from
* invisible to visible in order to clear the notification light.
*/
+ @Override
public void onPanelRevealed() {
enforceStatusBarService();
-
- // tell the notification manager to turn off the lights.
- mNotificationCallbacks.onPanelRevealed();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // tell the notification manager to turn off the lights.
+ mNotificationDelegate.onPanelRevealed();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
+ @Override
public void onNotificationClick(String pkg, String tag, int id) {
enforceStatusBarService();
-
- mNotificationCallbacks.onNotificationClick(pkg, tag, id);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationClick(pkg, tag, id);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
+ @Override
public void onNotificationError(String pkg, String tag, int id,
int uid, int initialPid, String message) {
enforceStatusBarService();
-
- // WARNING: this will call back into us to do the remove. Don't hold any locks.
- mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // WARNING: this will call back into us to do the remove. Don't hold any locks.
+ mNotificationDelegate.onNotificationError(pkg, tag, id, uid, initialPid, message);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
+ @Override
public void onNotificationClear(String pkg, String tag, int id) {
enforceStatusBarService();
-
- mNotificationCallbacks.onNotificationClear(pkg, tag, id);
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationClear(pkg, tag, id);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
+ @Override
public void onClearAllNotifications() {
enforceStatusBarService();
-
- mNotificationCallbacks.onClearAll();
- }
-
- // ================================================================================
- // Callbacks for NotificationManagerService.
- // ================================================================================
- public IBinder addNotification(StatusBarNotification notification) {
- synchronized (mNotifications) {
- IBinder key = new Binder();
- mNotifications.put(key, notification);
- if (mBar != null) {
- try {
- mBar.addNotification(key, notification);
- } catch (RemoteException ex) {
- }
- }
- return key;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onClearAll();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
- public void updateNotification(IBinder key, StatusBarNotification notification) {
- synchronized (mNotifications) {
- if (!mNotifications.containsKey(key)) {
- throw new IllegalArgumentException("updateNotification key not found: " + key);
- }
- mNotifications.put(key, notification);
- if (mBar != null) {
- try {
- mBar.updateNotification(key, notification);
- } catch (RemoteException ex) {
- }
- }
- }
- }
-
- public void removeNotification(IBinder key) {
- synchronized (mNotifications) {
- final StatusBarNotification n = mNotifications.remove(key);
- if (n == null) {
- Slog.e(TAG, "removeNotification key not found: " + key);
- return;
- }
- if (mBar != null) {
- try {
- mBar.removeNotification(key);
- } catch (RemoteException ex) {
- }
- }
- }
- }
// ================================================================================
// Can be called from any thread
diff --git a/services/java/com/android/server/storage/DeviceStorageMonitorInternal.java b/services/java/com/android/server/storage/DeviceStorageMonitorInternal.java
new file mode 100644
index 0000000..a91a81b
--- /dev/null
+++ b/services/java/com/android/server/storage/DeviceStorageMonitorInternal.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2013, 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.storage;
+
+public interface DeviceStorageMonitorInternal {
+ boolean isMemoryLow();
+ long getMemoryLowThreshold();
+ void checkMemory();
+}
+
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/storage/DeviceStorageMonitorService.java
index 016c561..8805084 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.storage;
+
+import com.android.server.EventLogTags;
+import com.android.server.SystemService;
import android.app.Notification;
import android.app.NotificationManager;
@@ -29,8 +32,8 @@ import android.os.Binder;
import android.os.Environment;
import android.os.FileObserver;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
@@ -66,13 +69,13 @@ import java.io.PrintWriter;
* settings parameter with a default value of 2MB), the free memory is
* logged to the event log.
*/
-public class DeviceStorageMonitorService extends Binder {
- private static final String TAG = "DeviceStorageMonitorService";
+public class DeviceStorageMonitorService extends SystemService {
+ static final String TAG = "DeviceStorageMonitorService";
- private static final boolean DEBUG = false;
- private static final boolean localLOGV = false;
+ static final boolean DEBUG = false;
+ static final boolean localLOGV = false;
- private static final int DEVICE_MEMORY_WHAT = 1;
+ static final int DEVICE_MEMORY_WHAT = 1;
private static final int MONITOR_INTERVAL = 1; //in minutes
private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
@@ -84,9 +87,8 @@ public class DeviceStorageMonitorService extends Binder {
private long mFreeMemAfterLastCacheClear; // on /data
private long mLastReportedFreeMem;
private long mLastReportedFreeMemTime;
- private boolean mLowMemFlag=false;
+ boolean mLowMemFlag=false;
private boolean mMemFullFlag=false;
- private Context mContext;
private ContentResolver mResolver;
private long mTotalMemory; // on /data
private StatFs mDataFileStats;
@@ -98,19 +100,19 @@ public class DeviceStorageMonitorService extends Binder {
private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
private long mThreadStartTime = -1;
- private boolean mClearSucceeded = false;
- private boolean mClearingCache;
+ boolean mClearSucceeded = false;
+ boolean mClearingCache;
private Intent mStorageLowIntent;
private Intent mStorageOkIntent;
private Intent mStorageFullIntent;
private Intent mStorageNotFullIntent;
private CachePackageDataObserver mClearCacheObserver;
- private final CacheFileDeletedObserver mCacheFileDeletedObserver;
+ private CacheFileDeletedObserver mCacheFileDeletedObserver;
private static final int _TRUE = 1;
private static final int _FALSE = 0;
// This is the raw threshold that has been set at which we consider
// storage to be low.
- private long mMemLowThreshold;
+ long mMemLowThreshold;
// This is the threshold at which we start trying to flush caches
// to get below the low threshold limit. It is less than the low
// threshold; we will allow storage to get a bit beyond the limit
@@ -126,13 +128,13 @@ public class DeviceStorageMonitorService extends Binder {
/**
* This string is used for ServiceManager access to this class.
*/
- public static final String SERVICE = "devicestoragemonitor";
+ static final String SERVICE = "devicestoragemonitor";
/**
* Handler that checks the amount of disk space on the device and sends a
* notification if the device runs low on disk space
*/
- Handler mHandler = new Handler() {
+ private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//don't handle an invalid message
@@ -144,7 +146,7 @@ public class DeviceStorageMonitorService extends Binder {
}
};
- class CachePackageDataObserver extends IPackageDataObserver.Stub {
+ private class CachePackageDataObserver extends IPackageDataObserver.Stub {
public void onRemoveCompleted(String packageName, boolean succeeded) {
mClearSucceeded = succeeded;
mClearingCache = false;
@@ -154,7 +156,7 @@ public class DeviceStorageMonitorService extends Binder {
}
}
- private final void restatDataDir() {
+ private void restatDataDir() {
try {
mDataFileStats.restat(DATA_PATH.getAbsolutePath());
mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
@@ -206,7 +208,7 @@ public class DeviceStorageMonitorService extends Binder {
}
}
- private final void clearCache() {
+ private void clearCache() {
if (mClearCacheObserver == null) {
// Lazy instantiation
mClearCacheObserver = new CachePackageDataObserver();
@@ -223,7 +225,7 @@ public class DeviceStorageMonitorService extends Binder {
}
}
- private final void checkMemory(boolean checkCache) {
+ void checkMemory(boolean checkCache) {
//if the thread that was started to clear cache is still running do nothing till its
//finished clearing cache. Ideally this flag could be modified by clearCache
// and should be accessed via a lock but even if it does this test will fail now and
@@ -300,7 +302,7 @@ public class DeviceStorageMonitorService extends Binder {
postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
}
- private void postCheckMemoryMsg(boolean clearCache, long delay) {
+ void postCheckMemoryMsg(boolean clearCache, long delay) {
// Remove queued messages
mHandler.removeMessages(DEVICE_MEMORY_WHAT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
@@ -312,10 +314,10 @@ public class DeviceStorageMonitorService extends Binder {
* Constructor to run service. initializes the disk space threshold value
* and posts an empty message to kickstart the process.
*/
- public DeviceStorageMonitorService(Context context) {
+ @Override
+ public void onCreate(Context context) {
mLastReportedFreeMemTime = 0;
- mContext = context;
- mResolver = mContext.getContentResolver();
+ mResolver = context.getContentResolver();
//create StatFs object
mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
@@ -331,9 +333,12 @@ public class DeviceStorageMonitorService extends Binder {
mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ }
+ @Override
+ public void onStart() {
// cache storage thresholds
- final StorageManager sm = StorageManager.from(context);
+ final StorageManager sm = StorageManager.from(getContext());
mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
@@ -345,6 +350,78 @@ public class DeviceStorageMonitorService extends Binder {
mCacheFileDeletedObserver = new CacheFileDeletedObserver();
mCacheFileDeletedObserver.startWatching();
+
+ publishBinderService(SERVICE, mRemoteService);
+ publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
+ }
+
+ private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
+ @Override
+ public void checkMemory() {
+ // force an early check
+ postCheckMemoryMsg(true, 0);
+ }
+
+ @Override
+ public boolean isMemoryLow() {
+ return mLowMemFlag;
+ }
+
+ @Override
+ public long getMemoryLowThreshold() {
+ return mMemLowThreshold;
+ }
+ };
+
+ private final IBinder mRemoteService = new Binder() {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ pw.println("Permission Denial: can't dump " + SERVICE + " from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ dumpImpl(pw);
+ }
+ };
+
+ void dumpImpl(PrintWriter pw) {
+ final Context context = getContext();
+
+ pw.println("Current DeviceStorageMonitor state:");
+
+ pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem));
+ pw.print(" mTotalMemory=");
+ pw.println(Formatter.formatFileSize(context, mTotalMemory));
+
+ pw.print(" mFreeMemAfterLastCacheClear=");
+ pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
+
+ pw.print(" mLastReportedFreeMem=");
+ pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
+ pw.print(" mLastReportedFreeMemTime=");
+ TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
+ pw.println();
+
+ pw.print(" mLowMemFlag="); pw.print(mLowMemFlag);
+ pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
+
+ pw.print(" mClearSucceeded="); pw.print(mClearSucceeded);
+ pw.print(" mClearingCache="); pw.println(mClearingCache);
+
+ pw.print(" mMemLowThreshold=");
+ pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
+ pw.print(" mMemFullThreshold=");
+ pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
+
+ pw.print(" mMemCacheStartTrimThreshold=");
+ pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
+ pw.print(" mMemCacheTrimToThreshold=");
+ pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
}
/**
@@ -352,7 +429,8 @@ public class DeviceStorageMonitorService extends Binder {
* an error dialog indicating low disk space and launch the Installer
* application
*/
- private final void sendNotification() {
+ private void sendNotification() {
+ final Context context = getContext();
if(localLOGV) Slog.i(TAG, "Sending low memory notification");
//log the event to event log with the amount of free storage(in bytes) left on the device
EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
@@ -363,86 +441,58 @@ public class DeviceStorageMonitorService extends Binder {
lowMemIntent.putExtra("memory", mFreeMem);
lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
NotificationManager mNotificationMgr =
- (NotificationManager)mContext.getSystemService(
+ (NotificationManager)context.getSystemService(
Context.NOTIFICATION_SERVICE);
- CharSequence title = mContext.getText(
+ CharSequence title = context.getText(
com.android.internal.R.string.low_internal_storage_view_title);
- CharSequence details = mContext.getText(
+ CharSequence details = context.getText(
com.android.internal.R.string.low_internal_storage_view_text);
- PendingIntent intent = PendingIntent.getActivityAsUser(mContext, 0, lowMemIntent, 0,
+ PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0,
null, UserHandle.CURRENT);
Notification notification = new Notification();
notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
notification.tickerText = title;
notification.flags |= Notification.FLAG_NO_CLEAR;
- notification.setLatestEventInfo(mContext, title, details, intent);
+ notification.setLatestEventInfo(context, title, details, intent);
mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
UserHandle.ALL);
- mContext.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+ context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
}
/**
* Cancels low storage notification and sends OK intent.
*/
- private final void cancelNotification() {
+ private void cancelNotification() {
+ final Context context = getContext();
if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
NotificationManager mNotificationMgr =
- (NotificationManager)mContext.getSystemService(
+ (NotificationManager)context.getSystemService(
Context.NOTIFICATION_SERVICE);
//cancel notification since memory has been freed
mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
- mContext.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
- mContext.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
+ context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+ context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
}
/**
* Send a notification when storage is full.
*/
- private final void sendFullNotification() {
+ private void sendFullNotification() {
if(localLOGV) Slog.i(TAG, "Sending memory full notification");
- mContext.sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
+ getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
}
/**
* Cancels memory full notification and sends "not full" intent.
*/
- private final void cancelFullNotification() {
+ private void cancelFullNotification() {
if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
- mContext.removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
- mContext.sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
+ getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
}
- public void updateMemory() {
- int callingUid = getCallingUid();
- if(callingUid != Process.SYSTEM_UID) {
- return;
- }
- // force an early check
- postCheckMemoryMsg(true, 0);
- }
-
- /**
- * Callable from other things in the system service to obtain the low memory
- * threshold.
- *
- * @return low memory threshold in bytes
- */
- public long getMemoryLowThreshold() {
- return mMemLowThreshold;
- }
-
- /**
- * Callable from other things in the system process to check whether memory
- * is low.
- *
- * @return true is memory is low
- */
- public boolean isMemoryLow() {
- return mLowMemFlag;
- }
-
- public static class CacheFileDeletedObserver extends FileObserver {
+ private static class CacheFileDeletedObserver extends FileObserver {
public CacheFileDeletedObserver() {
super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
}
@@ -452,40 +502,4 @@ public class DeviceStorageMonitorService extends Binder {
EventLogTags.writeCacheFileDeleted(path);
}
}
-
- @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 " + SERVICE + " from from pid="
- + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- return;
- }
-
- pw.println("Current DeviceStorageMonitor state:");
- pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(mContext, mFreeMem));
- pw.print(" mTotalMemory=");
- pw.println(Formatter.formatFileSize(mContext, mTotalMemory));
- pw.print(" mFreeMemAfterLastCacheClear=");
- pw.println(Formatter.formatFileSize(mContext, mFreeMemAfterLastCacheClear));
- pw.print(" mLastReportedFreeMem=");
- pw.print(Formatter.formatFileSize(mContext, mLastReportedFreeMem));
- pw.print(" mLastReportedFreeMemTime=");
- TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
- pw.println();
- pw.print(" mLowMemFlag="); pw.print(mLowMemFlag);
- pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
- pw.print(" mClearSucceeded="); pw.print(mClearSucceeded);
- pw.print(" mClearingCache="); pw.println(mClearingCache);
- pw.print(" mMemLowThreshold=");
- pw.print(Formatter.formatFileSize(mContext, mMemLowThreshold));
- pw.print(" mMemFullThreshold=");
- pw.println(Formatter.formatFileSize(mContext, mMemFullThreshold));
- pw.print(" mMemCacheStartTrimThreshold=");
- pw.print(Formatter.formatFileSize(mContext, mMemCacheStartTrimThreshold));
- pw.print(" mMemCacheTrimToThreshold=");
- pw.println(Formatter.formatFileSize(mContext, mMemCacheTrimToThreshold));
- }
}
diff --git a/services/java/com/android/server/twilight/TwilightListener.java b/services/java/com/android/server/twilight/TwilightListener.java
new file mode 100644
index 0000000..29ead44
--- /dev/null
+++ b/services/java/com/android/server/twilight/TwilightListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013 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.twilight;
+
+public interface TwilightListener {
+ void onTwilightStateChanged();
+} \ No newline at end of file
diff --git a/services/java/com/android/server/twilight/TwilightManager.java b/services/java/com/android/server/twilight/TwilightManager.java
new file mode 100644
index 0000000..b3de58b
--- /dev/null
+++ b/services/java/com/android/server/twilight/TwilightManager.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 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.twilight;
+
+import android.os.Handler;
+
+public interface TwilightManager {
+ void registerListener(TwilightListener listener, Handler handler);
+ TwilightState getCurrentState();
+}
diff --git a/services/java/com/android/server/TwilightService.java b/services/java/com/android/server/twilight/TwilightService.java
index 0356faa..8feb97b 100644
--- a/services/java/com/android/server/TwilightService.java
+++ b/services/java/com/android/server/twilight/TwilightService.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.twilight;
+
+import com.android.server.SystemService;
+import com.android.server.TwilightCalculator;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -34,9 +37,7 @@ import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Slog;
-import java.text.DateFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.Iterator;
import libcore.util.Objects;
@@ -47,78 +48,88 @@ import libcore.util.Objects;
* Used by the UI mode manager and other components to adjust night mode
* effects based on sunrise and sunset.
*/
-public final class TwilightService {
- private static final String TAG = "TwilightService";
-
- private static final boolean DEBUG = false;
-
- private static final String ACTION_UPDATE_TWILIGHT_STATE =
+public final class TwilightService extends SystemService {
+ static final String TAG = "TwilightService";
+ static final boolean DEBUG = false;
+ static final String ACTION_UPDATE_TWILIGHT_STATE =
"com.android.server.action.UPDATE_TWILIGHT_STATE";
- private final Context mContext;
- private final AlarmManager mAlarmManager;
- private final LocationManager mLocationManager;
- private final LocationHandler mLocationHandler;
+ final Object mLock = new Object();
- private final Object mLock = new Object();
+ AlarmManager mAlarmManager;
+ LocationManager mLocationManager;
+ LocationHandler mLocationHandler;
- private final ArrayList<TwilightListenerRecord> mListeners =
+ final ArrayList<TwilightListenerRecord> mListeners =
new ArrayList<TwilightListenerRecord>();
- private boolean mSystemReady;
+ TwilightState mTwilightState;
- private TwilightState mTwilightState;
+ @Override
+ public void onStart() {
+ mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ mLocationManager = (LocationManager) getContext().getSystemService(
+ Context.LOCATION_SERVICE);
+ mLocationHandler = new LocationHandler();
- public TwilightService(Context context) {
- mContext = context;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(ACTION_UPDATE_TWILIGHT_STATE);
+ getContext().registerReceiver(mUpdateLocationReceiver, filter);
- mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- mLocationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE);
- mLocationHandler = new LocationHandler();
+ publishLocalService(TwilightManager.class, mService);
}
- void systemReady() {
- synchronized (mLock) {
- mSystemReady = true;
+ private static class TwilightListenerRecord implements Runnable {
+ private final TwilightListener mListener;
+ private final Handler mHandler;
- IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(ACTION_UPDATE_TWILIGHT_STATE);
- mContext.registerReceiver(mUpdateLocationReceiver, filter);
+ public TwilightListenerRecord(TwilightListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
- if (!mListeners.isEmpty()) {
- mLocationHandler.enableLocationUpdates();
- }
+ public void postUpdate() {
+ mHandler.post(this);
}
- }
- /**
- * Gets the current twilight state.
- *
- * @return The current twilight state, or null if no information is available.
- */
- public TwilightState getCurrentState() {
- synchronized (mLock) {
- return mTwilightState;
+ @Override
+ public void run() {
+ mListener.onTwilightStateChanged();
}
+
}
- /**
- * Listens for twilight time.
- *
- * @param listener The listener.
- * @param handler The handler on which to post calls into the listener.
- */
- public void registerListener(TwilightListener listener, Handler handler) {
- synchronized (mLock) {
- mListeners.add(new TwilightListenerRecord(listener, handler));
+ private final TwilightManager mService = new TwilightManager() {
+ /**
+ * Gets the current twilight state.
+ *
+ * @return The current twilight state, or null if no information is available.
+ */
+ @Override
+ public TwilightState getCurrentState() {
+ synchronized (mLock) {
+ return mTwilightState;
+ }
+ }
- if (mSystemReady && mListeners.size() == 1) {
- mLocationHandler.enableLocationUpdates();
+ /**
+ * Listens for twilight time.
+ *
+ * @param listener The listener.
+ */
+ @Override
+ public void registerListener(TwilightListener listener, Handler handler) {
+ synchronized (mLock) {
+ mListeners.add(new TwilightListenerRecord(listener, handler));
+
+ if (mListeners.size() == 1) {
+ mLocationHandler.enableLocationUpdates();
+ }
}
}
- }
+ };
private void setTwilightState(TwilightState state) {
synchronized (mLock) {
@@ -128,9 +139,10 @@ public final class TwilightService {
}
mTwilightState = state;
- int count = mListeners.size();
- for (int i = 0; i < count; i++) {
- mListeners.get(i).post();
+
+ final int listenerLen = mListeners.size();
+ for (int i = 0; i < listenerLen; i++) {
+ mListeners.get(i).postUpdate();
}
}
}
@@ -162,124 +174,6 @@ public final class TwilightService {
return distance >= totalAccuracy;
}
- /**
- * Describes whether it is day or night.
- * This object is immutable.
- */
- public static final class TwilightState {
- private final boolean mIsNight;
- private final long mYesterdaySunset;
- private final long mTodaySunrise;
- private final long mTodaySunset;
- private final long mTomorrowSunrise;
-
- TwilightState(boolean isNight,
- long yesterdaySunset,
- long todaySunrise, long todaySunset,
- long tomorrowSunrise) {
- mIsNight = isNight;
- mYesterdaySunset = yesterdaySunset;
- mTodaySunrise = todaySunrise;
- mTodaySunset = todaySunset;
- mTomorrowSunrise = tomorrowSunrise;
- }
-
- /**
- * Returns true if it is currently night time.
- */
- public boolean isNight() {
- return mIsNight;
- }
-
- /**
- * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase,
- * or -1 if the sun never sets.
- */
- public long getYesterdaySunset() {
- return mYesterdaySunset;
- }
-
- /**
- * Returns the time of today's sunrise in the System.currentTimeMillis() timebase,
- * or -1 if the sun never rises.
- */
- public long getTodaySunrise() {
- return mTodaySunrise;
- }
-
- /**
- * Returns the time of today's sunset in the System.currentTimeMillis() timebase,
- * or -1 if the sun never sets.
- */
- public long getTodaySunset() {
- return mTodaySunset;
- }
-
- /**
- * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase,
- * or -1 if the sun never rises.
- */
- public long getTomorrowSunrise() {
- return mTomorrowSunrise;
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof TwilightState && equals((TwilightState)o);
- }
-
- public boolean equals(TwilightState other) {
- return other != null
- && mIsNight == other.mIsNight
- && mYesterdaySunset == other.mYesterdaySunset
- && mTodaySunrise == other.mTodaySunrise
- && mTodaySunset == other.mTodaySunset
- && mTomorrowSunrise == other.mTomorrowSunrise;
- }
-
- @Override
- public int hashCode() {
- return 0; // don't care
- }
-
- @Override
- public String toString() {
- DateFormat f = DateFormat.getDateTimeInstance();
- return "{TwilightState: isNight=" + mIsNight
- + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset))
- + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise))
- + ", mTodaySunset=" + f.format(new Date(mTodaySunset))
- + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise))
- + "}";
- }
- }
-
- /**
- * Listener for changes in twilight state.
- */
- public interface TwilightListener {
- public void onTwilightStateChanged();
- }
-
- private static final class TwilightListenerRecord implements Runnable {
- private final TwilightListener mListener;
- private final Handler mHandler;
-
- public TwilightListenerRecord(TwilightListener listener, Handler handler) {
- mListener = listener;
- mHandler = handler;
- }
-
- public void post() {
- mHandler.post(this);
- }
-
- @Override
- public void run() {
- mListener.onTwilightStateChanged();
- }
- }
-
private final class LocationHandler extends Handler {
private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
private static final int MSG_GET_NEW_LOCATION_UPDATE = 2;
@@ -518,11 +412,12 @@ public final class TwilightService {
}
Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, updateIntent, 0);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ getContext(), 0, updateIntent, 0);
mAlarmManager.cancel(pendingIntent);
mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent);
}
- };
+ }
private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() {
@Override
diff --git a/services/java/com/android/server/twilight/TwilightState.java b/services/java/com/android/server/twilight/TwilightState.java
new file mode 100644
index 0000000..91e24d7
--- /dev/null
+++ b/services/java/com/android/server/twilight/TwilightState.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 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.twilight;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * Describes whether it is day or night.
+ * This object is immutable.
+ */
+public class TwilightState {
+ private final boolean mIsNight;
+ private final long mYesterdaySunset;
+ private final long mTodaySunrise;
+ private final long mTodaySunset;
+ private final long mTomorrowSunrise;
+
+ TwilightState(boolean isNight,
+ long yesterdaySunset,
+ long todaySunrise, long todaySunset,
+ long tomorrowSunrise) {
+ mIsNight = isNight;
+ mYesterdaySunset = yesterdaySunset;
+ mTodaySunrise = todaySunrise;
+ mTodaySunset = todaySunset;
+ mTomorrowSunrise = tomorrowSunrise;
+ }
+
+ /**
+ * Returns true if it is currently night time.
+ */
+ public boolean isNight() {
+ return mIsNight;
+ }
+
+ /**
+ * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase,
+ * or -1 if the sun never sets.
+ */
+ public long getYesterdaySunset() {
+ return mYesterdaySunset;
+ }
+
+ /**
+ * Returns the time of today's sunrise in the System.currentTimeMillis() timebase,
+ * or -1 if the sun never rises.
+ */
+ public long getTodaySunrise() {
+ return mTodaySunrise;
+ }
+
+ /**
+ * Returns the time of today's sunset in the System.currentTimeMillis() timebase,
+ * or -1 if the sun never sets.
+ */
+ public long getTodaySunset() {
+ return mTodaySunset;
+ }
+
+ /**
+ * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase,
+ * or -1 if the sun never rises.
+ */
+ public long getTomorrowSunrise() {
+ return mTomorrowSunrise;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof TwilightState && equals((TwilightState)o);
+ }
+
+ public boolean equals(TwilightState other) {
+ return other != null
+ && mIsNight == other.mIsNight
+ && mYesterdaySunset == other.mYesterdaySunset
+ && mTodaySunrise == other.mTodaySunrise
+ && mTodaySunset == other.mTodaySunset
+ && mTomorrowSunrise == other.mTomorrowSunrise;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ @Override
+ public String toString() {
+ DateFormat f = DateFormat.getDateTimeInstance();
+ return "{TwilightState: isNight=" + mIsNight
+ + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset))
+ + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise))
+ + ", mTodaySunset=" + f.format(new Date(mTodaySunset))
+ + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise))
+ + "}";
+ }
+}
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 5a60de0..c1a3646 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -53,6 +53,7 @@ import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -287,12 +288,7 @@ public class UsbDeviceManager {
}
private static boolean containsFunction(String functions, String function) {
- int index = functions.indexOf(function);
- if (index < 0) return false;
- if (index > 0 && functions.charAt(index - 1) != ',') return false;
- int charAfter = index + function.length();
- if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
- return true;
+ return Arrays.asList(functions.split(",")).contains(function);
}
private final class UsbHandler extends Handler {
diff --git a/services/java/com/android/server/wifi/NetworkUpdateResult.java b/services/java/com/android/server/wifi/NetworkUpdateResult.java
new file mode 100644
index 0000000..63cc33f
--- /dev/null
+++ b/services/java/com/android/server/wifi/NetworkUpdateResult.java
@@ -0,0 +1,70 @@
+/*
+ * 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 static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
+class NetworkUpdateResult {
+ int netId;
+ boolean ipChanged;
+ boolean proxyChanged;
+ boolean isNewNetwork = false;
+
+ public NetworkUpdateResult(int id) {
+ netId = id;
+ ipChanged = false;
+ proxyChanged = false;
+ }
+
+ public NetworkUpdateResult(boolean ip, boolean proxy) {
+ netId = INVALID_NETWORK_ID;
+ ipChanged = ip;
+ proxyChanged = proxy;
+ }
+
+ public void setNetworkId(int id) {
+ netId = id;
+ }
+
+ public int getNetworkId() {
+ return netId;
+ }
+
+ public void setIpChanged(boolean ip) {
+ ipChanged = ip;
+ }
+
+ public boolean hasIpChanged() {
+ return ipChanged;
+ }
+
+ public void setProxyChanged(boolean proxy) {
+ proxyChanged = proxy;
+ }
+
+ public boolean hasProxyChanged() {
+ return proxyChanged;
+ }
+
+ public boolean isNewNetwork() {
+ return isNewNetwork;
+ }
+
+ public void setIsNewNetwork(boolean isNew) {
+ isNewNetwork = isNew;
+ }
+}
diff --git a/services/java/com/android/server/wifi/StateChangeResult.java b/services/java/com/android/server/wifi/StateChangeResult.java
new file mode 100644
index 0000000..7d2f2b4
--- /dev/null
+++ b/services/java/com/android/server/wifi/StateChangeResult.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.net.wifi.SupplicantState;
+import android.net.wifi.WifiSsid;
+
+/**
+ * Stores supplicant state change information passed from WifiMonitor to
+ * a state machine. WifiStateMachine, SupplicantStateTracker and WpsStateMachine
+ * are example state machines that handle it.
+ * @hide
+ */
+public class StateChangeResult {
+ StateChangeResult(int networkId, WifiSsid wifiSsid, String BSSID,
+ SupplicantState state) {
+ this.state = state;
+ this.wifiSsid= wifiSsid;
+ this.BSSID = BSSID;
+ this.networkId = networkId;
+ }
+
+ int networkId;
+ WifiSsid wifiSsid;
+ String BSSID;
+ SupplicantState state;
+}
diff --git a/services/java/com/android/server/wifi/SupplicantStateTracker.java b/services/java/com/android/server/wifi/SupplicantStateTracker.java
new file mode 100644
index 0000000..f8048ff
--- /dev/null
+++ b/services/java/com/android/server/wifi/SupplicantStateTracker.java
@@ -0,0 +1,363 @@
+/*
+ * 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 com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Tracks the state changes in supplicant and provides functionality
+ * that is based on these state changes:
+ * - detect a failed WPA handshake that loops indefinitely
+ * - authentication failure handling
+ */
+class SupplicantStateTracker extends StateMachine {
+
+ private static final String TAG = "SupplicantStateTracker";
+ private static final boolean DBG = false;
+
+ private WifiStateMachine mWifiStateMachine;
+ private WifiConfigStore mWifiConfigStore;
+ private int mAuthenticationFailuresCount = 0;
+ private int mAssociationRejectCount = 0;
+ /* Indicates authentication failure in supplicant broadcast.
+ * TODO: enhance auth failure reporting to include notification
+ * for all type of failures: EAP, WPS & WPA networks */
+ private boolean mAuthFailureInSupplicantBroadcast = false;
+
+ /* Maximum retries on a authentication failure notification */
+ private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
+
+ /* Maximum retries on assoc rejection events */
+ private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16;
+
+ /* Tracks if networks have been disabled during a connection */
+ private boolean mNetworksDisabledDuringConnect = false;
+
+ private Context mContext;
+
+ private State mUninitializedState = new UninitializedState();
+ private State mDefaultState = new DefaultState();
+ private State mInactiveState = new InactiveState();
+ private State mDisconnectState = new DisconnectedState();
+ private State mScanState = new ScanState();
+ private State mHandshakeState = new HandshakeState();
+ private State mCompletedState = new CompletedState();
+ private State mDormantState = new DormantState();
+
+ public SupplicantStateTracker(Context c, WifiStateMachine wsm, WifiConfigStore wcs, Handler t) {
+ super(TAG, t.getLooper());
+
+ mContext = c;
+ mWifiStateMachine = wsm;
+ mWifiConfigStore = wcs;
+ addState(mDefaultState);
+ addState(mUninitializedState, mDefaultState);
+ addState(mInactiveState, mDefaultState);
+ addState(mDisconnectState, mDefaultState);
+ addState(mScanState, mDefaultState);
+ addState(mHandshakeState, mDefaultState);
+ addState(mCompletedState, mDefaultState);
+ addState(mDormantState, mDefaultState);
+
+ setInitialState(mUninitializedState);
+ setLogRecSize(50);
+ setLogOnlyTransitions(true);
+ //start the state machine
+ start();
+ }
+
+ private void handleNetworkConnectionFailure(int netId, int disableReason) {
+ /* If other networks disabled during connection, enable them */
+ if (mNetworksDisabledDuringConnect) {
+ mWifiConfigStore.enableAllNetworks();
+ mNetworksDisabledDuringConnect = false;
+ }
+ /* Disable failed network */
+ mWifiConfigStore.disableNetwork(netId, disableReason);
+ }
+
+ private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
+ SupplicantState supState = (SupplicantState) stateChangeResult.state;
+
+ if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
+
+ switch (supState) {
+ case DISCONNECTED:
+ transitionTo(mDisconnectState);
+ break;
+ case INTERFACE_DISABLED:
+ //we should have received a disconnection already, do nothing
+ break;
+ case SCANNING:
+ transitionTo(mScanState);
+ break;
+ case AUTHENTICATING:
+ case ASSOCIATING:
+ case ASSOCIATED:
+ case FOUR_WAY_HANDSHAKE:
+ case GROUP_HANDSHAKE:
+ transitionTo(mHandshakeState);
+ break;
+ case COMPLETED:
+ transitionTo(mCompletedState);
+ break;
+ case DORMANT:
+ transitionTo(mDormantState);
+ break;
+ case INACTIVE:
+ transitionTo(mInactiveState);
+ break;
+ case UNINITIALIZED:
+ case INVALID:
+ transitionTo(mUninitializedState);
+ break;
+ default:
+ Log.e(TAG, "Unknown supplicant state " + supState);
+ break;
+ }
+ }
+
+ private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
+ Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
+ if (failedAuth) {
+ intent.putExtra(
+ WifiManager.EXTRA_SUPPLICANT_ERROR,
+ WifiManager.ERROR_AUTHENTICATING);
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /********************************************************
+ * HSM states
+ *******************************************************/
+
+ class DefaultState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch (message.what) {
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ mAuthenticationFailuresCount++;
+ mAuthFailureInSupplicantBroadcast = true;
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
+ mAuthFailureInSupplicantBroadcast = false;
+ transitionOnSupplicantStateChange(stateChangeResult);
+ break;
+ case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
+ transitionTo(mUninitializedState);
+ break;
+ case WifiManager.CONNECT_NETWORK:
+ mNetworksDisabledDuringConnect = true;
+ mAssociationRejectCount = 0;
+ break;
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ mAssociationRejectCount++;
+ break;
+ default:
+ Log.e(TAG, "Ignoring " + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ /*
+ * This indicates that the supplicant state as seen
+ * by the framework is not initialized yet. We are
+ * in this state right after establishing a control
+ * channel connection before any supplicant events
+ * or after we have lost the control channel
+ * connection to the supplicant
+ */
+ class UninitializedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class InactiveState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class DisconnectedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* If a disconnect event happens after authentication failure
+ * exceeds maximum retries, disable the network
+ */
+ Message message = getCurrentMessage();
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+ if (mAuthenticationFailuresCount >= MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
+ Log.d(TAG, "Failed to authenticate, disabling network " +
+ stateChangeResult.networkId);
+ handleNetworkConnectionFailure(stateChangeResult.networkId,
+ WifiConfiguration.DISABLED_AUTH_FAILURE);
+ mAuthenticationFailuresCount = 0;
+ }
+ else if (mAssociationRejectCount >= MAX_RETRIES_ON_ASSOCIATION_REJECT) {
+ Log.d(TAG, "Association getting rejected, disabling network " +
+ stateChangeResult.networkId);
+ handleNetworkConnectionFailure(stateChangeResult.networkId,
+ WifiConfiguration.DISABLED_ASSOCIATION_REJECT);
+ mAssociationRejectCount = 0;
+ }
+ }
+ }
+
+ class ScanState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class HandshakeState extends State {
+ /**
+ * The max number of the WPA supplicant loop iterations before we
+ * decide that the loop should be terminated:
+ */
+ private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+ private int mLoopDetectIndex;
+ private int mLoopDetectCount;
+
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ mLoopDetectIndex = 0;
+ mLoopDetectCount = 0;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch (message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ if (SupplicantState.isHandshakeState(state)) {
+ if (mLoopDetectIndex > state.ordinal()) {
+ mLoopDetectCount++;
+ }
+ if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+ Log.d(TAG, "Supplicant loop detected, disabling network " +
+ stateChangeResult.networkId);
+ handleNetworkConnectionFailure(stateChangeResult.networkId,
+ WifiConfiguration.DISABLED_AUTH_FAILURE);
+ }
+ mLoopDetectIndex = state.ordinal();
+ sendSupplicantStateChangedBroadcast(state,
+ mAuthFailureInSupplicantBroadcast);
+ } else {
+ //Have the DefaultState handle the transition
+ return NOT_HANDLED;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class CompletedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* Reset authentication failure count */
+ mAuthenticationFailuresCount = 0;
+ mAssociationRejectCount = 0;
+ if (mNetworksDisabledDuringConnect) {
+ mWifiConfigStore.enableAllNetworks();
+ mNetworksDisabledDuringConnect = false;
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch(message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
+ /* Ignore any connecting state in completed state. Group re-keying
+ * events and other auth events that do not affect connectivity are
+ * ignored
+ */
+ if (SupplicantState.isConnecting(state)) {
+ break;
+ }
+ transitionOnSupplicantStateChange(stateChangeResult);
+ break;
+ case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
+ sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
+ transitionTo(mUninitializedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ //TODO: remove after getting rid of the state in supplicant
+ class DormantState extends State {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mAuthenticationFailuresCount " + mAuthenticationFailuresCount);
+ pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
+ pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
+ pw.println();
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiApConfigStore.java b/services/java/com/android/server/wifi/WifiApConfigStore.java
new file mode 100644
index 0000000..5e28fcf
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiApConfigStore.java
@@ -0,0 +1,212 @@
+/*
+ * 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.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.R;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Provides API to the WifiStateMachine for doing read/write access
+ * to soft access point configuration
+ */
+class WifiApConfigStore extends StateMachine {
+
+ private Context mContext;
+ private static final String TAG = "WifiApConfigStore";
+
+ private static final String AP_CONFIG_FILE = Environment.getDataDirectory() +
+ "/misc/wifi/softap.conf";
+
+ private static final int AP_CONFIG_FILE_VERSION = 1;
+
+ private State mDefaultState = new DefaultState();
+ private State mInactiveState = new InactiveState();
+ private State mActiveState = new ActiveState();
+
+ private WifiConfiguration mWifiApConfig = null;
+ private AsyncChannel mReplyChannel = new AsyncChannel();
+
+ WifiApConfigStore(Context context, Handler target) {
+ super(TAG, target.getLooper());
+
+ mContext = context;
+ addState(mDefaultState);
+ addState(mInactiveState, mDefaultState);
+ addState(mActiveState, mDefaultState);
+
+ setInitialState(mInactiveState);
+ }
+
+ public static WifiApConfigStore makeWifiApConfigStore(Context context, Handler target) {
+ WifiApConfigStore s = new WifiApConfigStore(context, target);
+ s.start();
+ return s;
+ }
+
+ class DefaultState extends State {
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiStateMachine.CMD_SET_AP_CONFIG:
+ case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
+ Log.e(TAG, "Unexpected message: " + message);
+ break;
+ case WifiStateMachine.CMD_REQUEST_AP_CONFIG:
+ mReplyChannel.replyToMessage(message,
+ WifiStateMachine.CMD_RESPONSE_AP_CONFIG, mWifiApConfig);
+ break;
+ default:
+ Log.e(TAG, "Failed to handle " + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ class InactiveState extends State {
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiStateMachine.CMD_SET_AP_CONFIG:
+ mWifiApConfig = (WifiConfiguration) message.obj;
+ transitionTo(mActiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ActiveState extends State {
+ public void enter() {
+ new Thread(new Runnable() {
+ public void run() {
+ writeApConfiguration(mWifiApConfig);
+ sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED);
+ }
+ }).start();
+ }
+
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ //TODO: have feedback to the user when we do this
+ //to indicate the write is currently in progress
+ case WifiStateMachine.CMD_SET_AP_CONFIG:
+ deferMessage(message);
+ break;
+ case WifiStateMachine.CMD_SET_AP_CONFIG_COMPLETED:
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ void loadApConfiguration() {
+ DataInputStream in = null;
+ try {
+ WifiConfiguration config = new WifiConfiguration();
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+ AP_CONFIG_FILE)));
+
+ int version = in.readInt();
+ if (version != 1) {
+ Log.e(TAG, "Bad version on hotspot configuration file, set defaults");
+ setDefaultApConfiguration();
+ return;
+ }
+ config.SSID = in.readUTF();
+ int authType = in.readInt();
+ config.allowedKeyManagement.set(authType);
+ if (authType != KeyMgmt.NONE) {
+ config.preSharedKey = in.readUTF();
+ }
+ mWifiApConfig = config;
+ } catch (IOException ignore) {
+ setDefaultApConfiguration();
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {}
+ }
+ }
+ }
+
+ Messenger getMessenger() {
+ return new Messenger(getHandler());
+ }
+
+ private void writeApConfiguration(final WifiConfiguration config) {
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(AP_CONFIG_FILE)));
+
+ out.writeInt(AP_CONFIG_FILE_VERSION);
+ out.writeUTF(config.SSID);
+ int authType = config.getAuthType();
+ out.writeInt(authType);
+ if(authType != KeyMgmt.NONE) {
+ out.writeUTF(config.preSharedKey);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error writing hotspot configuration" + e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {}
+ }
+ }
+ }
+
+ /* Generate a default WPA2 based configuration with a random password.
+ We are changing the Wifi Ap configuration storage from secure settings to a
+ flat file accessible only by the system. A WPA2 based default configuration
+ will keep the device secure after the update */
+ private void setDefaultApConfiguration() {
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
+ config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
+ String randomUUID = UUID.randomUUID().toString();
+ //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9,13);
+ sendMessage(WifiStateMachine.CMD_SET_AP_CONFIG, config);
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiConfigStore.java b/services/java/com/android/server/wifi/WifiConfigStore.java
new file mode 100644
index 0000000..6ce4732
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiConfigStore.java
@@ -0,0 +1,2001 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkUtils;
+import android.net.NetworkInfo.DetailedState;
+import android.net.ProxyProperties;
+import android.net.RouteInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.IpAssignment;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiConfiguration.Status;
+import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
+
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.WpsResult;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.security.Credentials;
+import android.security.KeyChain;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class provides the API to manage configured
+ * wifi networks. The API is not thread safe is being
+ * used only from WifiStateMachine.
+ *
+ * It deals with the following
+ * - Add/update/remove a WifiConfiguration
+ * The configuration contains two types of information.
+ * = IP and proxy configuration that is handled by WifiConfigStore and
+ * is saved to disk on any change.
+ *
+ * The format of configuration file is as follows:
+ * <version>
+ * <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
+ * <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
+ * ..
+ *
+ * (key, value) pairs for a given network are grouped together and can
+ * be in any order. A EOS at the end of a set of (key, value) pairs
+ * indicates that the next set of (key, value) pairs are for a new
+ * network. A network is identified by a unique ID_KEY. If there is no
+ * ID_KEY in the (key, value) pairs, the data is discarded.
+ *
+ * An invalid version on read would result in discarding the contents of
+ * the file. On the next write, the latest version is written to file.
+ *
+ * Any failures during read or write to the configuration file are ignored
+ * without reporting to the user since the likelihood of these errors are
+ * low and the impact on connectivity is low.
+ *
+ * = SSID & security details that is pushed to the supplicant.
+ * supplicant saves these details to the disk on calling
+ * saveConfigCommand().
+ *
+ * We have two kinds of APIs exposed:
+ * > public API calls that provide fine grained control
+ * - enableNetwork, disableNetwork, addOrUpdateNetwork(),
+ * removeNetwork(). For these calls, the config is not persisted
+ * to the disk. (TODO: deprecate these calls in WifiManager)
+ * > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
+ * These calls persist the supplicant config to disk.
+ *
+ * - Maintain a list of configured networks for quick access
+ *
+ */
+public class WifiConfigStore {
+
+ private Context mContext;
+ private static final String TAG = "WifiConfigStore";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private static final String SUPPLICANT_CONFIG_FILE = "/data/misc/wifi/wpa_supplicant.conf";
+
+ /* configured networks with network id as the key */
+ private HashMap<Integer, WifiConfiguration> mConfiguredNetworks =
+ new HashMap<Integer, WifiConfiguration>();
+
+ /* A network id is a unique identifier for a network configured in the
+ * supplicant. Network ids are generated when the supplicant reads
+ * the configuration file at start and can thus change for networks.
+ * We store the IP configuration for networks along with a unique id
+ * that is generated from SSID and security type of the network. A mapping
+ * from the generated unique id to network id of the network is needed to
+ * map supplicant config to IP configuration. */
+ private HashMap<Integer, Integer> mNetworkIds =
+ new HashMap<Integer, Integer>();
+
+ /* Tracks the highest priority of configured networks */
+ private int mLastPriority = -1;
+
+ private static final String ipConfigFile = Environment.getDataDirectory() +
+ "/misc/wifi/ipconfig.txt";
+
+ private static final int IPCONFIG_FILE_VERSION = 2;
+
+ /* IP and proxy configuration keys */
+ private static final String ID_KEY = "id";
+ private static final String IP_ASSIGNMENT_KEY = "ipAssignment";
+ private static final String LINK_ADDRESS_KEY = "linkAddress";
+ private static final String GATEWAY_KEY = "gateway";
+ private static final String DNS_KEY = "dns";
+ private static final String PROXY_SETTINGS_KEY = "proxySettings";
+ private static final String PROXY_HOST_KEY = "proxyHost";
+ private static final String PROXY_PORT_KEY = "proxyPort";
+ private static final String PROXY_PAC_FILE = "proxyPac";
+ private static final String EXCLUSION_LIST_KEY = "exclusionList";
+ private static final String EOS = "eos";
+
+
+ /* Enterprise configuration keys */
+ /**
+ * In old configurations, the "private_key" field was used. However, newer
+ * configurations use the key_id field with the engine_id set to "keystore".
+ * If this field is found in the configuration, the migration code is
+ * triggered.
+ */
+ public static final String OLD_PRIVATE_KEY_NAME = "private_key";
+
+ /** This represents an empty value of an enterprise field.
+ * NULL is used at wpa_supplicant to indicate an empty value
+ */
+ static final String EMPTY_VALUE = "NULL";
+
+ /** Internal use only */
+ private static final String[] ENTERPRISE_CONFIG_SUPPLICANT_KEYS = new String[] {
+ WifiEnterpriseConfig.EAP_KEY, WifiEnterpriseConfig.PHASE2_KEY,
+ WifiEnterpriseConfig.IDENTITY_KEY, WifiEnterpriseConfig.ANON_IDENTITY_KEY,
+ WifiEnterpriseConfig.PASSWORD_KEY, WifiEnterpriseConfig.CLIENT_CERT_KEY,
+ WifiEnterpriseConfig.CA_CERT_KEY, WifiEnterpriseConfig.SUBJECT_MATCH_KEY,
+ WifiEnterpriseConfig.ENGINE_KEY, WifiEnterpriseConfig.ENGINE_ID_KEY,
+ WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY };
+
+ private final LocalLog mLocalLog;
+ private final WpaConfigFileObserver mFileObserver;
+
+ private WifiNative mWifiNative;
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ WifiConfigStore(Context c, WifiNative wn) {
+ mContext = c;
+ mWifiNative = wn;
+
+ if (VDBG) {
+ mLocalLog = mWifiNative.getLocalLog();
+ mFileObserver = new WpaConfigFileObserver();
+ mFileObserver.startWatching();
+ } else {
+ mLocalLog = null;
+ mFileObserver = null;
+ }
+ }
+
+ class WpaConfigFileObserver extends FileObserver {
+
+ public WpaConfigFileObserver() {
+ super(SUPPLICANT_CONFIG_FILE, CLOSE_WRITE);
+ }
+
+ @Override
+ public void onEvent(int event, String path) {
+ if (event == CLOSE_WRITE) {
+ File file = new File(SUPPLICANT_CONFIG_FILE);
+ if (VDBG) localLog("wpa_supplicant.conf changed; new size = " + file.length());
+ }
+ }
+ }
+
+
+ /**
+ * Fetch the list of configured networks
+ * and enable all stored networks in supplicant.
+ */
+ void loadAndEnableAllNetworks() {
+ if (DBG) log("Loading config and enabling all networks");
+ loadConfiguredNetworks();
+ enableAllNetworks();
+ }
+
+ /**
+ * Fetch the list of currently configured networks
+ * @return List of networks
+ */
+ List<WifiConfiguration> getConfiguredNetworks() {
+ List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ networks.add(new WifiConfiguration(config));
+ }
+ return networks;
+ }
+
+ /**
+ * enable all networks and save config. This will be a no-op if the list
+ * of configured networks indicates all networks as being enabled
+ */
+ void enableAllNetworks() {
+ boolean networkEnabledStateChanged = false;
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if(config != null && config.status == Status.DISABLED) {
+ if(mWifiNative.enableNetwork(config.networkId, false)) {
+ networkEnabledStateChanged = true;
+ config.status = Status.ENABLED;
+ } else {
+ loge("Enable network failed on " + config.networkId);
+ }
+ }
+ }
+
+ if (networkEnabledStateChanged) {
+ mWifiNative.saveConfig();
+ sendConfiguredNetworksChangedBroadcast();
+ }
+ }
+
+
+ /**
+ * Selects the specified network for connection. This involves
+ * updating the priority of all the networks and enabling the given
+ * network while disabling others.
+ *
+ * Selecting a network will leave the other networks disabled and
+ * a call to enableAllNetworks() needs to be issued upon a connection
+ * or a failure event from supplicant
+ *
+ * @param netId network to select for connection
+ * @return false if the network id is invalid
+ */
+ boolean selectNetwork(int netId) {
+ if (VDBG) localLog("selectNetwork", netId);
+ if (netId == INVALID_NETWORK_ID) return false;
+
+ // Reset the priority of each network at start or if it goes too high.
+ if (mLastPriority == -1 || mLastPriority > 1000000) {
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if (config.networkId != INVALID_NETWORK_ID) {
+ config.priority = 0;
+ addOrUpdateNetworkNative(config);
+ }
+ }
+ mLastPriority = 0;
+ }
+
+ // Set to the highest priority and save the configuration.
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = netId;
+ config.priority = ++mLastPriority;
+
+ addOrUpdateNetworkNative(config);
+ mWifiNative.saveConfig();
+
+ /* Enable the given network while disabling all other networks */
+ enableNetworkWithoutBroadcast(netId, true);
+
+ /* Avoid saving the config & sending a broadcast to prevent settings
+ * from displaying a disabled list of networks */
+ return true;
+ }
+
+ /**
+ * Add/update the specified configuration and save config
+ *
+ * @param config WifiConfiguration to be saved
+ * @return network update result
+ */
+ NetworkUpdateResult saveNetwork(WifiConfiguration config) {
+ if (VDBG) localLog("saveNetwork", config.networkId);
+ // A new network cannot have null SSID
+ if (config == null || (config.networkId == INVALID_NETWORK_ID &&
+ config.SSID == null)) {
+ return new NetworkUpdateResult(INVALID_NETWORK_ID);
+ }
+
+ boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
+ NetworkUpdateResult result = addOrUpdateNetworkNative(config);
+ int netId = result.getNetworkId();
+ /* enable a new network */
+ if (newNetwork && netId != INVALID_NETWORK_ID) {
+ mWifiNative.enableNetwork(netId, false);
+ mConfiguredNetworks.get(netId).status = Status.ENABLED;
+ }
+ mWifiNative.saveConfig();
+ sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?
+ WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ return result;
+ }
+
+ void updateStatus(int netId, DetailedState state) {
+ if (netId != INVALID_NETWORK_ID) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config == null) return;
+ switch (state) {
+ case CONNECTED:
+ config.status = Status.CURRENT;
+ break;
+ case DISCONNECTED:
+ //If network is already disabled, keep the status
+ if (config.status == Status.CURRENT) {
+ config.status = Status.ENABLED;
+ }
+ break;
+ default:
+ //do nothing, retain the existing state
+ break;
+ }
+ }
+ }
+
+ /**
+ * Forget the specified network and save config
+ *
+ * @param netId network to forget
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean forgetNetwork(int netId) {
+ if (VDBG) localLog("forgetNetwork", netId);
+ if (mWifiNative.removeNetwork(netId)) {
+ mWifiNative.saveConfig();
+ removeConfigAndSendBroadcastIfNeeded(netId);
+ return true;
+ } else {
+ loge("Failed to remove network " + netId);
+ return false;
+ }
+ }
+
+ /**
+ * Add/update a network. Note that there is no saveConfig operation.
+ * This function is retained for compatibility with the public
+ * API. The more powerful saveNetwork() is used by the
+ * state machine
+ *
+ * @param config wifi configuration to add/update
+ * @return network Id
+ */
+ int addOrUpdateNetwork(WifiConfiguration config) {
+ if (VDBG) localLog("addOrUpdateNetwork", config.networkId);
+ NetworkUpdateResult result = addOrUpdateNetworkNative(config);
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ sendConfiguredNetworksChangedBroadcast(mConfiguredNetworks.get(result.getNetworkId()),
+ result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED :
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ return result.getNetworkId();
+ }
+
+ /**
+ * Remove a network. Note that there is no saveConfig operation.
+ * This function is retained for compatibility with the public
+ * API. The more powerful forgetNetwork() is used by the
+ * state machine for network removal
+ *
+ * @param netId network to be removed
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean removeNetwork(int netId) {
+ if (VDBG) localLog("removeNetwork", netId);
+ boolean ret = mWifiNative.removeNetwork(netId);
+ if (ret) {
+ removeConfigAndSendBroadcastIfNeeded(netId);
+ }
+ return ret;
+ }
+
+ private void removeConfigAndSendBroadcastIfNeeded(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) {
+ // Remove any associated keys
+ if (config.enterpriseConfig != null) {
+ removeKeys(config.enterpriseConfig);
+ }
+ mConfiguredNetworks.remove(netId);
+ mNetworkIds.remove(configKey(config));
+
+ writeIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+ }
+ }
+
+ /**
+ * Enable a network. Note that there is no saveConfig operation.
+ * This function is retained for compatibility with the public
+ * API. The more powerful selectNetwork()/saveNetwork() is used by the
+ * state machine for connecting to a network
+ *
+ * @param netId network to be enabled
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean enableNetwork(int netId, boolean disableOthers) {
+ boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
+ if (disableOthers) {
+ if (VDBG) localLog("enableNetwork(disableOthers=true) ", netId);
+ sendConfiguredNetworksChangedBroadcast();
+ } else {
+ if (VDBG) localLog("enableNetwork(disableOthers=false) ", netId);
+ WifiConfiguration enabledNetwork = null;
+ synchronized(mConfiguredNetworks) {
+ enabledNetwork = mConfiguredNetworks.get(netId);
+ }
+ // check just in case the network was removed by someone else.
+ if (enabledNetwork != null) {
+ sendConfiguredNetworksChangedBroadcast(enabledNetwork,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ }
+ return ret;
+ }
+
+ boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) {
+ boolean ret = mWifiNative.enableNetwork(netId, disableOthers);
+
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) config.status = Status.ENABLED;
+
+ if (disableOthers) {
+ markAllNetworksDisabledExcept(netId);
+ }
+ return ret;
+ }
+
+ void disableAllNetworks() {
+ if (VDBG) localLog("disableAllNetworks");
+ boolean networkDisabled = false;
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if(config != null && config.status != Status.DISABLED) {
+ if(mWifiNative.disableNetwork(config.networkId)) {
+ networkDisabled = true;
+ config.status = Status.DISABLED;
+ } else {
+ loge("Disable network failed on " + config.networkId);
+ }
+ }
+ }
+
+ if (networkDisabled) {
+ sendConfiguredNetworksChangedBroadcast();
+ }
+ }
+ /**
+ * Disable a network. Note that there is no saveConfig operation.
+ * @param netId network to be disabled
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean disableNetwork(int netId) {
+ return disableNetwork(netId, WifiConfiguration.DISABLED_UNKNOWN_REASON);
+ }
+
+ /**
+ * Disable a network. Note that there is no saveConfig operation.
+ * @param netId network to be disabled
+ * @param reason reason code network was disabled
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean disableNetwork(int netId, int reason) {
+ if (VDBG) localLog("disableNetwork", netId);
+ boolean ret = mWifiNative.disableNetwork(netId);
+ WifiConfiguration network = null;
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ /* Only change the reason if the network was not previously disabled */
+ if (config != null && config.status != Status.DISABLED) {
+ config.status = Status.DISABLED;
+ config.disableReason = reason;
+ network = config;
+ }
+ if (network != null) {
+ sendConfiguredNetworksChangedBroadcast(network,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ return ret;
+ }
+
+ /**
+ * Save the configured networks in supplicant to disk
+ * @return {@code true} if it succeeds, {@code false} otherwise
+ */
+ boolean saveConfig() {
+ return mWifiNative.saveConfig();
+ }
+
+ /**
+ * Start WPS pin method configuration with pin obtained
+ * from the access point
+ * @param config WPS configuration
+ * @return Wps result containing status and pin
+ */
+ WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
+ WpsResult result = new WpsResult();
+ if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
+ /* WPS leaves all networks disabled */
+ markAllNetworksDisabled();
+ result.status = WpsResult.Status.SUCCESS;
+ } else {
+ loge("Failed to start WPS pin method configuration");
+ result.status = WpsResult.Status.FAILURE;
+ }
+ return result;
+ }
+
+ /**
+ * Start WPS pin method configuration with pin obtained
+ * from the device
+ * @return WpsResult indicating status and pin
+ */
+ WpsResult startWpsWithPinFromDevice(WpsInfo config) {
+ WpsResult result = new WpsResult();
+ result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
+ /* WPS leaves all networks disabled */
+ if (!TextUtils.isEmpty(result.pin)) {
+ markAllNetworksDisabled();
+ result.status = WpsResult.Status.SUCCESS;
+ } else {
+ loge("Failed to start WPS pin method configuration");
+ result.status = WpsResult.Status.FAILURE;
+ }
+ return result;
+ }
+
+ /**
+ * Start WPS push button configuration
+ * @param config WPS configuration
+ * @return WpsResult indicating status and pin
+ */
+ WpsResult startWpsPbc(WpsInfo config) {
+ WpsResult result = new WpsResult();
+ if (mWifiNative.startWpsPbc(config.BSSID)) {
+ /* WPS leaves all networks disabled */
+ markAllNetworksDisabled();
+ result.status = WpsResult.Status.SUCCESS;
+ } else {
+ loge("Failed to start WPS push button configuration");
+ result.status = WpsResult.Status.FAILURE;
+ }
+ return result;
+ }
+
+ /**
+ * Fetch the link properties for a given network id
+ * @return LinkProperties for the given network id
+ */
+ LinkProperties getLinkProperties(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) return new LinkProperties(config.linkProperties);
+ return null;
+ }
+
+ /**
+ * set IP configuration for a given network id
+ */
+ void setLinkProperties(int netId, LinkProperties linkProperties) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null) {
+ // add old proxy details - TODO - is this still needed?
+ if(config.linkProperties != null) {
+ linkProperties.setHttpProxy(config.linkProperties.getHttpProxy());
+ }
+ config.linkProperties = linkProperties;
+ }
+ }
+
+ /**
+ * clear IP configuration for a given network id
+ * @param network id
+ */
+ void clearLinkProperties(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null && config.linkProperties != null) {
+ // Clear everything except proxy
+ ProxyProperties proxy = config.linkProperties.getHttpProxy();
+ config.linkProperties.clear();
+ config.linkProperties.setHttpProxy(proxy);
+ }
+ }
+
+
+ /**
+ * Fetch the proxy properties for a given network id
+ * @param network id
+ * @return ProxyProperties for the network id
+ */
+ ProxyProperties getProxyProperties(int netId) {
+ LinkProperties linkProperties = getLinkProperties(netId);
+ if (linkProperties != null) {
+ return new ProxyProperties(linkProperties.getHttpProxy());
+ }
+ return null;
+ }
+
+ /**
+ * Return if the specified network is using static IP
+ * @param network id
+ * @return {@code true} if using static ip for netId
+ */
+ boolean isUsingStaticIp(int netId) {
+ WifiConfiguration config = mConfiguredNetworks.get(netId);
+ if (config != null && config.ipAssignment == IpAssignment.STATIC) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Should be called when a single network configuration is made.
+ * @param network The network configuration that changed.
+ * @param reason The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
+ * WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
+ */
+ private void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network,
+ int reason) {
+ Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
+ intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
+ intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /**
+ * Should be called when multiple network configuration changes are made.
+ */
+ private void sendConfiguredNetworksChangedBroadcast() {
+ Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ void loadConfiguredNetworks() {
+ String listStr = mWifiNative.listNetworks();
+ mLastPriority = 0;
+
+ mConfiguredNetworks.clear();
+ mNetworkIds.clear();
+
+ if (listStr == null)
+ return;
+
+ String[] lines = listStr.split("\n");
+ // Skip the first line, which is a header
+ for (int i = 1; i < lines.length; i++) {
+ String[] result = lines[i].split("\t");
+ // network-id | ssid | bssid | flags
+ WifiConfiguration config = new WifiConfiguration();
+ try {
+ config.networkId = Integer.parseInt(result[0]);
+ } catch(NumberFormatException e) {
+ loge("Failed to read network-id '" + result[0] + "'");
+ continue;
+ }
+ if (result.length > 3) {
+ if (result[3].indexOf("[CURRENT]") != -1)
+ config.status = WifiConfiguration.Status.CURRENT;
+ else if (result[3].indexOf("[DISABLED]") != -1)
+ config.status = WifiConfiguration.Status.DISABLED;
+ else
+ config.status = WifiConfiguration.Status.ENABLED;
+ } else {
+ config.status = WifiConfiguration.Status.ENABLED;
+ }
+ readNetworkVariables(config);
+ if (config.priority > mLastPriority) {
+ mLastPriority = config.priority;
+ }
+ config.ipAssignment = IpAssignment.DHCP;
+ config.proxySettings = ProxySettings.NONE;
+
+ if (mNetworkIds.containsKey(configKey(config))) {
+ // That SSID is already known, just ignore this duplicate entry
+ if (VDBG) localLog("discarded duplicate network", config.networkId);
+ } else {
+ mConfiguredNetworks.put(config.networkId, config);
+ mNetworkIds.put(configKey(config), config.networkId);
+ if (VDBG) localLog("loaded configured network", config.networkId);
+ }
+ }
+
+ readIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast();
+
+ if (VDBG) localLog("loadConfiguredNetworks loaded " + mNetworkIds.size() + " networks");
+
+ if (mNetworkIds.size() == 0) {
+ // no networks? Lets log if the wpa_supplicant.conf file contents
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
+ if (VDBG) localLog("--- Begin wpa_supplicant.conf Contents ---");
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ if (VDBG) localLog(line);
+ }
+ if (VDBG) localLog("--- End wpa_supplicant.conf Contents ---");
+ } catch (FileNotFoundException e) {
+ if (VDBG) localLog("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
+ } catch (IOException e) {
+ if (VDBG) localLog("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ // Just ignore the fact that we couldn't close
+ }
+ }
+ }
+ }
+
+ /* Mark all networks except specified netId as disabled */
+ private void markAllNetworksDisabledExcept(int netId) {
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ if(config != null && config.networkId != netId) {
+ if (config.status != Status.DISABLED) {
+ config.status = Status.DISABLED;
+ config.disableReason = WifiConfiguration.DISABLED_UNKNOWN_REASON;
+ }
+ }
+ }
+ }
+
+ private void markAllNetworksDisabled() {
+ markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
+ }
+
+ boolean needsUnlockedKeyStore() {
+
+ // Any network using certificates to authenticate access requires
+ // unlocked key store; unless the certificates can be stored with
+ // hardware encryption
+
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+
+ if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
+ && config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+
+ if (needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void writeIpAndProxyConfigurations() {
+
+ /* Make a copy */
+ List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
+ for(WifiConfiguration config : mConfiguredNetworks.values()) {
+ networks.add(new WifiConfiguration(config));
+ }
+
+ DelayedDiskWrite.write(networks);
+ }
+
+ private static class DelayedDiskWrite {
+
+ private static HandlerThread sDiskWriteHandlerThread;
+ private static Handler sDiskWriteHandler;
+ /* Tracks multiple writes on the same thread */
+ private static int sWriteSequence = 0;
+ private static final String TAG = "DelayedDiskWrite";
+
+ static void write (final List<WifiConfiguration> networks) {
+
+ /* Do a delayed write to disk on a seperate handler thread */
+ synchronized (DelayedDiskWrite.class) {
+ if (++sWriteSequence == 1) {
+ sDiskWriteHandlerThread = new HandlerThread("WifiConfigThread");
+ sDiskWriteHandlerThread.start();
+ sDiskWriteHandler = new Handler(sDiskWriteHandlerThread.getLooper());
+ }
+ }
+
+ sDiskWriteHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onWriteCalled(networks);
+ }
+ });
+ }
+
+ private static void onWriteCalled(List<WifiConfiguration> networks) {
+
+ DataOutputStream out = null;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(
+ new FileOutputStream(ipConfigFile)));
+
+ out.writeInt(IPCONFIG_FILE_VERSION);
+
+ for(WifiConfiguration config : networks) {
+ boolean writeToFile = false;
+
+ try {
+ LinkProperties linkProperties = config.linkProperties;
+ switch (config.ipAssignment) {
+ case STATIC:
+ out.writeUTF(IP_ASSIGNMENT_KEY);
+ out.writeUTF(config.ipAssignment.toString());
+ for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) {
+ out.writeUTF(LINK_ADDRESS_KEY);
+ out.writeUTF(linkAddr.getAddress().getHostAddress());
+ out.writeInt(linkAddr.getNetworkPrefixLength());
+ }
+ for (RouteInfo route : linkProperties.getRoutes()) {
+ out.writeUTF(GATEWAY_KEY);
+ LinkAddress dest = route.getDestination();
+ if (dest != null) {
+ out.writeInt(1);
+ out.writeUTF(dest.getAddress().getHostAddress());
+ out.writeInt(dest.getNetworkPrefixLength());
+ } else {
+ out.writeInt(0);
+ }
+ if (route.getGateway() != null) {
+ out.writeInt(1);
+ out.writeUTF(route.getGateway().getHostAddress());
+ } else {
+ out.writeInt(0);
+ }
+ }
+ for (InetAddress inetAddr : linkProperties.getDnses()) {
+ out.writeUTF(DNS_KEY);
+ out.writeUTF(inetAddr.getHostAddress());
+ }
+ writeToFile = true;
+ break;
+ case DHCP:
+ out.writeUTF(IP_ASSIGNMENT_KEY);
+ out.writeUTF(config.ipAssignment.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid ip assignment while writing");
+ break;
+ }
+
+ switch (config.proxySettings) {
+ case STATIC:
+ ProxyProperties proxyProperties = linkProperties.getHttpProxy();
+ String exclusionList = proxyProperties.getExclusionList();
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(PROXY_HOST_KEY);
+ out.writeUTF(proxyProperties.getHost());
+ out.writeUTF(PROXY_PORT_KEY);
+ out.writeInt(proxyProperties.getPort());
+ out.writeUTF(EXCLUSION_LIST_KEY);
+ out.writeUTF(exclusionList);
+ writeToFile = true;
+ break;
+ case PAC:
+ ProxyProperties proxyPacProperties = linkProperties.getHttpProxy();
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.proxySettings.toString());
+ out.writeUTF(PROXY_PAC_FILE);
+ out.writeUTF(proxyPacProperties.getPacFileUrl());
+ writeToFile = true;
+ break;
+ case NONE:
+ out.writeUTF(PROXY_SETTINGS_KEY);
+ out.writeUTF(config.proxySettings.toString());
+ writeToFile = true;
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignthisore invalid proxy settings while writing");
+ break;
+ }
+ if (writeToFile) {
+ out.writeUTF(ID_KEY);
+ out.writeInt(configKey(config));
+ }
+ } catch (NullPointerException e) {
+ loge("Failure in writing " + config.linkProperties + e);
+ }
+ out.writeUTF(EOS);
+ }
+
+ } catch (IOException e) {
+ loge("Error writing data file");
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (Exception e) {}
+ }
+
+ //Quit if no more writes sent
+ synchronized (DelayedDiskWrite.class) {
+ if (--sWriteSequence == 0) {
+ sDiskWriteHandler.getLooper().quit();
+ sDiskWriteHandler = null;
+ sDiskWriteHandlerThread = null;
+ }
+ }
+ }
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+ }
+
+ private void readIpAndProxyConfigurations() {
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(
+ ipConfigFile)));
+
+ int version = in.readInt();
+ if (version != 2 && version != 1) {
+ loge("Bad version on IP configuration file, ignore read");
+ return;
+ }
+
+ while (true) {
+ int id = -1;
+ // Default is DHCP with no proxy
+ IpAssignment ipAssignment = IpAssignment.DHCP;
+ ProxySettings proxySettings = ProxySettings.NONE;
+ LinkProperties linkProperties = new LinkProperties();
+ String proxyHost = null;
+ String pacFileUrl = null;
+ int proxyPort = -1;
+ String exclusionList = null;
+ String key;
+
+ do {
+ key = in.readUTF();
+ try {
+ if (key.equals(ID_KEY)) {
+ id = in.readInt();
+ } else if (key.equals(IP_ASSIGNMENT_KEY)) {
+ ipAssignment = IpAssignment.valueOf(in.readUTF());
+ } else if (key.equals(LINK_ADDRESS_KEY)) {
+ LinkAddress linkAddr = new LinkAddress(
+ NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt());
+ linkProperties.addLinkAddress(linkAddr);
+ } else if (key.equals(GATEWAY_KEY)) {
+ LinkAddress dest = null;
+ InetAddress gateway = null;
+ if (version == 1) {
+ // only supported default gateways - leave the dest/prefix empty
+ gateway = NetworkUtils.numericToInetAddress(in.readUTF());
+ } else {
+ if (in.readInt() == 1) {
+ dest = new LinkAddress(
+ NetworkUtils.numericToInetAddress(in.readUTF()),
+ in.readInt());
+ }
+ if (in.readInt() == 1) {
+ gateway = NetworkUtils.numericToInetAddress(in.readUTF());
+ }
+ }
+ linkProperties.addRoute(new RouteInfo(dest, gateway));
+ } else if (key.equals(DNS_KEY)) {
+ linkProperties.addDns(
+ NetworkUtils.numericToInetAddress(in.readUTF()));
+ } else if (key.equals(PROXY_SETTINGS_KEY)) {
+ proxySettings = ProxySettings.valueOf(in.readUTF());
+ } else if (key.equals(PROXY_HOST_KEY)) {
+ proxyHost = in.readUTF();
+ } else if (key.equals(PROXY_PORT_KEY)) {
+ proxyPort = in.readInt();
+ } else if (key.equals(PROXY_PAC_FILE)) {
+ pacFileUrl = in.readUTF();
+ } else if (key.equals(EXCLUSION_LIST_KEY)) {
+ exclusionList = in.readUTF();
+ } else if (key.equals(EOS)) {
+ break;
+ } else {
+ loge("Ignore unknown key " + key + "while reading");
+ }
+ } catch (IllegalArgumentException e) {
+ loge("Ignore invalid address while reading" + e);
+ }
+ } while (true);
+
+ if (id != -1) {
+ WifiConfiguration config = mConfiguredNetworks.get(
+ mNetworkIds.get(id));
+
+ if (config == null) {
+ loge("configuration found for missing network, ignored");
+ } else {
+ config.linkProperties = linkProperties;
+ switch (ipAssignment) {
+ case STATIC:
+ case DHCP:
+ config.ipAssignment = ipAssignment;
+ break;
+ case UNASSIGNED:
+ loge("BUG: Found UNASSIGNED IP on file, use DHCP");
+ config.ipAssignment = IpAssignment.DHCP;
+ break;
+ default:
+ loge("Ignore invalid ip assignment while reading");
+ break;
+ }
+
+ switch (proxySettings) {
+ case STATIC:
+ config.proxySettings = proxySettings;
+ ProxyProperties proxyProperties =
+ new ProxyProperties(proxyHost, proxyPort, exclusionList);
+ linkProperties.setHttpProxy(proxyProperties);
+ break;
+ case PAC:
+ config.proxySettings = proxySettings;
+ ProxyProperties proxyPacProperties =
+ new ProxyProperties(pacFileUrl);
+ linkProperties.setHttpProxy(proxyPacProperties);
+ break;
+ case NONE:
+ config.proxySettings = proxySettings;
+ break;
+ case UNASSIGNED:
+ loge("BUG: Found UNASSIGNED proxy on file, use NONE");
+ config.proxySettings = ProxySettings.NONE;
+ break;
+ default:
+ loge("Ignore invalid proxy settings while reading");
+ break;
+ }
+ }
+ } else {
+ if (DBG) log("Missing id while parsing configuration");
+ }
+ }
+ } catch (EOFException ignore) {
+ } catch (IOException e) {
+ loge("Error parsing configuration" + e);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (Exception e) {}
+ }
+ }
+ }
+
+ private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) {
+ /*
+ * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
+ * network configuration. Otherwise, the networkId should
+ * refer to an existing configuration.
+ */
+
+ if (VDBG) localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+
+ int netId = config.networkId;
+ boolean newNetwork = false;
+ // networkId of INVALID_NETWORK_ID means we want to create a new network
+ if (netId == INVALID_NETWORK_ID) {
+ Integer savedNetId = mNetworkIds.get(configKey(config));
+ if (savedNetId != null) {
+ netId = savedNetId;
+ } else {
+ newNetwork = true;
+ netId = mWifiNative.addNetwork();
+ if (netId < 0) {
+ loge("Failed to add a network!");
+ return new NetworkUpdateResult(INVALID_NETWORK_ID);
+ }
+ }
+ }
+
+ boolean updateFailed = true;
+
+ setVariables: {
+
+ if (config.SSID != null &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.ssidVarName,
+ config.SSID)) {
+ loge("failed to set SSID: "+config.SSID);
+ break setVariables;
+ }
+
+ if (config.BSSID != null &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.bssidVarName,
+ config.BSSID)) {
+ loge("failed to set BSSID: "+config.BSSID);
+ break setVariables;
+ }
+
+ String allowedKeyManagementString =
+ makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
+ if (config.allowedKeyManagement.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.KeyMgmt.varName,
+ allowedKeyManagementString)) {
+ loge("failed to set key_mgmt: "+
+ allowedKeyManagementString);
+ break setVariables;
+ }
+
+ String allowedProtocolsString =
+ makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
+ if (config.allowedProtocols.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.Protocol.varName,
+ allowedProtocolsString)) {
+ loge("failed to set proto: "+
+ allowedProtocolsString);
+ break setVariables;
+ }
+
+ String allowedAuthAlgorithmsString =
+ makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
+ if (config.allowedAuthAlgorithms.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.AuthAlgorithm.varName,
+ allowedAuthAlgorithmsString)) {
+ loge("failed to set auth_alg: "+
+ allowedAuthAlgorithmsString);
+ break setVariables;
+ }
+
+ String allowedPairwiseCiphersString =
+ makeString(config.allowedPairwiseCiphers,
+ WifiConfiguration.PairwiseCipher.strings);
+ if (config.allowedPairwiseCiphers.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.PairwiseCipher.varName,
+ allowedPairwiseCiphersString)) {
+ loge("failed to set pairwise: "+
+ allowedPairwiseCiphersString);
+ break setVariables;
+ }
+
+ String allowedGroupCiphersString =
+ makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
+ if (config.allowedGroupCiphers.cardinality() != 0 &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.GroupCipher.varName,
+ allowedGroupCiphersString)) {
+ loge("failed to set group: "+
+ allowedGroupCiphersString);
+ break setVariables;
+ }
+
+ // Prevent client screw-up by passing in a WifiConfiguration we gave it
+ // by preventing "*" as a key.
+ if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
+ !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.pskVarName,
+ config.preSharedKey)) {
+ loge("failed to set psk");
+ break setVariables;
+ }
+
+ boolean hasSetKey = false;
+ if (config.wepKeys != null) {
+ for (int i = 0; i < config.wepKeys.length; i++) {
+ // Prevent client screw-up by passing in a WifiConfiguration we gave it
+ // by preventing "*" as a key.
+ if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.wepKeyVarNames[i],
+ config.wepKeys[i])) {
+ loge("failed to set wep_key" + i + ": " + config.wepKeys[i]);
+ break setVariables;
+ }
+ hasSetKey = true;
+ }
+ }
+ }
+
+ if (hasSetKey) {
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.wepTxKeyIdxVarName,
+ Integer.toString(config.wepTxKeyIndex))) {
+ loge("failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
+ break setVariables;
+ }
+ }
+
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.priorityVarName,
+ Integer.toString(config.priority))) {
+ loge(config.SSID + ": failed to set priority: "
+ +config.priority);
+ break setVariables;
+ }
+
+ if (config.hiddenSSID && !mWifiNative.setNetworkVariable(
+ netId,
+ WifiConfiguration.hiddenSSIDVarName,
+ Integer.toString(config.hiddenSSID ? 1 : 0))) {
+ loge(config.SSID + ": failed to set hiddenSSID: "+
+ config.hiddenSSID);
+ break setVariables;
+ }
+
+ if (config.enterpriseConfig != null &&
+ config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+
+ WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
+
+ if (needsKeyStore(enterpriseConfig)) {
+ /**
+ * Keyguard settings may eventually be controlled by device policy.
+ * We check here if keystore is unlocked before installing
+ * credentials.
+ * TODO: Do we need a dialog here ?
+ */
+ if (mKeyStore.state() != KeyStore.State.UNLOCKED) {
+ loge(config.SSID + ": key store is locked");
+ break setVariables;
+ }
+
+ try {
+ /* config passed may include only fields being updated.
+ * In order to generate the key id, fetch uninitialized
+ * fields from the currently tracked configuration
+ */
+ WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
+ String keyId = config.getKeyIdForCredentials(currentConfig);
+
+ if (!installKeys(enterpriseConfig, keyId)) {
+ loge(config.SSID + ": failed to install keys");
+ break setVariables;
+ }
+ } catch (IllegalStateException e) {
+ loge(config.SSID + " invalid config for key installation");
+ break setVariables;
+ }
+ }
+
+ HashMap<String, String> enterpriseFields = enterpriseConfig.getFields();
+ for (String key : enterpriseFields.keySet()) {
+ String value = enterpriseFields.get(key);
+ if (!mWifiNative.setNetworkVariable(
+ netId,
+ key,
+ value)) {
+ removeKeys(enterpriseConfig);
+ loge(config.SSID + ": failed to set " + key +
+ ": " + value);
+ break setVariables;
+ }
+ }
+ }
+ updateFailed = false;
+ } //end of setVariables
+
+ if (updateFailed) {
+ if (newNetwork) {
+ mWifiNative.removeNetwork(netId);
+ loge("Failed to set a network variable, removed network: " + netId);
+ }
+ return new NetworkUpdateResult(INVALID_NETWORK_ID);
+ }
+
+ /* An update of the network variables requires reading them
+ * back from the supplicant to update mConfiguredNetworks.
+ * This is because some of the variables (SSID, wep keys &
+ * passphrases) reflect different values when read back than
+ * when written. For example, wep key is stored as * irrespective
+ * of the value sent to the supplicant
+ */
+ WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
+ if (currentConfig == null) {
+ currentConfig = new WifiConfiguration();
+ currentConfig.ipAssignment = IpAssignment.DHCP;
+ currentConfig.proxySettings = ProxySettings.NONE;
+ currentConfig.networkId = netId;
+ }
+
+ readNetworkVariables(currentConfig);
+
+ mConfiguredNetworks.put(netId, currentConfig);
+ mNetworkIds.put(configKey(currentConfig), netId);
+
+ NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config);
+ result.setIsNewNetwork(newNetwork);
+ result.setNetworkId(netId);
+ return result;
+ }
+
+ /* Compare current and new configuration and write to file on change */
+ private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange(
+ WifiConfiguration currentConfig,
+ WifiConfiguration newConfig) {
+ boolean ipChanged = false;
+ boolean proxyChanged = false;
+ LinkProperties linkProperties = null;
+
+ switch (newConfig.ipAssignment) {
+ case STATIC:
+ Collection<LinkAddress> currentLinkAddresses = currentConfig.linkProperties
+ .getLinkAddresses();
+ Collection<LinkAddress> newLinkAddresses = newConfig.linkProperties
+ .getLinkAddresses();
+ Collection<InetAddress> currentDnses = currentConfig.linkProperties.getDnses();
+ Collection<InetAddress> newDnses = newConfig.linkProperties.getDnses();
+ Collection<RouteInfo> currentRoutes = currentConfig.linkProperties.getRoutes();
+ Collection<RouteInfo> newRoutes = newConfig.linkProperties.getRoutes();
+
+ boolean linkAddressesDiffer =
+ (currentLinkAddresses.size() != newLinkAddresses.size()) ||
+ !currentLinkAddresses.containsAll(newLinkAddresses);
+ boolean dnsesDiffer = (currentDnses.size() != newDnses.size()) ||
+ !currentDnses.containsAll(newDnses);
+ boolean routesDiffer = (currentRoutes.size() != newRoutes.size()) ||
+ !currentRoutes.containsAll(newRoutes);
+
+ if ((currentConfig.ipAssignment != newConfig.ipAssignment) ||
+ linkAddressesDiffer ||
+ dnsesDiffer ||
+ routesDiffer) {
+ ipChanged = true;
+ }
+ break;
+ case DHCP:
+ if (currentConfig.ipAssignment != newConfig.ipAssignment) {
+ ipChanged = true;
+ }
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid ip assignment during write");
+ break;
+ }
+
+ switch (newConfig.proxySettings) {
+ case STATIC:
+ case PAC:
+ ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy();
+ ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy();
+
+ if (newHttpProxy != null) {
+ proxyChanged = !newHttpProxy.equals(currentHttpProxy);
+ } else {
+ proxyChanged = (currentHttpProxy != null);
+ }
+ break;
+ case NONE:
+ if (currentConfig.proxySettings != newConfig.proxySettings) {
+ proxyChanged = true;
+ }
+ break;
+ case UNASSIGNED:
+ /* Ignore */
+ break;
+ default:
+ loge("Ignore invalid proxy configuration during write");
+ break;
+ }
+
+ if (!ipChanged) {
+ linkProperties = copyIpSettingsFromConfig(currentConfig);
+ } else {
+ currentConfig.ipAssignment = newConfig.ipAssignment;
+ linkProperties = copyIpSettingsFromConfig(newConfig);
+ log("IP config changed SSID = " + currentConfig.SSID + " linkProperties: " +
+ linkProperties.toString());
+ }
+
+
+ if (!proxyChanged) {
+ linkProperties.setHttpProxy(currentConfig.linkProperties.getHttpProxy());
+ } else {
+ currentConfig.proxySettings = newConfig.proxySettings;
+ linkProperties.setHttpProxy(newConfig.linkProperties.getHttpProxy());
+ log("proxy changed SSID = " + currentConfig.SSID);
+ if (linkProperties.getHttpProxy() != null) {
+ log(" proxyProperties: " + linkProperties.getHttpProxy().toString());
+ }
+ }
+
+ if (ipChanged || proxyChanged) {
+ currentConfig.linkProperties = linkProperties;
+ writeIpAndProxyConfigurations();
+ sendConfiguredNetworksChangedBroadcast(currentConfig,
+ WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+ }
+ return new NetworkUpdateResult(ipChanged, proxyChanged);
+ }
+
+ private LinkProperties copyIpSettingsFromConfig(WifiConfiguration config) {
+ LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(config.linkProperties.getInterfaceName());
+ for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
+ linkProperties.addLinkAddress(linkAddr);
+ }
+ for (RouteInfo route : config.linkProperties.getRoutes()) {
+ linkProperties.addRoute(route);
+ }
+ for (InetAddress dns : config.linkProperties.getDnses()) {
+ linkProperties.addDns(dns);
+ }
+ return linkProperties;
+ }
+
+ /**
+ * Read the variables from the supplicant daemon that are needed to
+ * fill in the WifiConfiguration object.
+ *
+ * @param config the {@link WifiConfiguration} object to be filled in.
+ */
+ private void readNetworkVariables(WifiConfiguration config) {
+
+ int netId = config.networkId;
+ if (netId < 0)
+ return;
+
+ /*
+ * TODO: maybe should have a native method that takes an array of
+ * variable names and returns an array of values. But we'd still
+ * be doing a round trip to the supplicant daemon for each variable.
+ */
+ String value;
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
+ if (!TextUtils.isEmpty(value)) {
+ if (value.charAt(0) != '"') {
+ config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
+ //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
+ //supplicant string
+ } else {
+ config.SSID = value;
+ }
+ } else {
+ config.SSID = null;
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.BSSID = value;
+ } else {
+ config.BSSID = null;
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
+ config.priority = -1;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.priority = Integer.parseInt(value);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
+ config.hiddenSSID = false;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.hiddenSSID = Integer.parseInt(value) != 0;
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
+ config.wepTxKeyIndex = -1;
+ if (!TextUtils.isEmpty(value)) {
+ try {
+ config.wepTxKeyIndex = Integer.parseInt(value);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+
+ for (int i = 0; i < 4; i++) {
+ value = mWifiNative.getNetworkVariable(netId,
+ WifiConfiguration.wepKeyVarNames[i]);
+ if (!TextUtils.isEmpty(value)) {
+ config.wepKeys[i] = value;
+ } else {
+ config.wepKeys[i] = null;
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName);
+ if (!TextUtils.isEmpty(value)) {
+ config.preSharedKey = value;
+ } else {
+ config.preSharedKey = null;
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.Protocol.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.Protocol.strings);
+ if (0 <= index) {
+ config.allowedProtocols.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.KeyMgmt.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.KeyMgmt.strings);
+ if (0 <= index) {
+ config.allowedKeyManagement.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.AuthAlgorithm.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
+ if (0 <= index) {
+ config.allowedAuthAlgorithms.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.PairwiseCipher.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.PairwiseCipher.strings);
+ if (0 <= index) {
+ config.allowedPairwiseCiphers.set(index);
+ }
+ }
+ }
+
+ value = mWifiNative.getNetworkVariable(config.networkId,
+ WifiConfiguration.GroupCipher.varName);
+ if (!TextUtils.isEmpty(value)) {
+ String vals[] = value.split(" ");
+ for (String val : vals) {
+ int index =
+ lookupString(val, WifiConfiguration.GroupCipher.strings);
+ if (0 <= index) {
+ config.allowedGroupCiphers.set(index);
+ }
+ }
+ }
+
+ if (config.enterpriseConfig == null) {
+ config.enterpriseConfig = new WifiEnterpriseConfig();
+ }
+ HashMap<String, String> enterpriseFields = config.enterpriseConfig.getFields();
+ for (String key : ENTERPRISE_CONFIG_SUPPLICANT_KEYS) {
+ value = mWifiNative.getNetworkVariable(netId, key);
+ if (!TextUtils.isEmpty(value)) {
+ enterpriseFields.put(key, removeDoubleQuotes(value));
+ } else {
+ enterpriseFields.put(key, EMPTY_VALUE);
+ }
+ }
+
+ if (migrateOldEapTlsNative(config.enterpriseConfig, netId)) {
+ saveConfig();
+ }
+
+ migrateCerts(config.enterpriseConfig);
+ // initializeSoftwareKeystoreFlag(config.enterpriseConfig, mKeyStore);
+ }
+
+ private static String removeDoubleQuotes(String string) {
+ int length = string.length();
+ if ((length > 1) && (string.charAt(0) == '"')
+ && (string.charAt(length - 1) == '"')) {
+ return string.substring(1, length - 1);
+ }
+ return string;
+ }
+
+ private static String convertToQuotedString(String string) {
+ return "\"" + string + "\"";
+ }
+
+ private static String makeString(BitSet set, String[] strings) {
+ StringBuffer buf = new StringBuffer();
+ int nextSetBit = -1;
+
+ /* Make sure all set bits are in [0, strings.length) to avoid
+ * going out of bounds on strings. (Shouldn't happen, but...) */
+ set = set.get(0, strings.length);
+
+ while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+ buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
+ }
+
+ // remove trailing space
+ if (set.cardinality() > 0) {
+ buf.setLength(buf.length() - 1);
+ }
+
+ return buf.toString();
+ }
+
+ private int lookupString(String string, String[] strings) {
+ int size = strings.length;
+
+ string = string.replace('-', '_');
+
+ for (int i = 0; i < size; i++)
+ if (string.equals(strings[i]))
+ return i;
+
+ // if we ever get here, we should probably add the
+ // value to WifiConfiguration to reflect that it's
+ // supported by the WPA supplicant
+ loge("Failed to look-up a string: " + string);
+
+ return -1;
+ }
+
+ /* Returns a unique for a given configuration */
+ private static int configKey(WifiConfiguration config) {
+ String key;
+
+ if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
+ key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
+ } else if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
+ config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
+ key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
+ } else if (config.wepKeys[0] != null) {
+ key = config.SSID + "WEP";
+ } else {
+ key = config.SSID + KeyMgmt.strings[KeyMgmt.NONE];
+ }
+
+ return key.hashCode();
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("WifiConfigStore");
+ pw.println("mLastPriority " + mLastPriority);
+ pw.println("Configured networks");
+ for (WifiConfiguration conf : getConfiguredNetworks()) {
+ pw.println(conf);
+ }
+ pw.println();
+
+ if (mLocalLog != null) {
+ pw.println("WifiConfigStore - Log Begin ----");
+ mLocalLog.dump(fd, pw, args);
+ pw.println("WifiConfigStore - Log End ----");
+ }
+ }
+
+ public String getConfigFile() {
+ return ipConfigFile;
+ }
+
+ private void loge(String s) {
+ Log.e(TAG, s);
+ }
+
+ private void log(String s) {
+ Log.d(TAG, s);
+ }
+
+ private void localLog(String s) {
+ if (mLocalLog != null) {
+ mLocalLog.log(s);
+ }
+ }
+
+ private void localLog(String s, int netId) {
+ if (mLocalLog == null) {
+ return;
+ }
+
+ WifiConfiguration config;
+ synchronized(mConfiguredNetworks) {
+ config = mConfiguredNetworks.get(netId);
+ }
+
+ if (config != null) {
+ mLocalLog.log(s + " " + config.getPrintableSsid());
+ } else {
+ mLocalLog.log(s + " " + netId);
+ }
+ }
+
+ // Certificate and private key management for EnterpriseConfig
+ static boolean needsKeyStore(WifiEnterpriseConfig config) {
+ // Has no keys to be installed
+ if (config.getClientCertificate() == null && config.getCaCertificate() == null)
+ return false;
+ return true;
+ }
+
+ static boolean isHardwareBackedKey(PrivateKey key) {
+ return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
+ }
+
+ static boolean hasHardwareBackedKey(Certificate certificate) {
+ return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
+ }
+
+ static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
+ String client = config.getClientCertificateAlias();
+ if (!TextUtils.isEmpty(client)) {
+ // a valid client certificate is configured
+
+ // BUGBUG: keyStore.get() never returns certBytes; because it is not
+ // taking WIFI_UID as a parameter. It always looks for certificate
+ // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
+ // all certificates need software keystore until we get the get() API
+ // fixed.
+
+ return true;
+ }
+
+ /*
+ try {
+
+ if (DBG) Slog.d(TAG, "Loading client certificate " + Credentials
+ .USER_CERTIFICATE + client);
+
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ if (factory == null) {
+ Slog.e(TAG, "Error getting certificate factory");
+ return;
+ }
+
+ byte[] certBytes = keyStore.get(Credentials.USER_CERTIFICATE + client);
+ if (certBytes != null) {
+ Certificate cert = (X509Certificate) factory.generateCertificate(
+ new ByteArrayInputStream(certBytes));
+
+ if (cert != null) {
+ mNeedsSoftwareKeystore = hasHardwareBackedKey(cert);
+
+ if (DBG) Slog.d(TAG, "Loaded client certificate " + Credentials
+ .USER_CERTIFICATE + client);
+ if (DBG) Slog.d(TAG, "It " + (mNeedsSoftwareKeystore ? "needs" :
+ "does not need" ) + " software key store");
+ } else {
+ Slog.d(TAG, "could not generate certificate");
+ }
+ } else {
+ Slog.e(TAG, "Could not load client certificate " + Credentials
+ .USER_CERTIFICATE + client);
+ mNeedsSoftwareKeystore = true;
+ }
+
+ } catch(CertificateException e) {
+ Slog.e(TAG, "Could not read certificates");
+ mCaCert = null;
+ mClientCertificate = null;
+ }
+ */
+
+ return false;
+ }
+
+ boolean installKeys(WifiEnterpriseConfig config, String name) {
+ boolean ret = true;
+ String privKeyName = Credentials.USER_PRIVATE_KEY + name;
+ String userCertName = Credentials.USER_CERTIFICATE + name;
+ String caCertName = Credentials.CA_CERTIFICATE + name;
+ if (config.getClientCertificate() != null) {
+ byte[] privKeyData = config.getClientPrivateKey().getEncoded();
+ if (isHardwareBackedKey(config.getClientPrivateKey())) {
+ // Hardware backed key store is secure enough to store keys un-encrypted, this
+ // removes the need for user to punch a PIN to get access to these keys
+ if (DBG) Log.d(TAG, "importing keys " + name + " in hardware backed store");
+ ret = mKeyStore.importKey(privKeyName, privKeyData, android.os.Process.WIFI_UID,
+ KeyStore.FLAG_NONE);
+ } else {
+ // Software backed key store is NOT secure enough to store keys un-encrypted.
+ // Save keys encrypted so they are protected with user's PIN. User will
+ // have to unlock phone before being able to use these keys and connect to
+ // networks.
+ if (DBG) Log.d(TAG, "importing keys " + name + " in software backed store");
+ ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
+ KeyStore.FLAG_ENCRYPTED);
+ }
+ if (ret == false) {
+ return ret;
+ }
+
+ ret = putCertInKeyStore(userCertName, config.getClientCertificate());
+ if (ret == false) {
+ // Remove private key installed
+ mKeyStore.delKey(privKeyName, Process.WIFI_UID);
+ return ret;
+ }
+ }
+
+ if (config.getCaCertificate() != null) {
+ ret = putCertInKeyStore(caCertName, config.getCaCertificate());
+ if (ret == false) {
+ if (config.getClientCertificate() != null) {
+ // Remove client key+cert
+ mKeyStore.delKey(privKeyName, Process.WIFI_UID);
+ mKeyStore.delete(userCertName, Process.WIFI_UID);
+ }
+ return ret;
+ }
+ }
+
+ // Set alias names
+ if (config.getClientCertificate() != null) {
+ config.setClientCertificateAlias(name);
+ config.resetClientKeyEntry();
+ }
+
+ if (config.getCaCertificate() != null) {
+ config.setCaCertificateAlias(name);
+ config.resetCaCertificate();
+ }
+
+ return ret;
+ }
+
+ private boolean putCertInKeyStore(String name, Certificate cert) {
+ try {
+ byte[] certData = Credentials.convertToPem(cert);
+ if (DBG) Log.d(TAG, "putting certificate " + name + " in keystore");
+ return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+
+ } catch (IOException e1) {
+ return false;
+ } catch (CertificateException e2) {
+ return false;
+ }
+ }
+
+ void removeKeys(WifiEnterpriseConfig config) {
+ String client = config.getClientCertificateAlias();
+ // a valid client certificate is configured
+ if (!TextUtils.isEmpty(client)) {
+ if (DBG) Log.d(TAG, "removing client private key and user cert");
+ mKeyStore.delKey(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
+ mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
+ }
+
+ String ca = config.getCaCertificateAlias();
+ // a valid ca certificate is configured
+ if (!TextUtils.isEmpty(ca)) {
+ if (DBG) Log.d(TAG, "removing CA cert");
+ mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
+ }
+ }
+
+
+ /** Migrates the old style TLS config to the new config style. This should only be used
+ * when restoring an old wpa_supplicant.conf or upgrading from a previous
+ * platform version.
+ * @return true if the config was updated
+ * @hide
+ */
+ boolean migrateOldEapTlsNative(WifiEnterpriseConfig config, int netId) {
+ String oldPrivateKey = mWifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
+ /*
+ * If the old configuration value is not present, then there is nothing
+ * to do.
+ */
+ if (TextUtils.isEmpty(oldPrivateKey)) {
+ return false;
+ } else {
+ // Also ignore it if it's empty quotes.
+ oldPrivateKey = removeDoubleQuotes(oldPrivateKey);
+ if (TextUtils.isEmpty(oldPrivateKey)) {
+ return false;
+ }
+ }
+
+ config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, WifiEnterpriseConfig.ENGINE_ENABLE);
+ config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY,
+ WifiEnterpriseConfig.ENGINE_ID_KEYSTORE);
+
+ /*
+ * The old key started with the keystore:// URI prefix, but we don't
+ * need that anymore. Trim it off if it exists.
+ */
+ final String keyName;
+ if (oldPrivateKey.startsWith(WifiEnterpriseConfig.KEYSTORE_URI)) {
+ keyName = new String(
+ oldPrivateKey.substring(WifiEnterpriseConfig.KEYSTORE_URI.length()));
+ } else {
+ keyName = oldPrivateKey;
+ }
+ config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, keyName);
+
+ mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.ENGINE_KEY,
+ config.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY, ""));
+
+ mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.ENGINE_ID_KEY,
+ config.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, ""));
+
+ mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY,
+ config.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, ""));
+
+ // Remove old private_key string so we don't run this again.
+ mWifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE);
+
+ return true;
+ }
+
+ /** Migrate certs from global pool to wifi UID if not already done */
+ void migrateCerts(WifiEnterpriseConfig config) {
+ String client = config.getClientCertificateAlias();
+ // a valid client certificate is configured
+ if (!TextUtils.isEmpty(client)) {
+ if (!mKeyStore.contains(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID)) {
+ mKeyStore.duplicate(Credentials.USER_PRIVATE_KEY + client, -1,
+ Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
+ mKeyStore.duplicate(Credentials.USER_CERTIFICATE + client, -1,
+ Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
+ }
+ }
+
+ String ca = config.getCaCertificateAlias();
+ // a valid ca certificate is configured
+ if (!TextUtils.isEmpty(ca)) {
+ if (!mKeyStore.contains(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID)) {
+ mKeyStore.duplicate(Credentials.CA_CERTIFICATE + ca, -1,
+ Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
+ }
+ }
+ }
+
+}
+
diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java
index a3d514e..8766826 100644
--- a/services/java/com/android/server/wifi/WifiController.java
+++ b/services/java/com/android/server/wifi/WifiController.java
@@ -30,7 +30,6 @@ import android.net.wifi.WifiManager;
import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
-import android.net.wifi.WifiStateMachine;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
diff --git a/services/java/com/android/server/wifi/WifiMonitor.java b/services/java/com/android/server/wifi/WifiMonitor.java
new file mode 100644
index 0000000..0761c11
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiMonitor.java
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.NetworkInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.server.wifi.p2p.WifiP2pService.P2pStatus;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.StateMachine;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Listens for events from the wpa_supplicant server, and passes them on
+ * to the {@link StateMachine} for handling. Runs in its own thread.
+ *
+ * @hide
+ */
+public class WifiMonitor {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "WifiMonitor";
+
+ /** Events we receive from the supplicant daemon */
+
+ private static final int CONNECTED = 1;
+ private static final int DISCONNECTED = 2;
+ private static final int STATE_CHANGE = 3;
+ private static final int SCAN_RESULTS = 4;
+ private static final int LINK_SPEED = 5;
+ private static final int TERMINATING = 6;
+ private static final int DRIVER_STATE = 7;
+ private static final int EAP_FAILURE = 8;
+ private static final int ASSOC_REJECT = 9;
+ private static final int UNKNOWN = 10;
+
+ /** All events coming from the supplicant start with this prefix */
+ private static final String EVENT_PREFIX_STR = "CTRL-EVENT-";
+ private static final int EVENT_PREFIX_LEN_STR = EVENT_PREFIX_STR.length();
+
+ /** All WPA events coming from the supplicant start with this prefix */
+ private static final String WPA_EVENT_PREFIX_STR = "WPA:";
+ private static final String PASSWORD_MAY_BE_INCORRECT_STR =
+ "pre-shared key may be incorrect";
+
+ /* WPS events */
+ private static final String WPS_SUCCESS_STR = "WPS-SUCCESS";
+
+ /* Format: WPS-FAIL msg=%d [config_error=%d] [reason=%d (%s)] */
+ private static final String WPS_FAIL_STR = "WPS-FAIL";
+ private static final String WPS_FAIL_PATTERN =
+ "WPS-FAIL msg=\\d+(?: config_error=(\\d+))?(?: reason=(\\d+))?";
+
+ /* config error code values for config_error=%d */
+ private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
+ private static final int CONFIG_AUTH_FAILURE = 18;
+
+ /* reason code values for reason=%d */
+ private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
+ private static final int REASON_WEP_PROHIBITED = 2;
+
+ private static final String WPS_OVERLAP_STR = "WPS-OVERLAP-DETECTED";
+ private static final String WPS_TIMEOUT_STR = "WPS-TIMEOUT";
+
+ /**
+ * Names of events from wpa_supplicant (minus the prefix). In the
+ * format descriptions, * &quot;<code>x</code>&quot;
+ * designates a dynamic value that needs to be parsed out from the event
+ * string
+ */
+ /**
+ * <pre>
+ * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed
+ * </pre>
+ * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point
+ */
+ private static final String CONNECTED_STR = "CONNECTED";
+ /**
+ * <pre>
+ * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys
+ * </pre>
+ */
+ private static final String DISCONNECTED_STR = "DISCONNECTED";
+ /**
+ * <pre>
+ * CTRL-EVENT-STATE-CHANGE x
+ * </pre>
+ * <code>x</code> is the numerical value of the new state.
+ */
+ private static final String STATE_CHANGE_STR = "STATE-CHANGE";
+ /**
+ * <pre>
+ * CTRL-EVENT-SCAN-RESULTS ready
+ * </pre>
+ */
+ private static final String SCAN_RESULTS_STR = "SCAN-RESULTS";
+
+ /**
+ * <pre>
+ * CTRL-EVENT-LINK-SPEED x Mb/s
+ * </pre>
+ * {@code x} is the link speed in Mb/sec.
+ */
+ private static final String LINK_SPEED_STR = "LINK-SPEED";
+ /**
+ * <pre>
+ * CTRL-EVENT-TERMINATING - signal x
+ * </pre>
+ * <code>x</code> is the signal that caused termination.
+ */
+ private static final String TERMINATING_STR = "TERMINATING";
+ /**
+ * <pre>
+ * CTRL-EVENT-DRIVER-STATE state
+ * </pre>
+ * <code>state</code> can be HANGED
+ */
+ private static final String DRIVER_STATE_STR = "DRIVER-STATE";
+ /**
+ * <pre>
+ * CTRL-EVENT-EAP-FAILURE EAP authentication failed
+ * </pre>
+ */
+ private static final String EAP_FAILURE_STR = "EAP-FAILURE";
+
+ /**
+ * This indicates an authentication failure on EAP FAILURE event
+ */
+ private static final String EAP_AUTH_FAILURE_STR = "EAP authentication failed";
+
+ /**
+ * This indicates an assoc reject event
+ */
+ private static final String ASSOC_REJECT_STR = "ASSOC-REJECT";
+
+ /**
+ * Regex pattern for extracting an Ethernet-style MAC address from a string.
+ * Matches a strings like the following:<pre>
+ * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre>
+ */
+ private static Pattern mConnectedEventPattern =
+ Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) ");
+
+ /** P2P events */
+ private static final String P2P_EVENT_PREFIX_STR = "P2P";
+
+ /* P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 pri_dev_type=1-0050F204-1
+ name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 group_capab=0x0 */
+ private static final String P2P_DEVICE_FOUND_STR = "P2P-DEVICE-FOUND";
+
+ /* P2P-DEVICE-LOST p2p_dev_addr=42:fc:89:e1:e2:27 */
+ private static final String P2P_DEVICE_LOST_STR = "P2P-DEVICE-LOST";
+
+ /* P2P-FIND-STOPPED */
+ private static final String P2P_FIND_STOPPED_STR = "P2P-FIND-STOPPED";
+
+ /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */
+ private static final String P2P_GO_NEG_REQUEST_STR = "P2P-GO-NEG-REQUEST";
+
+ private static final String P2P_GO_NEG_SUCCESS_STR = "P2P-GO-NEG-SUCCESS";
+
+ /* P2P-GO-NEG-FAILURE status=x */
+ private static final String P2P_GO_NEG_FAILURE_STR = "P2P-GO-NEG-FAILURE";
+
+ private static final String P2P_GROUP_FORMATION_SUCCESS_STR =
+ "P2P-GROUP-FORMATION-SUCCESS";
+
+ private static final String P2P_GROUP_FORMATION_FAILURE_STR =
+ "P2P-GROUP-FORMATION-FAILURE";
+
+ /* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
+ [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|passphrase="fKG4jMe3"]
+ go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] */
+ private static final String P2P_GROUP_STARTED_STR = "P2P-GROUP-STARTED";
+
+ /* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED */
+ private static final String P2P_GROUP_REMOVED_STR = "P2P-GROUP-REMOVED";
+
+ /* P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13
+ bssid=fa:7b:7a:42:82:13 unknown-network */
+ private static final String P2P_INVITATION_RECEIVED_STR = "P2P-INVITATION-RECEIVED";
+
+ /* P2P-INVITATION-RESULT status=1 */
+ private static final String P2P_INVITATION_RESULT_STR = "P2P-INVITATION-RESULT";
+
+ /* P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27
+ pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
+ group_capab=0x0 */
+ private static final String P2P_PROV_DISC_PBC_REQ_STR = "P2P-PROV-DISC-PBC-REQ";
+
+ /* P2P-PROV-DISC-PBC-RESP 02:12:47:f2:5a:36 */
+ private static final String P2P_PROV_DISC_PBC_RSP_STR = "P2P-PROV-DISC-PBC-RESP";
+
+ /* P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27
+ pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
+ group_capab=0x0 */
+ private static final String P2P_PROV_DISC_ENTER_PIN_STR = "P2P-PROV-DISC-ENTER-PIN";
+ /* P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27
+ pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
+ group_capab=0x0 */
+ private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN";
+ /* P2P-PROV-DISC-FAILURE p2p_dev_addr=42:fc:89:e1:e2:27 */
+ private static final String P2P_PROV_DISC_FAILURE_STR = "P2P-PROV-DISC-FAILURE";
+
+ /*
+ * Protocol format is as follows.<br>
+ * See the Table.62 in the WiFi Direct specification for the detail.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101
+ * length=3, service type=0(ALL Service), transaction id=1,
+ * status=1(service protocol type not available)<br>
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201
+ * length=3, service type=2(UPnP), transaction id=2,
+ * status=1(service protocol type not available)
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323
+ * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3
+ * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746
+ * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3
+ * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646
+ * 576696365
+ * length=153,type=2(UPnP),transaction id=3,status=0
+ *
+ * UPnP Protocol format is as follows.
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ *
+ * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678
+ * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d
+ * ede-8574-59ab-9332-123456789012::upnp:rootdevice
+ *
+ * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970
+ * 70c00c000c01094d795072696e746572c027
+ * length=25, type=1(Bonjour),transaction id=2,status=0
+ *
+ * Bonjour Protocol format is as follows.
+ * __________________________________________________________
+ * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)|
+ *
+ * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1,
+ * RDATA=MyPrinter._ipp._tcp.local.
+ *
+ */
+ private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP";
+
+ private static final String HOST_AP_EVENT_PREFIX_STR = "AP";
+ /* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */
+ private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
+ /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 */
+ private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED";
+
+ /* Supplicant events reported to a state machine */
+ private static final int BASE = Protocol.BASE_WIFI_MONITOR;
+
+ /* Connection to supplicant established */
+ public static final int SUP_CONNECTION_EVENT = BASE + 1;
+ /* Connection to supplicant lost */
+ public static final int SUP_DISCONNECTION_EVENT = BASE + 2;
+ /* Network connection completed */
+ public static final int NETWORK_CONNECTION_EVENT = BASE + 3;
+ /* Network disconnection completed */
+ public static final int NETWORK_DISCONNECTION_EVENT = BASE + 4;
+ /* Scan results are available */
+ public static final int SCAN_RESULTS_EVENT = BASE + 5;
+ /* Supplicate state changed */
+ public static final int SUPPLICANT_STATE_CHANGE_EVENT = BASE + 6;
+ /* Password failure and EAP authentication failure */
+ public static final int AUTHENTICATION_FAILURE_EVENT = BASE + 7;
+ /* WPS success detected */
+ public static final int WPS_SUCCESS_EVENT = BASE + 8;
+ /* WPS failure detected */
+ public static final int WPS_FAIL_EVENT = BASE + 9;
+ /* WPS overlap detected */
+ public static final int WPS_OVERLAP_EVENT = BASE + 10;
+ /* WPS timeout detected */
+ public static final int WPS_TIMEOUT_EVENT = BASE + 11;
+ /* Driver was hung */
+ public static final int DRIVER_HUNG_EVENT = BASE + 12;
+
+ /* P2P events */
+ public static final int P2P_DEVICE_FOUND_EVENT = BASE + 21;
+ public static final int P2P_DEVICE_LOST_EVENT = BASE + 22;
+ public static final int P2P_GO_NEGOTIATION_REQUEST_EVENT = BASE + 23;
+ public static final int P2P_GO_NEGOTIATION_SUCCESS_EVENT = BASE + 25;
+ public static final int P2P_GO_NEGOTIATION_FAILURE_EVENT = BASE + 26;
+ public static final int P2P_GROUP_FORMATION_SUCCESS_EVENT = BASE + 27;
+ public static final int P2P_GROUP_FORMATION_FAILURE_EVENT = BASE + 28;
+ public static final int P2P_GROUP_STARTED_EVENT = BASE + 29;
+ public static final int P2P_GROUP_REMOVED_EVENT = BASE + 30;
+ public static final int P2P_INVITATION_RECEIVED_EVENT = BASE + 31;
+ public static final int P2P_INVITATION_RESULT_EVENT = BASE + 32;
+ public static final int P2P_PROV_DISC_PBC_REQ_EVENT = BASE + 33;
+ public static final int P2P_PROV_DISC_PBC_RSP_EVENT = BASE + 34;
+ public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 35;
+ public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 36;
+ public static final int P2P_FIND_STOPPED_EVENT = BASE + 37;
+ public static final int P2P_SERV_DISC_RESP_EVENT = BASE + 38;
+ public static final int P2P_PROV_DISC_FAILURE_EVENT = BASE + 39;
+
+ /* hostap events */
+ public static final int AP_STA_DISCONNECTED_EVENT = BASE + 41;
+ public static final int AP_STA_CONNECTED_EVENT = BASE + 42;
+
+ /* Indicates assoc reject event */
+ public static final int ASSOCIATION_REJECTION_EVENT = BASE + 43;
+
+ /**
+ * This indicates a read error on the monitor socket conenction
+ */
+ private static final String WPA_RECV_ERROR_STR = "recv error";
+
+ /**
+ * Max errors before we close supplicant connection
+ */
+ private static final int MAX_RECV_ERRORS = 10;
+
+ private final String mInterfaceName;
+ private final WifiNative mWifiNative;
+ private final StateMachine mStateMachine;
+ private boolean mMonitoring;
+
+ // This is a global counter, since it's not monitor specific. However, the existing
+ // implementation forwards all "global" control events like CTRL-EVENT-TERMINATING
+ // to the p2p0 monitor. Is that expected ? It seems a bit surprising.
+ //
+ // TODO: If the p2p0 monitor isn't registered, the behaviour is even more surprising.
+ // The event will be dispatched to all monitors, and each of them will end up incrementing
+ // it in their dispatchXXX method. If we have 5 registered monitors (say), 2 consecutive
+ // recv errors will cause us to disconnect from the supplicant (instead of the intended 10).
+ //
+ // This variable is always accessed and modified under a WifiMonitorSingleton lock.
+ private static int sRecvErrors;
+
+ public WifiMonitor(StateMachine wifiStateMachine, WifiNative wifiNative) {
+ if (DBG) Log.d(TAG, "Creating WifiMonitor");
+ mWifiNative = wifiNative;
+ mInterfaceName = wifiNative.mInterfaceName;
+ mStateMachine = wifiStateMachine;
+ mMonitoring = false;
+
+ WifiMonitorSingleton.sInstance.registerInterfaceMonitor(mInterfaceName, this);
+ }
+
+ public void startMonitoring() {
+ WifiMonitorSingleton.sInstance.startMonitoring(mInterfaceName);
+ }
+
+ public void stopMonitoring() {
+ WifiMonitorSingleton.sInstance.stopMonitoring(mInterfaceName);
+ }
+
+ public void stopSupplicant() {
+ WifiMonitorSingleton.sInstance.stopSupplicant();
+ }
+
+ public void killSupplicant(boolean p2pSupported) {
+ WifiMonitorSingleton.sInstance.killSupplicant(p2pSupported);
+ }
+
+ private static class WifiMonitorSingleton {
+ private static final WifiMonitorSingleton sInstance = new WifiMonitorSingleton();
+
+ private final HashMap<String, WifiMonitor> mIfaceMap = new HashMap<String, WifiMonitor>();
+ private boolean mConnected = false;
+ private WifiNative mWifiNative;
+
+ private WifiMonitorSingleton() {
+ }
+
+ public synchronized void startMonitoring(String iface) {
+ WifiMonitor m = mIfaceMap.get(iface);
+ if (m == null) {
+ Log.e(TAG, "startMonitor called with unknown iface=" + iface);
+ return;
+ }
+
+ Log.d(TAG, "startMonitoring(" + iface + ") with mConnected = " + mConnected);
+
+ if (mConnected) {
+ m.mMonitoring = true;
+ m.mStateMachine.sendMessage(SUP_CONNECTION_EVENT);
+ } else {
+ if (DBG) Log.d(TAG, "connecting to supplicant");
+ int connectTries = 0;
+ while (true) {
+ if (mWifiNative.connectToSupplicant()) {
+ m.mMonitoring = true;
+ m.mStateMachine.sendMessage(SUP_CONNECTION_EVENT);
+ new MonitorThread(mWifiNative, this).start();
+ mConnected = true;
+ break;
+ }
+ if (connectTries++ < 5) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignore) {
+ }
+ } else {
+ mIfaceMap.remove(iface);
+ m.mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
+ Log.e(TAG, "startMonitoring(" + iface + ") failed!");
+ break;
+ }
+ }
+ }
+ }
+
+ public synchronized void stopMonitoring(String iface) {
+ WifiMonitor m = mIfaceMap.get(iface);
+ if (DBG) Log.d(TAG, "stopMonitoring(" + iface + ") = " + m.mStateMachine);
+ m.mMonitoring = false;
+ m.mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
+ }
+
+ public synchronized void registerInterfaceMonitor(String iface, WifiMonitor m) {
+ if (DBG) Log.d(TAG, "registerInterface(" + iface + "+" + m.mStateMachine + ")");
+ mIfaceMap.put(iface, m);
+ if (mWifiNative == null) {
+ mWifiNative = m.mWifiNative;
+ }
+ }
+
+ public synchronized void unregisterInterfaceMonitor(String iface) {
+ // REVIEW: When should we call this? If this isn't called, then WifiMonitor
+ // objects will remain in the mIfaceMap; and won't ever get deleted
+
+ WifiMonitor m = mIfaceMap.remove(iface);
+ if (DBG) Log.d(TAG, "unregisterInterface(" + iface + "+" + m.mStateMachine + ")");
+ }
+
+ public synchronized void stopSupplicant() {
+ mWifiNative.stopSupplicant();
+ }
+
+ public synchronized void killSupplicant(boolean p2pSupported) {
+ WifiNative.killSupplicant(p2pSupported);
+ mConnected = false;
+ for (WifiMonitor m : mIfaceMap.values()) {
+ m.mMonitoring = false;
+ }
+ }
+
+ private synchronized boolean dispatchEvent(String eventStr) {
+ String iface;
+ if (eventStr.startsWith("IFNAME=")) {
+ int space = eventStr.indexOf(' ');
+ if (space != -1) {
+ iface = eventStr.substring(7, space);
+ if (!mIfaceMap.containsKey(iface) && iface.startsWith("p2p-")) {
+ // p2p interfaces are created dynamically, but we have
+ // only one P2p state machine monitoring all of them; look
+ // for it explicitly, and send messages there ..
+ iface = "p2p0";
+ }
+ eventStr = eventStr.substring(space + 1);
+ } else {
+ // No point dispatching this event to any interface, the dispatched
+ // event string will begin with "IFNAME=" which dispatchEvent can't really
+ // do anything about.
+ Log.e(TAG, "Dropping malformed event (unparsable iface): " + eventStr);
+ return false;
+ }
+ } else {
+ // events without prefix belong to p2p0 monitor
+ iface = "p2p0";
+ }
+
+ if (DBG) Log.d(TAG, "Dispatching event to interface: " + iface);
+
+ WifiMonitor m = mIfaceMap.get(iface);
+ if (m != null) {
+ if (m.mMonitoring) {
+ if (m.dispatchEvent(eventStr)) {
+ mConnected = false;
+ return true;
+ }
+
+ return false;
+ } else {
+ if (DBG) Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+ return false;
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Sending to all monitors because there's no matching iface");
+ boolean done = false;
+ for (WifiMonitor monitor : mIfaceMap.values()) {
+ if (monitor.mMonitoring && monitor.dispatchEvent(eventStr)) {
+ done = true;
+ }
+ }
+
+ if (done) {
+ mConnected = false;
+ }
+
+ return done;
+ }
+ }
+ }
+
+ private static class MonitorThread extends Thread {
+ private final WifiNative mWifiNative;
+ private final WifiMonitorSingleton mWifiMonitorSingleton;
+
+ public MonitorThread(WifiNative wifiNative, WifiMonitorSingleton wifiMonitorSingleton) {
+ super("WifiMonitor");
+ mWifiNative = wifiNative;
+ mWifiMonitorSingleton = wifiMonitorSingleton;
+ }
+
+ public void run() {
+ //noinspection InfiniteLoopStatement
+ for (;;) {
+ String eventStr = mWifiNative.waitForEvent();
+
+ // Skip logging the common but mostly uninteresting scan-results event
+ if (DBG && eventStr.indexOf(SCAN_RESULTS_STR) == -1) {
+ Log.d(TAG, "Event [" + eventStr + "]");
+ }
+
+ if (mWifiMonitorSingleton.dispatchEvent(eventStr)) {
+ if (DBG) Log.d(TAG, "Disconnecting from the supplicant, no more events");
+ break;
+ }
+ }
+ }
+ }
+
+ /* @return true if the event was supplicant disconnection */
+ private boolean dispatchEvent(String eventStr) {
+
+ if (!eventStr.startsWith(EVENT_PREFIX_STR)) {
+ if (eventStr.startsWith(WPA_EVENT_PREFIX_STR) &&
+ 0 < eventStr.indexOf(PASSWORD_MAY_BE_INCORRECT_STR)) {
+ mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT);
+ } else if (eventStr.startsWith(WPS_SUCCESS_STR)) {
+ mStateMachine.sendMessage(WPS_SUCCESS_EVENT);
+ } else if (eventStr.startsWith(WPS_FAIL_STR)) {
+ handleWpsFailEvent(eventStr);
+ } else if (eventStr.startsWith(WPS_OVERLAP_STR)) {
+ mStateMachine.sendMessage(WPS_OVERLAP_EVENT);
+ } else if (eventStr.startsWith(WPS_TIMEOUT_STR)) {
+ mStateMachine.sendMessage(WPS_TIMEOUT_EVENT);
+ } else if (eventStr.startsWith(P2P_EVENT_PREFIX_STR)) {
+ handleP2pEvents(eventStr);
+ } else if (eventStr.startsWith(HOST_AP_EVENT_PREFIX_STR)) {
+ handleHostApEvents(eventStr);
+ }
+ else {
+ if (DBG) Log.w(TAG, "couldn't identify event type - " + eventStr);
+ }
+ return false;
+ }
+
+ String eventName = eventStr.substring(EVENT_PREFIX_LEN_STR);
+ int nameEnd = eventName.indexOf(' ');
+ if (nameEnd != -1)
+ eventName = eventName.substring(0, nameEnd);
+ if (eventName.length() == 0) {
+ if (DBG) Log.i(TAG, "Received wpa_supplicant event with empty event name");
+ return false;
+ }
+ /*
+ * Map event name into event enum
+ */
+ int event;
+ if (eventName.equals(CONNECTED_STR))
+ event = CONNECTED;
+ else if (eventName.equals(DISCONNECTED_STR))
+ event = DISCONNECTED;
+ else if (eventName.equals(STATE_CHANGE_STR))
+ event = STATE_CHANGE;
+ else if (eventName.equals(SCAN_RESULTS_STR))
+ event = SCAN_RESULTS;
+ else if (eventName.equals(LINK_SPEED_STR))
+ event = LINK_SPEED;
+ else if (eventName.equals(TERMINATING_STR))
+ event = TERMINATING;
+ else if (eventName.equals(DRIVER_STATE_STR))
+ event = DRIVER_STATE;
+ else if (eventName.equals(EAP_FAILURE_STR))
+ event = EAP_FAILURE;
+ else if (eventName.equals(ASSOC_REJECT_STR))
+ event = ASSOC_REJECT;
+ else
+ event = UNKNOWN;
+
+ String eventData = eventStr;
+ if (event == DRIVER_STATE || event == LINK_SPEED)
+ eventData = eventData.split(" ")[1];
+ else if (event == STATE_CHANGE || event == EAP_FAILURE) {
+ int ind = eventStr.indexOf(" ");
+ if (ind != -1) {
+ eventData = eventStr.substring(ind + 1);
+ }
+ } else {
+ int ind = eventStr.indexOf(" - ");
+ if (ind != -1) {
+ eventData = eventStr.substring(ind + 3);
+ }
+ }
+
+ if (event == STATE_CHANGE) {
+ handleSupplicantStateChange(eventData);
+ } else if (event == DRIVER_STATE) {
+ handleDriverEvent(eventData);
+ } else if (event == TERMINATING) {
+ /**
+ * Close the supplicant connection if we see
+ * too many recv errors
+ */
+ if (eventData.startsWith(WPA_RECV_ERROR_STR)) {
+ if (++sRecvErrors > MAX_RECV_ERRORS) {
+ if (DBG) {
+ Log.d(TAG, "too many recv errors, closing connection");
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // notify and exit
+ mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT);
+ return true;
+ } else if (event == EAP_FAILURE) {
+ if (eventData.startsWith(EAP_AUTH_FAILURE_STR)) {
+ mStateMachine.sendMessage(AUTHENTICATION_FAILURE_EVENT);
+ }
+ } else if (event == ASSOC_REJECT) {
+ mStateMachine.sendMessage(ASSOCIATION_REJECTION_EVENT);
+ } else {
+ handleEvent(event, eventData);
+ }
+ sRecvErrors = 0;
+ return false;
+ }
+
+ private void handleDriverEvent(String state) {
+ if (state == null) {
+ return;
+ }
+ if (state.equals("HANGED")) {
+ mStateMachine.sendMessage(DRIVER_HUNG_EVENT);
+ }
+ }
+
+ /**
+ * Handle all supplicant events except STATE-CHANGE
+ * @param event the event type
+ * @param remainder the rest of the string following the
+ * event name and &quot;&#8195;&#8212;&#8195;&quot;
+ */
+ void handleEvent(int event, String remainder) {
+ switch (event) {
+ case DISCONNECTED:
+ handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);
+ break;
+
+ case CONNECTED:
+ handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);
+ break;
+
+ case SCAN_RESULTS:
+ mStateMachine.sendMessage(SCAN_RESULTS_EVENT);
+ break;
+
+ case UNKNOWN:
+ break;
+ }
+ }
+
+ private void handleWpsFailEvent(String dataString) {
+ final Pattern p = Pattern.compile(WPS_FAIL_PATTERN);
+ Matcher match = p.matcher(dataString);
+ if (match.find()) {
+ String cfgErr = match.group(1);
+ String reason = match.group(2);
+
+ if (reason != null) {
+ switch(Integer.parseInt(reason)) {
+ case REASON_TKIP_ONLY_PROHIBITED:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_TKIP_ONLY_PROHIBITED, 0));
+ return;
+ case REASON_WEP_PROHIBITED:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_WEP_PROHIBITED, 0));
+ return;
+ }
+ }
+ if (cfgErr != null) {
+ switch(Integer.parseInt(cfgErr)) {
+ case CONFIG_AUTH_FAILURE:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_AUTH_FAILURE, 0));
+ return;
+ case CONFIG_MULTIPLE_PBC_DETECTED:
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.WPS_OVERLAP_ERROR, 0));
+ return;
+ }
+ }
+ }
+ //For all other errors, return a generic internal error
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(WPS_FAIL_EVENT,
+ WifiManager.ERROR, 0));
+ }
+
+ /* <event> status=<err> and the special case of <event> reason=FREQ_CONFLICT */
+ private P2pStatus p2pError(String dataString) {
+ P2pStatus err = P2pStatus.UNKNOWN;
+ String[] tokens = dataString.split(" ");
+ if (tokens.length < 2) return err;
+ String[] nameValue = tokens[1].split("=");
+ if (nameValue.length != 2) return err;
+
+ /* Handle the special case of reason=FREQ+CONFLICT */
+ if (nameValue[1].equals("FREQ_CONFLICT")) {
+ return P2pStatus.NO_COMMON_CHANNEL;
+ }
+ try {
+ err = P2pStatus.valueOf(Integer.parseInt(nameValue[1]));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ return err;
+ }
+
+ /**
+ * Handle p2p events
+ */
+ private void handleP2pEvents(String dataString) {
+ if (dataString.startsWith(P2P_DEVICE_FOUND_STR)) {
+ mStateMachine.sendMessage(P2P_DEVICE_FOUND_EVENT, new WifiP2pDevice(dataString));
+ } else if (dataString.startsWith(P2P_DEVICE_LOST_STR)) {
+ mStateMachine.sendMessage(P2P_DEVICE_LOST_EVENT, new WifiP2pDevice(dataString));
+ } else if (dataString.startsWith(P2P_FIND_STOPPED_STR)) {
+ mStateMachine.sendMessage(P2P_FIND_STOPPED_EVENT);
+ } else if (dataString.startsWith(P2P_GO_NEG_REQUEST_STR)) {
+ mStateMachine.sendMessage(P2P_GO_NEGOTIATION_REQUEST_EVENT,
+ new WifiP2pConfig(dataString));
+ } else if (dataString.startsWith(P2P_GO_NEG_SUCCESS_STR)) {
+ mStateMachine.sendMessage(P2P_GO_NEGOTIATION_SUCCESS_EVENT);
+ } else if (dataString.startsWith(P2P_GO_NEG_FAILURE_STR)) {
+ mStateMachine.sendMessage(P2P_GO_NEGOTIATION_FAILURE_EVENT, p2pError(dataString));
+ } else if (dataString.startsWith(P2P_GROUP_FORMATION_SUCCESS_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_FORMATION_SUCCESS_EVENT);
+ } else if (dataString.startsWith(P2P_GROUP_FORMATION_FAILURE_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_FORMATION_FAILURE_EVENT, p2pError(dataString));
+ } else if (dataString.startsWith(P2P_GROUP_STARTED_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_STARTED_EVENT, new WifiP2pGroup(dataString));
+ } else if (dataString.startsWith(P2P_GROUP_REMOVED_STR)) {
+ mStateMachine.sendMessage(P2P_GROUP_REMOVED_EVENT, new WifiP2pGroup(dataString));
+ } else if (dataString.startsWith(P2P_INVITATION_RECEIVED_STR)) {
+ mStateMachine.sendMessage(P2P_INVITATION_RECEIVED_EVENT,
+ new WifiP2pGroup(dataString));
+ } else if (dataString.startsWith(P2P_INVITATION_RESULT_STR)) {
+ mStateMachine.sendMessage(P2P_INVITATION_RESULT_EVENT, p2pError(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_PBC_REQ_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_PBC_RSP_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_PBC_RSP_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_ENTER_PIN_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_ENTER_PIN_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_SHOW_PIN_EVENT,
+ new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_PROV_DISC_FAILURE_STR)) {
+ mStateMachine.sendMessage(P2P_PROV_DISC_FAILURE_EVENT);
+ } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) {
+ List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString);
+ if (list != null) {
+ mStateMachine.sendMessage(P2P_SERV_DISC_RESP_EVENT, list);
+ } else {
+ Log.e(TAG, "Null service resp " + dataString);
+ }
+ }
+ }
+
+ /**
+ * Handle hostap events
+ */
+ private void handleHostApEvents(String dataString) {
+ String[] tokens = dataString.split(" ");
+ /* AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */
+ if (tokens[0].equals(AP_STA_CONNECTED_STR)) {
+ mStateMachine.sendMessage(AP_STA_CONNECTED_EVENT, new WifiP2pDevice(dataString));
+ /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */
+ } else if (tokens[0].equals(AP_STA_DISCONNECTED_STR)) {
+ mStateMachine.sendMessage(AP_STA_DISCONNECTED_EVENT, new WifiP2pDevice(dataString));
+ }
+ }
+
+ /**
+ * Handle the supplicant STATE-CHANGE event
+ * @param dataString New supplicant state string in the format:
+ * id=network-id state=new-state
+ */
+ private void handleSupplicantStateChange(String dataString) {
+ WifiSsid wifiSsid = null;
+ int index = dataString.lastIndexOf("SSID=");
+ if (index != -1) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ dataString.substring(index + 5));
+ }
+ String[] dataTokens = dataString.split(" ");
+
+ String BSSID = null;
+ int networkId = -1;
+ int newState = -1;
+ for (String token : dataTokens) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) {
+ continue;
+ }
+
+ if (nameValue[0].equals("BSSID")) {
+ BSSID = nameValue[1];
+ continue;
+ }
+
+ int value;
+ try {
+ value = Integer.parseInt(nameValue[1]);
+ } catch (NumberFormatException e) {
+ continue;
+ }
+
+ if (nameValue[0].equals("id")) {
+ networkId = value;
+ } else if (nameValue[0].equals("state")) {
+ newState = value;
+ }
+ }
+
+ if (newState == -1) return;
+
+ SupplicantState newSupplicantState = SupplicantState.INVALID;
+ for (SupplicantState state : SupplicantState.values()) {
+ if (state.ordinal() == newState) {
+ newSupplicantState = state;
+ break;
+ }
+ }
+ if (newSupplicantState == SupplicantState.INVALID) {
+ Log.w(TAG, "Invalid supplicant state: " + newState);
+ }
+ notifySupplicantStateChange(networkId, wifiSsid, BSSID, newSupplicantState);
+ }
+
+ private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {
+ String BSSID = null;
+ int networkId = -1;
+ if (newState == NetworkInfo.DetailedState.CONNECTED) {
+ Matcher match = mConnectedEventPattern.matcher(data);
+ if (!match.find()) {
+ if (DBG) Log.d(TAG, "Could not find BSSID in CONNECTED event string");
+ } else {
+ BSSID = match.group(1);
+ try {
+ networkId = Integer.parseInt(match.group(2));
+ } catch (NumberFormatException e) {
+ networkId = -1;
+ }
+ }
+ notifyNetworkStateChange(newState, BSSID, networkId);
+ }
+ }
+
+ /**
+ * Send the state machine a notification that the state of Wifi connectivity
+ * has changed.
+ * @param newState the new network state
+ * @param BSSID when the new state is {@link NetworkInfo.DetailedState#CONNECTED},
+ * this is the MAC address of the access point. Otherwise, it
+ * is {@code null}.
+ * @param netId the configured network on which the state change occurred
+ */
+ void notifyNetworkStateChange(NetworkInfo.DetailedState newState, String BSSID, int netId) {
+ if (newState == NetworkInfo.DetailedState.CONNECTED) {
+ Message m = mStateMachine.obtainMessage(NETWORK_CONNECTION_EVENT,
+ netId, 0, BSSID);
+ mStateMachine.sendMessage(m);
+ } else {
+ Message m = mStateMachine.obtainMessage(NETWORK_DISCONNECTION_EVENT,
+ netId, 0, BSSID);
+ mStateMachine.sendMessage(m);
+ }
+ }
+
+ /**
+ * Send the state machine a notification that the state of the supplicant
+ * has changed.
+ * @param networkId the configured network on which the state change occurred
+ * @param wifiSsid network name
+ * @param BSSID network address
+ * @param newState the new {@code SupplicantState}
+ */
+ void notifySupplicantStateChange(int networkId, WifiSsid wifiSsid, String BSSID,
+ SupplicantState newState) {
+ mStateMachine.sendMessage(mStateMachine.obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT,
+ new StateChangeResult(networkId, wifiSsid, BSSID, newState)));
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiNative.java b/services/java/com/android/server/wifi/WifiNative.java
new file mode 100644
index 0000000..49c6d7d
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiNative.java
@@ -0,0 +1,972 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.text.TextUtils;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Native calls for bring up/shut down of the supplicant daemon and for
+ * sending requests to the supplicant daemon
+ *
+ * waitForEvent() is called on the monitor thread for events. All other methods
+ * must be serialized from the framework.
+ *
+ * {@hide}
+ */
+public class WifiNative {
+
+ private static final boolean DBG = false;
+ private final String mTAG;
+ private static final int DEFAULT_GROUP_OWNER_INTENT = 6;
+
+ static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0;
+ static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1;
+ static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2;
+
+ static final int SCAN_WITHOUT_CONNECTION_SETUP = 1;
+ static final int SCAN_WITH_CONNECTION_SETUP = 2;
+
+ // Hold this lock before calling supplicant - it is required to
+ // mutually exclude access from Wifi and P2p state machines
+ static final Object mLock = new Object();
+
+ public final String mInterfaceName;
+ public final String mInterfacePrefix;
+
+ private boolean mSuspendOptEnabled = false;
+
+ public native static boolean loadDriver();
+
+ public native static boolean isDriverLoaded();
+
+ public native static boolean unloadDriver();
+
+ public native static boolean startSupplicant(boolean p2pSupported);
+
+ /* Sends a kill signal to supplicant. To be used when we have lost connection
+ or when the supplicant is hung */
+ public native static boolean killSupplicant(boolean p2pSupported);
+
+ private native boolean connectToSupplicantNative();
+
+ private native void closeSupplicantConnectionNative();
+
+ /**
+ * Wait for the supplicant to send an event, returning the event string.
+ * @return the event string sent by the supplicant.
+ */
+ private native String waitForEventNative();
+
+ private native boolean doBooleanCommandNative(String command);
+
+ private native int doIntCommandNative(String command);
+
+ private native String doStringCommandNative(String command);
+
+ public WifiNative(String interfaceName) {
+ mInterfaceName = interfaceName;
+ mTAG = "WifiNative-" + interfaceName;
+ if (!interfaceName.equals("p2p0")) {
+ mInterfacePrefix = "IFNAME=" + interfaceName + " ";
+ } else {
+ // commands for p2p0 interface don't need prefix
+ mInterfacePrefix = "";
+ }
+ }
+
+ private static final LocalLog mLocalLog = new LocalLog(1024);
+
+ // hold mLock before accessing mCmdIdLock
+ private int mCmdId;
+
+ public LocalLog getLocalLog() {
+ return mLocalLog;
+ }
+
+ private int getNewCmdIdLocked() {
+ return mCmdId++;
+ }
+
+ private void localLog(String s) {
+ if (mLocalLog != null)
+ mLocalLog.log(mInterfaceName + ": " + s);
+ }
+
+ public boolean connectToSupplicant() {
+ // No synchronization necessary .. it is implemented in WifiMonitor
+ localLog(mInterfacePrefix + "connectToSupplicant");
+ return connectToSupplicantNative();
+ }
+
+ public void closeSupplicantConnection() {
+ localLog(mInterfacePrefix + "closeSupplicantConnection");
+ closeSupplicantConnectionNative();
+ }
+
+ public String waitForEvent() {
+ // No synchronization necessary .. it is implemented in WifiMonitor
+ return waitForEventNative();
+ }
+
+ private boolean doBooleanCommand(String command) {
+ if (DBG) Log.d(mTAG, "doBoolean: " + command);
+ synchronized (mLock) {
+ int cmdId = getNewCmdIdLocked();
+ localLog(cmdId + "->" + mInterfacePrefix + command);
+ boolean result = doBooleanCommandNative(mInterfacePrefix + command);
+ localLog(cmdId + "<-" + result);
+ if (DBG) Log.d(mTAG, " returned " + result);
+ return result;
+ }
+ }
+
+ private int doIntCommand(String command) {
+ if (DBG) Log.d(mTAG, "doInt: " + command);
+ synchronized (mLock) {
+ int cmdId = getNewCmdIdLocked();
+ localLog(cmdId + "->" + mInterfacePrefix + command);
+ int result = doIntCommandNative(mInterfacePrefix + command);
+ localLog(cmdId + "<-" + result);
+ if (DBG) Log.d(mTAG, " returned " + result);
+ return result;
+ }
+ }
+
+ private String doStringCommand(String command) {
+ if (DBG) Log.d(mTAG, "doString: " + command);
+ synchronized (mLock) {
+ int cmdId = getNewCmdIdLocked();
+ localLog(cmdId + "->" + mInterfacePrefix + command);
+ String result = doStringCommandNative(mInterfacePrefix + command);
+ localLog(cmdId + "<-" + result);
+ if (DBG) Log.d(mTAG, " returned " + result);
+ return result;
+ }
+ }
+
+ private String doStringCommandWithoutLogging(String command) {
+ if (DBG) Log.d(mTAG, "doString: " + command);
+ synchronized (mLock) {
+ return doStringCommandNative(mInterfacePrefix + command);
+ }
+ }
+
+ public boolean ping() {
+ String pong = doStringCommand("PING");
+ return (pong != null && pong.equals("PONG"));
+ }
+
+ public boolean scan(int type) {
+ if (type == SCAN_WITHOUT_CONNECTION_SETUP) {
+ return doBooleanCommand("SCAN TYPE=ONLY");
+ } else if (type == SCAN_WITH_CONNECTION_SETUP) {
+ return doBooleanCommand("SCAN");
+ } else {
+ throw new IllegalArgumentException("Invalid scan type");
+ }
+ }
+
+ /* Does a graceful shutdown of supplicant. Is a common stop function for both p2p and sta.
+ *
+ * Note that underneath we use a harsh-sounding "terminate" supplicant command
+ * for a graceful stop and a mild-sounding "stop" interface
+ * to kill the process
+ */
+ public boolean stopSupplicant() {
+ return doBooleanCommand("TERMINATE");
+ }
+
+ public String listNetworks() {
+ return doStringCommand("LIST_NETWORKS");
+ }
+
+ public int addNetwork() {
+ return doIntCommand("ADD_NETWORK");
+ }
+
+ public boolean setNetworkVariable(int netId, String name, String value) {
+ if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
+ return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
+ }
+
+ public String getNetworkVariable(int netId, String name) {
+ if (TextUtils.isEmpty(name)) return null;
+
+ // GET_NETWORK will likely flood the logs ...
+ return doStringCommandWithoutLogging("GET_NETWORK " + netId + " " + name);
+ }
+
+ public boolean removeNetwork(int netId) {
+ return doBooleanCommand("REMOVE_NETWORK " + netId);
+ }
+
+ public boolean enableNetwork(int netId, boolean disableOthers) {
+ if (disableOthers) {
+ return doBooleanCommand("SELECT_NETWORK " + netId);
+ } else {
+ return doBooleanCommand("ENABLE_NETWORK " + netId);
+ }
+ }
+
+ public boolean disableNetwork(int netId) {
+ return doBooleanCommand("DISABLE_NETWORK " + netId);
+ }
+
+ public boolean reconnect() {
+ return doBooleanCommand("RECONNECT");
+ }
+
+ public boolean reassociate() {
+ return doBooleanCommand("REASSOCIATE");
+ }
+
+ public boolean disconnect() {
+ return doBooleanCommand("DISCONNECT");
+ }
+
+ public String status() {
+ return doStringCommand("STATUS");
+ }
+
+ public String getMacAddress() {
+ //Macaddr = XX.XX.XX.XX.XX.XX
+ String ret = doStringCommand("DRIVER MACADDR");
+ if (!TextUtils.isEmpty(ret)) {
+ String[] tokens = ret.split(" = ");
+ if (tokens.length == 2) return tokens[1];
+ }
+ return null;
+ }
+
+ /**
+ * Format of results:
+ * =================
+ * id=1
+ * bssid=68:7f:74:d7:1b:6e
+ * freq=2412
+ * level=-43
+ * tsf=1344621975160944
+ * age=2623
+ * flags=[WPA2-PSK-CCMP][WPS][ESS]
+ * ssid=zubyb
+ * ====
+ *
+ * RANGE=ALL gets all scan results
+ * RANGE=ID- gets results from ID
+ * MASK=<N> see wpa_supplicant/src/common/wpa_ctrl.h for details
+ */
+ public String scanResults(int sid) {
+ return doStringCommandWithoutLogging("BSS RANGE=" + sid + "- MASK=0x21987");
+ }
+
+ /**
+ * Format of command
+ * DRIVER WLS_BATCHING SET SCANFREQ=x MSCAN=r BESTN=y CHANNEL=<z, w, t> RTT=s
+ * where x is an ascii representation of an integer number of seconds between scans
+ * r is an ascii representation of an integer number of scans per batch
+ * y is an ascii representation of an integer number of the max AP to remember per scan
+ * z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values
+ * indicating entire ranges of channels
+ * s is an ascii representation of an integer number of highest-strength AP
+ * for which we'd like approximate distance reported
+ *
+ * The return value is an ascii integer representing a guess of the number of scans
+ * the firmware can remember before it runs out of buffer space or -1 on error
+ */
+ public String setBatchedScanSettings(BatchedScanSettings settings) {
+ if (settings == null) {
+ return doStringCommand("DRIVER WLS_BATCHING STOP");
+ }
+ String cmd = "DRIVER WLS_BATCHING SET SCANFREQ=" + settings.scanIntervalSec;
+ cmd += " MSCAN=" + settings.maxScansPerBatch;
+ if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
+ cmd += " BESTN=" + settings.maxApPerScan;
+ }
+ if (settings.channelSet != null && !settings.channelSet.isEmpty()) {
+ cmd += " CHANNEL=<";
+ int i = 0;
+ for (String channel : settings.channelSet) {
+ cmd += (i > 0 ? "," : "") + channel;
+ ++i;
+ }
+ cmd += ">";
+ }
+ if (settings.maxApForDistance != BatchedScanSettings.UNSPECIFIED) {
+ cmd += " RTT=" + settings.maxApForDistance;
+ }
+ return doStringCommand(cmd);
+ }
+
+ public String getBatchedScanResults() {
+ return doStringCommand("DRIVER WLS_BATCHING GET");
+ }
+
+ public boolean startDriver() {
+ return doBooleanCommand("DRIVER START");
+ }
+
+ public boolean stopDriver() {
+ return doBooleanCommand("DRIVER STOP");
+ }
+
+
+ /**
+ * Start filtering out Multicast V4 packets
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ *
+ * Multicast filtering rules work as follows:
+ *
+ * The driver can filter multicast (v4 and/or v6) and broadcast packets when in
+ * a power optimized mode (typically when screen goes off).
+ *
+ * In order to prevent the driver from filtering the multicast/broadcast packets, we have to
+ * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective
+ *
+ * DRIVER RXFILTER-ADD Num
+ * where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6
+ *
+ * and DRIVER RXFILTER-START
+ * In order to stop the usage of these rules, we do
+ *
+ * DRIVER RXFILTER-STOP
+ * DRIVER RXFILTER-REMOVE Num
+ * where Num is as described for RXFILTER-ADD
+ *
+ * The SETSUSPENDOPT driver command overrides the filtering rules
+ */
+ public boolean startFilteringMulticastV4Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-REMOVE 2")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ /**
+ * Stop filtering out Multicast V4 packets.
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ */
+ public boolean stopFilteringMulticastV4Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-ADD 2")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ /**
+ * Start filtering out Multicast V6 packets
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ */
+ public boolean startFilteringMulticastV6Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-REMOVE 3")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ /**
+ * Stop filtering out Multicast V6 packets.
+ * @return {@code true} if the operation succeeded, {@code false} otherwise
+ */
+ public boolean stopFilteringMulticastV6Packets() {
+ return doBooleanCommand("DRIVER RXFILTER-STOP")
+ && doBooleanCommand("DRIVER RXFILTER-ADD 3")
+ && doBooleanCommand("DRIVER RXFILTER-START");
+ }
+
+ public int getBand() {
+ String ret = doStringCommand("DRIVER GETBAND");
+ if (!TextUtils.isEmpty(ret)) {
+ //reply is "BAND X" where X is the band
+ String[] tokens = ret.split(" ");
+ try {
+ if (tokens.length == 2) return Integer.parseInt(tokens[1]);
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+ return -1;
+ }
+
+ public boolean setBand(int band) {
+ return doBooleanCommand("DRIVER SETBAND " + band);
+ }
+
+ /**
+ * Sets the bluetooth coexistence mode.
+ *
+ * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED},
+ * {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or
+ * {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}.
+ * @return Whether the mode was successfully set.
+ */
+ public boolean setBluetoothCoexistenceMode(int mode) {
+ return doBooleanCommand("DRIVER BTCOEXMODE " + mode);
+ }
+
+ /**
+ * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
+ * some of the low-level scan parameters used by the driver are changed to
+ * reduce interference with A2DP streaming.
+ *
+ * @param isSet whether to enable or disable this mode
+ * @return {@code true} if the command succeeded, {@code false} otherwise.
+ */
+ public boolean setBluetoothCoexistenceScanMode(boolean setCoexScanMode) {
+ if (setCoexScanMode) {
+ return doBooleanCommand("DRIVER BTCOEXSCAN-START");
+ } else {
+ return doBooleanCommand("DRIVER BTCOEXSCAN-STOP");
+ }
+ }
+
+ public boolean saveConfig() {
+ return doBooleanCommand("SAVE_CONFIG");
+ }
+
+ public boolean addToBlacklist(String bssid) {
+ if (TextUtils.isEmpty(bssid)) return false;
+ return doBooleanCommand("BLACKLIST " + bssid);
+ }
+
+ public boolean clearBlacklist() {
+ return doBooleanCommand("BLACKLIST clear");
+ }
+
+ public boolean setSuspendOptimizations(boolean enabled) {
+ if (mSuspendOptEnabled == enabled) return true;
+ mSuspendOptEnabled = enabled;
+ if (enabled) {
+ return doBooleanCommand("DRIVER SETSUSPENDMODE 1");
+ } else {
+ return doBooleanCommand("DRIVER SETSUSPENDMODE 0");
+ }
+ }
+
+ public boolean setCountryCode(String countryCode) {
+ return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT));
+ }
+
+ public void enableBackgroundScan(boolean enable) {
+ if (enable) {
+ doBooleanCommand("SET pno 1");
+ } else {
+ doBooleanCommand("SET pno 0");
+ }
+ }
+
+ public void setScanInterval(int scanInterval) {
+ doBooleanCommand("SCAN_INTERVAL " + scanInterval);
+ }
+
+ public void startTdls(String macAddr, boolean enable) {
+ if (enable) {
+ doBooleanCommand("TDLS_DISCOVER " + macAddr);
+ doBooleanCommand("TDLS_SETUP " + macAddr);
+ } else {
+ doBooleanCommand("TDLS_TEARDOWN " + macAddr);
+ }
+ }
+
+ /** Example output:
+ * RSSI=-65
+ * LINKSPEED=48
+ * NOISE=9999
+ * FREQUENCY=0
+ */
+ public String signalPoll() {
+ return doStringCommandWithoutLogging("SIGNAL_POLL");
+ }
+
+ /** Example outout:
+ * TXGOOD=396
+ * TXBAD=1
+ */
+ public String pktcntPoll() {
+ return doStringCommand("PKTCNT_POLL");
+ }
+
+ public void bssFlush() {
+ doBooleanCommand("BSS_FLUSH 0");
+ }
+
+ public boolean startWpsPbc(String bssid) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doBooleanCommand("WPS_PBC");
+ } else {
+ return doBooleanCommand("WPS_PBC " + bssid);
+ }
+ }
+
+ public boolean startWpsPbc(String iface, String bssid) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC");
+ } else {
+ return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC " + bssid);
+ }
+ }
+ }
+
+ public boolean startWpsPinKeypad(String pin) {
+ if (TextUtils.isEmpty(pin)) return false;
+ return doBooleanCommand("WPS_PIN any " + pin);
+ }
+
+ public boolean startWpsPinKeypad(String iface, String pin) {
+ if (TextUtils.isEmpty(pin)) return false;
+ synchronized (mLock) {
+ return doBooleanCommandNative("IFNAME=" + iface + " WPS_PIN any " + pin);
+ }
+ }
+
+
+ public String startWpsPinDisplay(String bssid) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doStringCommand("WPS_PIN any");
+ } else {
+ return doStringCommand("WPS_PIN " + bssid);
+ }
+ }
+
+ public String startWpsPinDisplay(String iface, String bssid) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(bssid)) {
+ return doStringCommandNative("IFNAME=" + iface + " WPS_PIN any");
+ } else {
+ return doStringCommandNative("IFNAME=" + iface + " WPS_PIN " + bssid);
+ }
+ }
+ }
+
+ /* Configures an access point connection */
+ public boolean startWpsRegistrar(String bssid, String pin) {
+ if (TextUtils.isEmpty(bssid) || TextUtils.isEmpty(pin)) return false;
+ return doBooleanCommand("WPS_REG " + bssid + " " + pin);
+ }
+
+ public boolean cancelWps() {
+ return doBooleanCommand("WPS_CANCEL");
+ }
+
+ public boolean setPersistentReconnect(boolean enabled) {
+ int value = (enabled == true) ? 1 : 0;
+ return doBooleanCommand("SET persistent_reconnect " + value);
+ }
+
+ public boolean setDeviceName(String name) {
+ return doBooleanCommand("SET device_name " + name);
+ }
+
+ public boolean setDeviceType(String type) {
+ return doBooleanCommand("SET device_type " + type);
+ }
+
+ public boolean setConfigMethods(String cfg) {
+ return doBooleanCommand("SET config_methods " + cfg);
+ }
+
+ public boolean setManufacturer(String value) {
+ return doBooleanCommand("SET manufacturer " + value);
+ }
+
+ public boolean setModelName(String value) {
+ return doBooleanCommand("SET model_name " + value);
+ }
+
+ public boolean setModelNumber(String value) {
+ return doBooleanCommand("SET model_number " + value);
+ }
+
+ public boolean setSerialNumber(String value) {
+ return doBooleanCommand("SET serial_number " + value);
+ }
+
+ public boolean setP2pSsidPostfix(String postfix) {
+ return doBooleanCommand("SET p2p_ssid_postfix " + postfix);
+ }
+
+ public boolean setP2pGroupIdle(String iface, int time) {
+ synchronized (mLock) {
+ return doBooleanCommandNative("IFNAME=" + iface + " SET p2p_group_idle " + time);
+ }
+ }
+
+ public void setPowerSave(boolean enabled) {
+ if (enabled) {
+ doBooleanCommand("SET ps 1");
+ } else {
+ doBooleanCommand("SET ps 0");
+ }
+ }
+
+ public boolean setP2pPowerSave(String iface, boolean enabled) {
+ synchronized (mLock) {
+ if (enabled) {
+ return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 1");
+ } else {
+ return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 0");
+ }
+ }
+ }
+
+ public boolean setWfdEnable(boolean enable) {
+ return doBooleanCommand("SET wifi_display " + (enable ? "1" : "0"));
+ }
+
+ public boolean setWfdDeviceInfo(String hex) {
+ return doBooleanCommand("WFD_SUBELEM_SET 0 " + hex);
+ }
+
+ /**
+ * "sta" prioritizes STA connection over P2P and "p2p" prioritizes
+ * P2P connection over STA
+ */
+ public boolean setConcurrencyPriority(String s) {
+ return doBooleanCommand("P2P_SET conc_pref " + s);
+ }
+
+ public boolean p2pFind() {
+ return doBooleanCommand("P2P_FIND");
+ }
+
+ public boolean p2pFind(int timeout) {
+ if (timeout <= 0) {
+ return p2pFind();
+ }
+ return doBooleanCommand("P2P_FIND " + timeout);
+ }
+
+ public boolean p2pStopFind() {
+ return doBooleanCommand("P2P_STOP_FIND");
+ }
+
+ public boolean p2pListen() {
+ return doBooleanCommand("P2P_LISTEN");
+ }
+
+ public boolean p2pListen(int timeout) {
+ if (timeout <= 0) {
+ return p2pListen();
+ }
+ return doBooleanCommand("P2P_LISTEN " + timeout);
+ }
+
+ public boolean p2pExtListen(boolean enable, int period, int interval) {
+ if (enable && interval < period) {
+ return false;
+ }
+ return doBooleanCommand("P2P_EXT_LISTEN"
+ + (enable ? (" " + period + " " + interval) : ""));
+ }
+
+ public boolean p2pSetChannel(int lc, int oc) {
+ if (DBG) Log.d(mTAG, "p2pSetChannel: lc="+lc+", oc="+oc);
+
+ if (lc >=1 && lc <= 11) {
+ if (!doBooleanCommand("P2P_SET listen_channel " + lc)) {
+ return false;
+ }
+ } else if (lc != 0) {
+ return false;
+ }
+
+ if (oc >= 1 && oc <= 165 ) {
+ int freq = (oc <= 14 ? 2407 : 5000) + oc * 5;
+ return doBooleanCommand("P2P_SET disallow_freq 1000-"
+ + (freq - 5) + "," + (freq + 5) + "-6000");
+ } else if (oc == 0) {
+ /* oc==0 disables "P2P_SET disallow_freq" (enables all freqs) */
+ return doBooleanCommand("P2P_SET disallow_freq \"\"");
+ }
+
+ return false;
+ }
+
+ public boolean p2pFlush() {
+ return doBooleanCommand("P2P_FLUSH");
+ }
+
+ /* p2p_connect <peer device address> <pbc|pin|PIN#> [label|display|keypad]
+ [persistent] [join|auth] [go_intent=<0..15>] [freq=<in MHz>] */
+ public String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
+ if (config == null) return null;
+ List<String> args = new ArrayList<String>();
+ WpsInfo wps = config.wps;
+ args.add(config.deviceAddress);
+
+ switch (wps.setup) {
+ case WpsInfo.PBC:
+ args.add("pbc");
+ break;
+ case WpsInfo.DISPLAY:
+ if (TextUtils.isEmpty(wps.pin)) {
+ args.add("pin");
+ } else {
+ args.add(wps.pin);
+ }
+ args.add("display");
+ break;
+ case WpsInfo.KEYPAD:
+ args.add(wps.pin);
+ args.add("keypad");
+ break;
+ case WpsInfo.LABEL:
+ args.add(wps.pin);
+ args.add("label");
+ default:
+ break;
+ }
+
+ if (config.netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+ args.add("persistent");
+ }
+
+ if (joinExistingGroup) {
+ args.add("join");
+ } else {
+ //TODO: This can be adapted based on device plugged in state and
+ //device battery state
+ int groupOwnerIntent = config.groupOwnerIntent;
+ if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
+ groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
+ }
+ args.add("go_intent=" + groupOwnerIntent);
+ }
+
+ String command = "P2P_CONNECT ";
+ for (String s : args) command += s + " ";
+
+ return doStringCommand(command);
+ }
+
+ public boolean p2pCancelConnect() {
+ return doBooleanCommand("P2P_CANCEL");
+ }
+
+ public boolean p2pProvisionDiscovery(WifiP2pConfig config) {
+ if (config == null) return false;
+
+ switch (config.wps.setup) {
+ case WpsInfo.PBC:
+ return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " pbc");
+ case WpsInfo.DISPLAY:
+ //We are doing display, so provision discovery is keypad
+ return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " keypad");
+ case WpsInfo.KEYPAD:
+ //We are doing keypad, so provision discovery is display
+ return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " display");
+ default:
+ break;
+ }
+ return false;
+ }
+
+ public boolean p2pGroupAdd(boolean persistent) {
+ if (persistent) {
+ return doBooleanCommand("P2P_GROUP_ADD persistent");
+ }
+ return doBooleanCommand("P2P_GROUP_ADD");
+ }
+
+ public boolean p2pGroupAdd(int netId) {
+ return doBooleanCommand("P2P_GROUP_ADD persistent=" + netId);
+ }
+
+ public boolean p2pGroupRemove(String iface) {
+ if (TextUtils.isEmpty(iface)) return false;
+ synchronized (mLock) {
+ return doBooleanCommandNative("IFNAME=" + iface + " P2P_GROUP_REMOVE " + iface);
+ }
+ }
+
+ public boolean p2pReject(String deviceAddress) {
+ return doBooleanCommand("P2P_REJECT " + deviceAddress);
+ }
+
+ /* Invite a peer to a group */
+ public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) {
+ if (TextUtils.isEmpty(deviceAddress)) return false;
+
+ if (group == null) {
+ return doBooleanCommand("P2P_INVITE peer=" + deviceAddress);
+ } else {
+ return doBooleanCommand("P2P_INVITE group=" + group.getInterface()
+ + " peer=" + deviceAddress + " go_dev_addr=" + group.getOwner().deviceAddress);
+ }
+ }
+
+ /* Reinvoke a persistent connection */
+ public boolean p2pReinvoke(int netId, String deviceAddress) {
+ if (TextUtils.isEmpty(deviceAddress) || netId < 0) return false;
+
+ return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress);
+ }
+
+ public String p2pGetSsid(String deviceAddress) {
+ return p2pGetParam(deviceAddress, "oper_ssid");
+ }
+
+ public String p2pGetDeviceAddress() {
+ String status = status();
+ if (status == null) return "";
+
+ String[] tokens = status.split("\n");
+ for (String token : tokens) {
+ if (token.startsWith("p2p_device_address=")) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ return nameValue[1];
+ }
+ }
+ return "";
+ }
+
+ public int getGroupCapability(String deviceAddress) {
+ int gc = 0;
+ if (TextUtils.isEmpty(deviceAddress)) return gc;
+ String peerInfo = p2pPeer(deviceAddress);
+ if (TextUtils.isEmpty(peerInfo)) return gc;
+
+ String[] tokens = peerInfo.split("\n");
+ for (String token : tokens) {
+ if (token.startsWith("group_capab=")) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ try {
+ return Integer.decode(nameValue[1]);
+ } catch(NumberFormatException e) {
+ return gc;
+ }
+ }
+ }
+ return gc;
+ }
+
+ public String p2pPeer(String deviceAddress) {
+ return doStringCommand("P2P_PEER " + deviceAddress);
+ }
+
+ private String p2pGetParam(String deviceAddress, String key) {
+ if (deviceAddress == null) return null;
+
+ String peerInfo = p2pPeer(deviceAddress);
+ if (peerInfo == null) return null;
+ String[] tokens= peerInfo.split("\n");
+
+ key += "=";
+ for (String token : tokens) {
+ if (token.startsWith(key)) {
+ String[] nameValue = token.split("=");
+ if (nameValue.length != 2) break;
+ return nameValue[1];
+ }
+ }
+ return null;
+ }
+
+ public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
+ * P2P_SERVICE_ADD upnp <version hex> <service>
+ *
+ * e.g)
+ * [Bonjour]
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
+ * 09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
+ *
+ * [UPnP]
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_ADD";
+ command += (" " + s);
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_DEL bonjour <query hexdump>
+ * P2P_SERVICE_DEL upnp <version hex> <service>
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_DEL ";
+
+ String[] data = s.split(" ");
+ if (data.length < 2) {
+ return false;
+ }
+ if ("upnp".equals(data[0])) {
+ command += s;
+ } else if ("bonjour".equals(data[0])) {
+ command += data[0];
+ command += (" " + data[1]);
+ } else {
+ return false;
+ }
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceFlush() {
+ return doBooleanCommand("P2P_SERVICE_FLUSH");
+ }
+
+ public String p2pServDiscReq(String addr, String query) {
+ String command = "P2P_SERV_DISC_REQ";
+ command += (" " + addr);
+ command += (" " + query);
+
+ return doStringCommand(command);
+ }
+
+ public boolean p2pServDiscCancelReq(String id) {
+ return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
+ }
+
+ /* Set the current mode of miracast operation.
+ * 0 = disabled
+ * 1 = operating as source
+ * 2 = operating as sink
+ */
+ public void setMiracastMode(int mode) {
+ // Note: optional feature on the driver. It is ok for this to fail.
+ doBooleanCommand("DRIVER MIRACAST " + mode);
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiNotificationController.java b/services/java/com/android/server/wifi/WifiNotificationController.java
index a9206e0..ec6aa37 100644
--- a/services/java/com/android/server/wifi/WifiNotificationController.java
+++ b/services/java/com/android/server/wifi/WifiNotificationController.java
@@ -28,7 +28,6 @@ import android.database.ContentObserver;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateMachine;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index 4b5c567..4342272 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -38,8 +38,6 @@ 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;
diff --git a/services/java/com/android/server/wifi/WifiStateMachine.java b/services/java/com/android/server/wifi/WifiStateMachine.java
new file mode 100644
index 0000000..4f68a54
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiStateMachine.java
@@ -0,0 +1,4419 @@
+/*
+ * 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 static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+
+/**
+ * TODO:
+ * Deprecate WIFI_STATE_UNKNOWN
+ */
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.app.backup.IBackupManager;
+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.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.DhcpStateMachine;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.RssiPacketCountInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.WpsResult;
+import android.net.wifi.WpsResult.Status;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.LruCache;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import com.android.server.net.BaseNetworkObserver;
+import com.android.server.wifi.p2p.WifiP2pService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Iterator;
+import java.util.regex.Pattern;
+
+/**
+ * Track the state of Wifi connectivity. All event handling is done here,
+ * and all changes in connectivity state are initiated here.
+ *
+ * Wi-Fi now supports three modes of operation: Client, SoftAp and p2p
+ * In the current implementation, we support concurrent wifi p2p and wifi operation.
+ * The WifiStateMachine handles SoftAp and Client operations while WifiP2pService
+ * handles p2p operation.
+ *
+ * @hide
+ */
+public class WifiStateMachine extends StateMachine {
+
+ private static final String NETWORKTYPE = "WIFI";
+ private static final boolean DBG = false;
+
+ private WifiMonitor mWifiMonitor;
+ private WifiNative mWifiNative;
+ private WifiConfigStore mWifiConfigStore;
+ private INetworkManagementService mNwService;
+ private ConnectivityManager mCm;
+
+ private final boolean mP2pSupported;
+ private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
+ private boolean mTemporarilyDisconnectWifi = false;
+ private final String mPrimaryDeviceType;
+
+ /* Scan results handling */
+ private List<ScanResult> mScanResults = new ArrayList<ScanResult>();
+ private static final Pattern scanResultPattern = Pattern.compile("\t+");
+ private static final int SCAN_RESULT_CACHE_SIZE = 80;
+ private final LruCache<String, ScanResult> mScanResultCache;
+
+ /* Batch scan results */
+ private final List<BatchedScanResult> mBatchedScanResults =
+ new ArrayList<BatchedScanResult>();
+ private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE;
+ private int mExpectedBatchedScans = 0;
+ private long mBatchedScanMinPollTime = 0;
+
+ /* Chipset supports background scan */
+ private final boolean mBackgroundScanSupported;
+
+ private String mInterfaceName;
+ /* Tethering interface could be separate from wlan interface */
+ private String mTetherInterfaceName;
+
+ private int mLastSignalLevel = -1;
+ private String mLastBssid;
+ private int mLastNetworkId;
+ private boolean mEnableRssiPolling = false;
+ private boolean mEnableBackgroundScan = false;
+ private int mRssiPollToken = 0;
+ private int mReconnectCount = 0;
+ /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
+ * In CONNECT_MODE, the STA can scan and connect to an access point
+ * In SCAN_ONLY_MODE, the STA can only scan for access points
+ * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off
+ */
+ private int mOperationalMode = CONNECT_MODE;
+ private boolean mScanResultIsPending = false;
+ private WorkSource mScanWorkSource = null;
+ private static final int UNKNOWN_SCAN_SOURCE = -1;
+ /* Tracks if state machine has received any screen state change broadcast yet.
+ * We can miss one of these at boot.
+ */
+ private AtomicBoolean mScreenBroadcastReceived = new AtomicBoolean(false);
+
+ private boolean mBluetoothConnectionActive = false;
+
+ private PowerManager.WakeLock mSuspendWakeLock;
+
+ /**
+ * Interval in milliseconds between polling for RSSI
+ * and linkspeed information
+ */
+ private static final int POLL_RSSI_INTERVAL_MSECS = 3000;
+
+ /**
+ * Delay between supplicant restarts upon failure to establish connection
+ */
+ private static final int SUPPLICANT_RESTART_INTERVAL_MSECS = 5000;
+
+ /**
+ * Number of times we attempt to restart supplicant
+ */
+ private static final int SUPPLICANT_RESTART_TRIES = 5;
+
+ private int mSupplicantRestartCount = 0;
+ /* Tracks sequence number on stop failure message */
+ private int mSupplicantStopFailureToken = 0;
+
+ /**
+ * Tether state change notification time out
+ */
+ private static final int TETHER_NOTIFICATION_TIME_OUT_MSECS = 5000;
+
+ /* Tracks sequence number on a tether notification time out */
+ private int mTetherToken = 0;
+
+ /**
+ * Driver start time out.
+ */
+ private static final int DRIVER_START_TIME_OUT_MSECS = 10000;
+
+ /* Tracks sequence number on a driver time out */
+ private int mDriverStartToken = 0;
+
+ /**
+ * The link properties of the wifi interface.
+ * Do not modify this directly; use updateLinkProperties instead.
+ */
+ private LinkProperties mLinkProperties;
+
+ /**
+ * Subset of link properties coming from netlink.
+ * Currently includes IPv4 and IPv6 addresses. In the future will also include IPv6 DNS servers
+ * and domains obtained from router advertisements (RFC 6106).
+ */
+ private final LinkProperties mNetlinkLinkProperties;
+
+ /* Tracks sequence number on a periodic scan message */
+ private int mPeriodicScanToken = 0;
+
+ // Wakelock held during wifi start/stop and driver load/unload
+ private PowerManager.WakeLock mWakeLock;
+
+ private Context mContext;
+
+ private final Object mDhcpResultsLock = new Object();
+ private DhcpResults mDhcpResults;
+ private WifiInfo mWifiInfo;
+ private NetworkInfo mNetworkInfo;
+ private SupplicantStateTracker mSupplicantStateTracker;
+ private DhcpStateMachine mDhcpStateMachine;
+ private boolean mDhcpActive = false;
+
+ private class InterfaceObserver extends BaseNetworkObserver {
+ private WifiStateMachine mWifiStateMachine;
+
+ InterfaceObserver(WifiStateMachine wifiStateMachine) {
+ super();
+ mWifiStateMachine = wifiStateMachine;
+ }
+
+ private void maybeLog(String operation, String iface, LinkAddress address) {
+ if (DBG) {
+ log(operation + ": " + address + " on " + iface +
+ " flags " + address.getFlags() + " scope " + address.getScope());
+ }
+ }
+
+ @Override
+ public void addressUpdated(String iface, LinkAddress address) {
+ if (mWifiStateMachine.mInterfaceName.equals(iface)) {
+ maybeLog("addressUpdated", iface, address);
+ mWifiStateMachine.sendMessage(CMD_IP_ADDRESS_UPDATED, address);
+ }
+ }
+
+ @Override
+ public void addressRemoved(String iface, LinkAddress address) {
+ if (mWifiStateMachine.mInterfaceName.equals(iface)) {
+ maybeLog("addressRemoved", iface, address);
+ mWifiStateMachine.sendMessage(CMD_IP_ADDRESS_REMOVED, address);
+ }
+ }
+ }
+
+ private InterfaceObserver mInterfaceObserver;
+
+ private AlarmManager mAlarmManager;
+ private PendingIntent mScanIntent;
+ private PendingIntent mDriverStopIntent;
+ private PendingIntent mBatchedScanIntervalIntent;
+
+ /* Tracks current frequency mode */
+ private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
+
+ /* Tracks if we are filtering Multicast v4 packets. Default is to filter. */
+ private AtomicBoolean mFilteringMulticastV4Packets = new AtomicBoolean(true);
+
+ // Channel for sending replies.
+ private AsyncChannel mReplyChannel = new AsyncChannel();
+
+ private WifiP2pManager mWifiP2pManager;
+ //Used to initiate a connection with WifiP2pService
+ private AsyncChannel mWifiP2pChannel;
+ private AsyncChannel mWifiApConfigChannel;
+
+ /* The base for wifi message types */
+ static final int BASE = Protocol.BASE_WIFI;
+ /* Start the supplicant */
+ static final int CMD_START_SUPPLICANT = BASE + 11;
+ /* Stop the supplicant */
+ static final int CMD_STOP_SUPPLICANT = BASE + 12;
+ /* Start the driver */
+ static final int CMD_START_DRIVER = BASE + 13;
+ /* Stop the driver */
+ static final int CMD_STOP_DRIVER = BASE + 14;
+ /* Indicates Static IP succeeded */
+ static final int CMD_STATIC_IP_SUCCESS = BASE + 15;
+ /* Indicates Static IP failed */
+ static final int CMD_STATIC_IP_FAILURE = BASE + 16;
+ /* Indicates supplicant stop failed */
+ static final int CMD_STOP_SUPPLICANT_FAILED = BASE + 17;
+ /* Delayed stop to avoid shutting down driver too quick*/
+ static final int CMD_DELAYED_STOP_DRIVER = BASE + 18;
+ /* A delayed message sent to start driver when it fail to come up */
+ static final int CMD_DRIVER_START_TIMED_OUT = BASE + 19;
+
+ /* Start the soft access point */
+ static final int CMD_START_AP = BASE + 21;
+ /* Indicates soft ap start succeeded */
+ static final int CMD_START_AP_SUCCESS = BASE + 22;
+ /* Indicates soft ap start failed */
+ static final int CMD_START_AP_FAILURE = BASE + 23;
+ /* Stop the soft access point */
+ static final int CMD_STOP_AP = BASE + 24;
+ /* Set the soft access point configuration */
+ static final int CMD_SET_AP_CONFIG = BASE + 25;
+ /* Soft access point configuration set completed */
+ static final int CMD_SET_AP_CONFIG_COMPLETED = BASE + 26;
+ /* Request the soft access point configuration */
+ static final int CMD_REQUEST_AP_CONFIG = BASE + 27;
+ /* Response to access point configuration request */
+ static final int CMD_RESPONSE_AP_CONFIG = BASE + 28;
+ /* Invoked when getting a tether state change notification */
+ static final int CMD_TETHER_STATE_CHANGE = BASE + 29;
+ /* A delayed message sent to indicate tether state change failed to arrive */
+ static final int CMD_TETHER_NOTIFICATION_TIMED_OUT = BASE + 30;
+
+ static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE = BASE + 31;
+
+ /* Supplicant commands */
+ /* Is supplicant alive ? */
+ static final int CMD_PING_SUPPLICANT = BASE + 51;
+ /* Add/update a network configuration */
+ static final int CMD_ADD_OR_UPDATE_NETWORK = BASE + 52;
+ /* Delete a network */
+ static final int CMD_REMOVE_NETWORK = BASE + 53;
+ /* Enable a network. The device will attempt a connection to the given network. */
+ static final int CMD_ENABLE_NETWORK = BASE + 54;
+ /* Enable all networks */
+ static final int CMD_ENABLE_ALL_NETWORKS = BASE + 55;
+ /* Blacklist network. De-prioritizes the given BSSID for connection. */
+ static final int CMD_BLACKLIST_NETWORK = BASE + 56;
+ /* Clear the blacklist network list */
+ static final int CMD_CLEAR_BLACKLIST = BASE + 57;
+ /* Save configuration */
+ static final int CMD_SAVE_CONFIG = BASE + 58;
+ /* Get configured networks*/
+ static final int CMD_GET_CONFIGURED_NETWORKS = BASE + 59;
+
+ /* Supplicant commands after driver start*/
+ /* Initiate a scan */
+ static final int CMD_START_SCAN = BASE + 71;
+ /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
+ static final int CMD_SET_OPERATIONAL_MODE = BASE + 72;
+ /* Disconnect from a network */
+ static final int CMD_DISCONNECT = BASE + 73;
+ /* Reconnect to a network */
+ static final int CMD_RECONNECT = BASE + 74;
+ /* Reassociate to a network */
+ static final int CMD_REASSOCIATE = BASE + 75;
+ /* Controls suspend mode optimizations
+ *
+ * When high perf mode is enabled, suspend mode optimizations are disabled
+ *
+ * When high perf mode is disabled, suspend mode optimizations are enabled
+ *
+ * Suspend mode optimizations include:
+ * - packet filtering
+ * - turn off roaming
+ * - DTIM wake up settings
+ */
+ static final int CMD_SET_HIGH_PERF_MODE = BASE + 77;
+ /* Set the country code */
+ static final int CMD_SET_COUNTRY_CODE = BASE + 80;
+ /* Enables RSSI poll */
+ static final int CMD_ENABLE_RSSI_POLL = BASE + 82;
+ /* RSSI poll */
+ static final int CMD_RSSI_POLL = BASE + 83;
+ /* Set up packet filtering */
+ static final int CMD_START_PACKET_FILTERING = BASE + 84;
+ /* Clear packet filter */
+ static final int CMD_STOP_PACKET_FILTERING = BASE + 85;
+ /* Enable suspend mode optimizations in the driver */
+ static final int CMD_SET_SUSPEND_OPT_ENABLED = BASE + 86;
+ /* When there are no saved networks, we do a periodic scan to notify user of
+ * an open network */
+ static final int CMD_NO_NETWORKS_PERIODIC_SCAN = BASE + 88;
+
+ /* arg1 values to CMD_STOP_PACKET_FILTERING and CMD_START_PACKET_FILTERING */
+ static final int MULTICAST_V6 = 1;
+ static final int MULTICAST_V4 = 0;
+
+ /* Set the frequency band */
+ static final int CMD_SET_FREQUENCY_BAND = BASE + 90;
+ /* Enable background scan for configured networks */
+ static final int CMD_ENABLE_BACKGROUND_SCAN = BASE + 91;
+ /* Enable TDLS on a specific MAC address */
+ static final int CMD_ENABLE_TDLS = BASE + 92;
+
+ /* Commands from/to the SupplicantStateTracker */
+ /* Reset the supplicant state tracker */
+ static final int CMD_RESET_SUPPLICANT_STATE = BASE + 111;
+
+ /* P2p commands */
+ /* We are ok with no response here since we wont do much with it anyway */
+ public static final int CMD_ENABLE_P2P = BASE + 131;
+ /* In order to shut down supplicant cleanly, we wait till p2p has
+ * been disabled */
+ public static final int CMD_DISABLE_P2P_REQ = BASE + 132;
+ public static final int CMD_DISABLE_P2P_RSP = BASE + 133;
+
+ public static final int CMD_BOOT_COMPLETED = BASE + 134;
+
+ /* change the batch scan settings.
+ * arg1 = responsible UID
+ * arg2 = csph (channel scans per hour)
+ * obj = bundle with the new settings and the optional worksource
+ */
+ public static final int CMD_SET_BATCHED_SCAN = BASE + 135;
+ public static final int CMD_START_NEXT_BATCHED_SCAN = BASE + 136;
+ public static final int CMD_POLL_BATCHED_SCAN = BASE + 137;
+
+ /* Link configuration (IP address, DNS, ...) changes */
+ /* An new IP address was added to our interface, or an existing IP address was updated */
+ static final int CMD_IP_ADDRESS_UPDATED = BASE + 140;
+ /* An IP address was removed from our interface */
+ static final int CMD_IP_ADDRESS_REMOVED = BASE + 141;
+ /* Reload all networks and reconnect */
+ static final int CMD_RELOAD_TLS_AND_RECONNECT = BASE + 142;
+
+ /* Wifi state machine modes of operation */
+ /* CONNECT_MODE - connect to any 'known' AP when it becomes available */
+ public static final int CONNECT_MODE = 1;
+ /* SCAN_ONLY_MODE - don't connect to any APs; scan, but only while apps hold lock */
+ public static final int SCAN_ONLY_MODE = 2;
+ /* SCAN_ONLY_WITH_WIFI_OFF - scan, but don't connect to any APs */
+ public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
+
+ private static final int SUCCESS = 1;
+ private static final int FAILURE = -1;
+
+ /**
+ * The maximum number of times we will retry a connection to an access point
+ * for which we have failed in acquiring an IP address from DHCP. A value of
+ * N means that we will make N+1 connection attempts in all.
+ * <p>
+ * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
+ * value if a Settings value is not present.
+ */
+ private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
+
+ /* Tracks if suspend optimizations need to be disabled by DHCP,
+ * screen or due to high perf mode.
+ * When any of them needs to disable it, we keep the suspend optimizations
+ * disabled
+ */
+ private int mSuspendOptNeedsDisabled = 0;
+
+ private static final int SUSPEND_DUE_TO_DHCP = 1;
+ private static final int SUSPEND_DUE_TO_HIGH_PERF = 1<<1;
+ private static final int SUSPEND_DUE_TO_SCREEN = 1<<2;
+
+ /* Tracks if user has enabled suspend optimizations through settings */
+ private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
+
+ /**
+ * Default framework scan interval in milliseconds. This is used in the scenario in which
+ * wifi chipset does not support background scanning to set up a
+ * periodic wake up scan so that the device can connect to a new access
+ * point on the move. {@link Settings.Global#WIFI_FRAMEWORK_SCAN_INTERVAL_MS} can
+ * override this.
+ */
+ private final int mDefaultFrameworkScanIntervalMs;
+
+ /**
+ * Supplicant scan interval in milliseconds.
+ * Comes from {@link Settings.Global#WIFI_SUPPLICANT_SCAN_INTERVAL_MS} or
+ * from the default config if the setting is not set
+ */
+ private long mSupplicantScanIntervalMs;
+
+ /**
+ * Minimum time interval between enabling all networks.
+ * A device can end up repeatedly connecting to a bad network on screen on/off toggle
+ * due to enabling every time. We add a threshold to avoid this.
+ */
+ private static final int MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS = 10 * 60 * 1000; /* 10 minutes */
+ private long mLastEnableAllNetworksTime;
+
+ /**
+ * Starting and shutting down driver too quick causes problems leading to driver
+ * being in a bad state. Delay driver stop.
+ */
+ private final int mDriverStopDelayMs;
+ private int mDelayedStopCounter;
+ private boolean mInDelayedStop = false;
+
+ // sometimes telephony gives us this data before boot is complete and we can't store it
+ // until after, so the write is deferred
+ private volatile String mPersistedCountryCode;
+
+ // Supplicant doesn't like setting the same country code multiple times (it may drop
+ // currently connected network), so we save the country code here to avoid redundency
+ private String mLastSetCountryCode;
+
+ private static final int MIN_RSSI = -200;
+ private static final int MAX_RSSI = 256;
+
+ /* Default parent state */
+ private State mDefaultState = new DefaultState();
+ /* Temporary initial state */
+ private State mInitialState = new InitialState();
+ /* Driver loaded, waiting for supplicant to start */
+ private State mSupplicantStartingState = new SupplicantStartingState();
+ /* Driver loaded and supplicant ready */
+ private State mSupplicantStartedState = new SupplicantStartedState();
+ /* Waiting for supplicant to stop and monitor to exit */
+ private State mSupplicantStoppingState = new SupplicantStoppingState();
+ /* Driver start issued, waiting for completed event */
+ private State mDriverStartingState = new DriverStartingState();
+ /* Driver started */
+ private State mDriverStartedState = new DriverStartedState();
+ /* Wait until p2p is disabled
+ * This is a special state which is entered right after we exit out of DriverStartedState
+ * before transitioning to another state.
+ */
+ private State mWaitForP2pDisableState = new WaitForP2pDisableState();
+ /* Driver stopping */
+ private State mDriverStoppingState = new DriverStoppingState();
+ /* Driver stopped */
+ private State mDriverStoppedState = new DriverStoppedState();
+ /* Scan for networks, no connection will be established */
+ private State mScanModeState = new ScanModeState();
+ /* Connecting to an access point */
+ private State mConnectModeState = new ConnectModeState();
+ /* Connected at 802.11 (L2) level */
+ private State mL2ConnectedState = new L2ConnectedState();
+ /* fetching IP after connection to access point (assoc+auth complete) */
+ private State mObtainingIpState = new ObtainingIpState();
+ /* Waiting for link quality verification to be complete */
+ private State mVerifyingLinkState = new VerifyingLinkState();
+ /* Connected with IP addr */
+ private State mConnectedState = new ConnectedState();
+ /* disconnect issued, waiting for network disconnect confirmation */
+ private State mDisconnectingState = new DisconnectingState();
+ /* Network is not connected, supplicant assoc+auth is not complete */
+ private State mDisconnectedState = new DisconnectedState();
+ /* Waiting for WPS to be completed*/
+ private State mWpsRunningState = new WpsRunningState();
+
+ /* Soft ap is starting up */
+ private State mSoftApStartingState = new SoftApStartingState();
+ /* Soft ap is running */
+ private State mSoftApStartedState = new SoftApStartedState();
+ /* Soft ap is running and we are waiting for tether notification */
+ private State mTetheringState = new TetheringState();
+ /* Soft ap is running and we are tethered through connectivity service */
+ private State mTetheredState = new TetheredState();
+ /* Waiting for untether confirmation before stopping soft Ap */
+ private State mUntetheringState = new UntetheringState();
+
+ private class TetherStateChange {
+ ArrayList<String> available;
+ ArrayList<String> active;
+ TetherStateChange(ArrayList<String> av, ArrayList<String> ac) {
+ available = av;
+ active = ac;
+ }
+ }
+
+
+ /**
+ * 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}
+ *
+ */
+ private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
+
+ /**
+ * 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}
+ *
+ */
+ private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
+
+ private static final int SCAN_REQUEST = 0;
+ private static final String ACTION_START_SCAN =
+ "com.android.server.WifiManager.action.START_SCAN";
+
+ private static final String DELAYED_STOP_COUNTER = "DelayedStopCounter";
+ private static final int DRIVER_STOP_REQUEST = 0;
+ private static final String ACTION_DELAYED_DRIVER_STOP =
+ "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
+
+ private static final String ACTION_REFRESH_BATCHED_SCAN =
+ "com.android.server.WifiManager.action.REFRESH_BATCHED_SCAN";
+ /**
+ * Keep track of whether WIFI is running.
+ */
+ private boolean mIsRunning = false;
+
+ /**
+ * Keep track of whether we last told the battery stats we had started.
+ */
+ private boolean mReportedRunning = false;
+
+ /**
+ * Most recently set source of starting WIFI.
+ */
+ private final WorkSource mRunningWifiUids = new WorkSource();
+
+ /**
+ * The last reported UIDs that were responsible for starting WIFI.
+ */
+ private final WorkSource mLastRunningWifiUids = new WorkSource();
+
+ private final IBatteryStats mBatteryStats;
+
+ private BatchedScanSettings mBatchedScanSettings = null;
+
+ /**
+ * Track the worksource/cost of the current settings and track what's been noted
+ * to the battery stats, so we can mark the end of the previous when changing.
+ */
+ private WorkSource mBatchedScanWorkSource = null;
+ private int mBatchedScanCsph = 0;
+ private WorkSource mNotedBatchedScanWorkSource = null;
+ private int mNotedBatchedScanCsph = 0;
+
+
+ public WifiStateMachine(Context context, String wlanInterface) {
+ super("WifiStateMachine");
+ mContext = context;
+ mInterfaceName = wlanInterface;
+
+ mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
+ mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME));
+
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNwService = INetworkManagementService.Stub.asInterface(b);
+
+ mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT);
+
+ mWifiNative = new WifiNative(mInterfaceName);
+ mWifiConfigStore = new WifiConfigStore(context, mWifiNative);
+ mWifiMonitor = new WifiMonitor(this, mWifiNative);
+ mWifiInfo = new WifiInfo();
+ mSupplicantStateTracker = new SupplicantStateTracker(context, this, mWifiConfigStore,
+ getHandler());
+ mLinkProperties = new LinkProperties();
+ mNetlinkLinkProperties = new LinkProperties();
+
+ mWifiP2pManager = (WifiP2pManager) mContext.getSystemService(Context.WIFI_P2P_SERVICE);
+
+ mNetworkInfo.setIsAvailable(false);
+ mLastBssid = null;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ mLastSignalLevel = -1;
+
+ mInterfaceObserver = new InterfaceObserver(this);
+ try {
+ mNwService.registerObserver(mInterfaceObserver);
+ } catch (RemoteException e) {
+ loge("Couldn't register interface observer: " + e.toString());
+ }
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ Intent scanIntent = new Intent(ACTION_START_SCAN, null);
+ mScanIntent = PendingIntent.getBroadcast(mContext, SCAN_REQUEST, scanIntent, 0);
+
+ Intent batchedIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
+ mBatchedScanIntervalIntent = PendingIntent.getBroadcast(mContext, 0, batchedIntent, 0);
+
+ mDefaultFrameworkScanIntervalMs = mContext.getResources().getInteger(
+ R.integer.config_wifi_framework_scan_interval);
+
+ mDriverStopDelayMs = mContext.getResources().getInteger(
+ R.integer.config_wifi_driver_stop_delay);
+
+ mBackgroundScanSupported = mContext.getResources().getBoolean(
+ R.bool.config_wifi_background_scan_support);
+
+ mPrimaryDeviceType = mContext.getResources().getString(
+ R.string.config_wifi_p2p_device_type);
+
+ mUserWantsSuspendOpt.set(Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ArrayList<String> available = intent.getStringArrayListExtra(
+ ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+ ArrayList<String> active = intent.getStringArrayListExtra(
+ ConnectivityManager.EXTRA_ACTIVE_TETHER);
+ sendMessage(CMD_TETHER_STATE_CHANGE, new TetherStateChange(available, active));
+ }
+ },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final WorkSource workSource = null;
+ startScan(UNKNOWN_SCAN_SOURCE, workSource);
+ }
+ },
+ new IntentFilter(ACTION_START_SCAN));
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ handleScreenStateChanged(true);
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ handleScreenStateChanged(false);
+ } else if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
+ startNextBatchedScanAsync();
+ }
+ }
+ }, filter);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0);
+ sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0);
+ }
+ },
+ new IntentFilter(ACTION_DELAYED_DRIVER_STOP));
+
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED), false,
+ new ContentObserver(getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mUserWantsSuspendOpt.set(Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
+ }
+ });
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sendMessage(CMD_BOOT_COMPLETED);
+ }
+ },
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+
+ mScanResultCache = new LruCache<String, ScanResult>(SCAN_RESULT_CACHE_SIZE);
+
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getName());
+
+ mSuspendWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiSuspend");
+ mSuspendWakeLock.setReferenceCounted(false);
+
+ addState(mDefaultState);
+ addState(mInitialState, mDefaultState);
+ addState(mSupplicantStartingState, mDefaultState);
+ addState(mSupplicantStartedState, mDefaultState);
+ addState(mDriverStartingState, mSupplicantStartedState);
+ addState(mDriverStartedState, mSupplicantStartedState);
+ addState(mScanModeState, mDriverStartedState);
+ addState(mConnectModeState, mDriverStartedState);
+ addState(mL2ConnectedState, mConnectModeState);
+ addState(mObtainingIpState, mL2ConnectedState);
+ addState(mVerifyingLinkState, mL2ConnectedState);
+ addState(mConnectedState, mL2ConnectedState);
+ addState(mDisconnectingState, mConnectModeState);
+ addState(mDisconnectedState, mConnectModeState);
+ addState(mWpsRunningState, mConnectModeState);
+ addState(mWaitForP2pDisableState, mSupplicantStartedState);
+ addState(mDriverStoppingState, mSupplicantStartedState);
+ addState(mDriverStoppedState, mSupplicantStartedState);
+ addState(mSupplicantStoppingState, mDefaultState);
+ addState(mSoftApStartingState, mDefaultState);
+ addState(mSoftApStartedState, mDefaultState);
+ addState(mTetheringState, mSoftApStartedState);
+ addState(mTetheredState, mSoftApStartedState);
+ addState(mUntetheringState, mSoftApStartedState);
+
+ setInitialState(mInitialState);
+
+ setLogRecSize(2000);
+ setLogOnlyTransitions(false);
+ if (DBG) setDbg(true);
+
+ //start the state machine
+ start();
+
+ final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /*********************************************************
+ * Methods exposed for public use
+ ********************************************************/
+
+ public Messenger getMessenger() {
+ return new Messenger(getHandler());
+ }
+ /**
+ * TODO: doc
+ */
+ public boolean syncPingSupplicant(AsyncChannel channel) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_PING_SUPPLICANT);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Initiate a wifi scan. If workSource is not null, blame is given to it,
+ * otherwise blame is given to callingUid.
+ *
+ * @param callingUid The uid initiating the wifi scan. Blame will be given
+ * here unless workSource is specified.
+ * @param workSource If not null, blame is given to workSource.
+ */
+ public void startScan(int callingUid, WorkSource workSource) {
+ sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
+ }
+
+ /**
+ * start or stop batched scanning using the given settings
+ */
+ private static final String BATCHED_SETTING = "batched_settings";
+ private static final String BATCHED_WORKSOURCE = "batched_worksource";
+ public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid, int csph,
+ WorkSource workSource) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BATCHED_SETTING, settings);
+ bundle.putParcelable(BATCHED_WORKSOURCE, workSource);
+ sendMessage(CMD_SET_BATCHED_SCAN, callingUid, csph, bundle);
+ }
+
+ public List<BatchedScanResult> syncGetBatchedScanResultsList() {
+ synchronized (mBatchedScanResults) {
+ List<BatchedScanResult> batchedScanList =
+ new ArrayList<BatchedScanResult>(mBatchedScanResults.size());
+ for(BatchedScanResult result: mBatchedScanResults) {
+ batchedScanList.add(new BatchedScanResult(result));
+ }
+ return batchedScanList;
+ }
+ }
+
+ public void requestBatchedScanPoll() {
+ sendMessage(CMD_POLL_BATCHED_SCAN);
+ }
+
+ private void startBatchedScan() {
+ if (mBatchedScanSettings == null) return;
+
+ if (mDhcpActive) {
+ if (DBG) log("not starting Batched Scans due to DHCP");
+ return;
+ }
+
+ // first grab any existing data
+ retrieveBatchedScanData();
+
+ mAlarmManager.cancel(mBatchedScanIntervalIntent);
+
+ String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
+ try {
+ mExpectedBatchedScans = Integer.parseInt(scansExpected);
+ setNextBatchedAlarm(mExpectedBatchedScans);
+ if (mExpectedBatchedScans > 0) noteBatchedScanStart();
+ } catch (NumberFormatException e) {
+ stopBatchedScan();
+ loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
+ }
+ }
+
+ // called from BroadcastListener
+ private void startNextBatchedScanAsync() {
+ sendMessage(CMD_START_NEXT_BATCHED_SCAN);
+ }
+
+ private void startNextBatchedScan() {
+ // first grab any existing data
+ retrieveBatchedScanData();
+
+ setNextBatchedAlarm(mExpectedBatchedScans);
+ }
+
+ private void handleBatchedScanPollRequest() {
+ if (DBG) {
+ log("handleBatchedScanPoll Request - mBatchedScanMinPollTime=" +
+ mBatchedScanMinPollTime + " , mBatchedScanSettings=" +
+ mBatchedScanSettings);
+ }
+ // if there is no appropriate PollTime that's because we either aren't
+ // batching or we've already set a time for a poll request
+ if (mBatchedScanMinPollTime == 0) return;
+ if (mBatchedScanSettings == null) return;
+
+ long now = System.currentTimeMillis();
+
+ if (now > mBatchedScanMinPollTime) {
+ // do the poll and reset our timers
+ startNextBatchedScan();
+ } else {
+ mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, mBatchedScanMinPollTime,
+ mBatchedScanIntervalIntent);
+ mBatchedScanMinPollTime = 0;
+ }
+ }
+
+ // return true if new/different
+ private boolean recordBatchedScanSettings(int responsibleUid, int csph, Bundle bundle) {
+ BatchedScanSettings settings = bundle.getParcelable(BATCHED_SETTING);
+ WorkSource responsibleWorkSource = bundle.getParcelable(BATCHED_WORKSOURCE);
+
+ if (DBG) {
+ log("set batched scan to " + settings + " for uid=" + responsibleUid +
+ ", worksource=" + responsibleWorkSource);
+ }
+ if (settings != null) {
+ if (settings.equals(mBatchedScanSettings)) return false;
+ } else {
+ if (mBatchedScanSettings == null) return false;
+ }
+ mBatchedScanSettings = settings;
+ if (responsibleWorkSource == null) responsibleWorkSource = new WorkSource(responsibleUid);
+ mBatchedScanWorkSource = responsibleWorkSource;
+ mBatchedScanCsph = csph;
+ return true;
+ }
+
+ private void stopBatchedScan() {
+ mAlarmManager.cancel(mBatchedScanIntervalIntent);
+ retrieveBatchedScanData();
+ mWifiNative.setBatchedScanSettings(null);
+ noteBatchedScanStop();
+ }
+
+ private void setNextBatchedAlarm(int scansExpected) {
+
+ if (mBatchedScanSettings == null || scansExpected < 1) return;
+
+ mBatchedScanMinPollTime = System.currentTimeMillis() +
+ mBatchedScanSettings.scanIntervalSec * 1000;
+
+ if (mBatchedScanSettings.maxScansPerBatch < scansExpected) {
+ scansExpected = mBatchedScanSettings.maxScansPerBatch;
+ }
+
+ int secToFull = mBatchedScanSettings.scanIntervalSec;
+ secToFull *= scansExpected;
+
+ int debugPeriod = SystemProperties.getInt("wifi.batchedScan.pollPeriod", 0);
+ if (debugPeriod > 0) secToFull = debugPeriod;
+
+ // set the alarm to do the next poll. We set it a little short as we'd rather
+ // wake up wearly than miss a scan due to buffer overflow
+ mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ + ((secToFull - (mBatchedScanSettings.scanIntervalSec / 2)) * 1000),
+ mBatchedScanIntervalIntent);
+ }
+
+ /**
+ * Start reading new scan data
+ * Data comes in as:
+ * "scancount=5\n"
+ * "nextcount=5\n"
+ * "apcount=3\n"
+ * "trunc\n" (optional)
+ * "bssid=...\n"
+ * "ssid=...\n"
+ * "freq=...\n" (in Mhz)
+ * "level=...\n"
+ * "dist=...\n" (in cm)
+ * "distsd=...\n" (standard deviation, in cm)
+ * "===="
+ * "bssid=...\n"
+ * etc
+ * "===="
+ * "bssid=...\n"
+ * etc
+ * "%%%%"
+ * "apcount=2\n"
+ * "bssid=...\n"
+ * etc
+ * "%%%%
+ * etc
+ * "----"
+ */
+ private final static boolean DEBUG_PARSE = false;
+ private void retrieveBatchedScanData() {
+ String rawData = mWifiNative.getBatchedScanResults();
+ if (DEBUG_PARSE) log("rawData = " + rawData);
+ mBatchedScanMinPollTime = 0;
+ if (rawData == null || rawData.equalsIgnoreCase("OK")) {
+ loge("Unexpected BatchedScanResults :" + rawData);
+ return;
+ }
+
+ int scanCount = 0;
+ final String END_OF_BATCHES = "----";
+ final String SCANCOUNT = "scancount=";
+ final String TRUNCATED = "trunc";
+ final String AGE = "age=";
+ final String DIST = "dist=";
+ final String DISTSD = "distSd=";
+
+ String splitData[] = rawData.split("\n");
+ int n = 0;
+ if (splitData[n].startsWith(SCANCOUNT)) {
+ try {
+ scanCount = Integer.parseInt(splitData[n++].substring(SCANCOUNT.length()));
+ } catch (NumberFormatException e) {
+ loge("scancount parseInt Exception from " + splitData[n]);
+ }
+ } else log("scancount not found");
+ if (scanCount == 0) {
+ loge("scanCount==0 - aborting");
+ return;
+ }
+
+ final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+ synchronized (mBatchedScanResults) {
+ mBatchedScanResults.clear();
+ BatchedScanResult batchedScanResult = new BatchedScanResult();
+
+ String bssid = null;
+ WifiSsid wifiSsid = null;
+ int level = 0;
+ int freq = 0;
+ int dist, distSd;
+ long tsf = 0;
+ dist = distSd = ScanResult.UNSPECIFIED;
+ final long now = SystemClock.elapsedRealtime();
+ final int bssidStrLen = BSSID_STR.length();
+
+ while (true) {
+ while (n < splitData.length) {
+ if (DEBUG_PARSE) logd("parsing " + splitData[n]);
+ if (splitData[n].equals(END_OF_BATCHES)) {
+ if (n+1 != splitData.length) {
+ loge("didn't consume " + (splitData.length-n));
+ }
+ if (mBatchedScanResults.size() > 0) {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ logd("retrieveBatchedScanResults X");
+ return;
+ }
+ if ((splitData[n].equals(END_STR)) || splitData[n].equals(DELIMITER_STR)) {
+ if (bssid != null) {
+ batchedScanResult.scanResults.add(new ScanResult(
+ wifiSsid, bssid, "", level, freq, tsf, dist, distSd));
+ wifiSsid = null;
+ bssid = null;
+ level = 0;
+ freq = 0;
+ tsf = 0;
+ dist = distSd = ScanResult.UNSPECIFIED;
+ }
+ if (splitData[n].equals(END_STR)) {
+ if (batchedScanResult.scanResults.size() != 0) {
+ mBatchedScanResults.add(batchedScanResult);
+ batchedScanResult = new BatchedScanResult();
+ } else {
+ logd("Found empty batch");
+ }
+ }
+ } else if (splitData[n].equals(TRUNCATED)) {
+ batchedScanResult.truncated = true;
+ } else if (splitData[n].startsWith(BSSID_STR)) {
+ bssid = new String(splitData[n].getBytes(), bssidStrLen,
+ splitData[n].length() - bssidStrLen);
+ } else if (splitData[n].startsWith(FREQ_STR)) {
+ try {
+ freq = Integer.parseInt(splitData[n].substring(FREQ_STR.length()));
+ } catch (NumberFormatException e) {
+ loge("Invalid freqency: " + splitData[n]);
+ freq = 0;
+ }
+ } else if (splitData[n].startsWith(AGE)) {
+ try {
+ tsf = now - Long.parseLong(splitData[n].substring(AGE.length()));
+ tsf *= 1000; // convert mS -> uS
+ } catch (NumberFormatException e) {
+ loge("Invalid timestamp: " + splitData[n]);
+ tsf = 0;
+ }
+ } else if (splitData[n].startsWith(SSID_STR)) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ splitData[n].substring(SSID_STR.length()));
+ } else if (splitData[n].startsWith(LEVEL_STR)) {
+ try {
+ level = Integer.parseInt(splitData[n].substring(LEVEL_STR.length()));
+ if (level > 0) level -= 256;
+ } catch (NumberFormatException e) {
+ loge("Invalid level: " + splitData[n]);
+ level = 0;
+ }
+ } else if (splitData[n].startsWith(DIST)) {
+ try {
+ dist = Integer.parseInt(splitData[n].substring(DIST.length()));
+ } catch (NumberFormatException e) {
+ loge("Invalid distance: " + splitData[n]);
+ dist = ScanResult.UNSPECIFIED;
+ }
+ } else if (splitData[n].startsWith(DISTSD)) {
+ try {
+ distSd = Integer.parseInt(splitData[n].substring(DISTSD.length()));
+ } catch (NumberFormatException e) {
+ loge("Invalid distanceSd: " + splitData[n]);
+ distSd = ScanResult.UNSPECIFIED;
+ }
+ } else {
+ loge("Unable to parse batched scan result line: " + splitData[n]);
+ }
+ n++;
+ }
+ rawData = mWifiNative.getBatchedScanResults();
+ if (DEBUG_PARSE) log("reading more data:\n" + rawData);
+ if (rawData == null) {
+ loge("Unexpected null BatchedScanResults");
+ return;
+ }
+ splitData = rawData.split("\n");
+ if (splitData.length == 0 || splitData[0].equals("ok")) {
+ loge("batch scan results just ended!");
+ if (mBatchedScanResults.size() > 0) {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ return;
+ }
+ n = 0;
+ }
+ }
+ }
+
+ // If workSource is not null, blame is given to it, otherwise blame is given to callingUid.
+ private void noteScanStart(int callingUid, WorkSource workSource) {
+ if (mScanWorkSource == null && (callingUid != UNKNOWN_SCAN_SOURCE || workSource != null)) {
+ mScanWorkSource = workSource != null ? workSource : new WorkSource(callingUid);
+ try {
+ mBatteryStats.noteWifiScanStartedFromSource(mScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ }
+ }
+ }
+
+ private void noteScanEnd() {
+ if (mScanWorkSource != null) {
+ try {
+ mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ } finally {
+ mScanWorkSource = null;
+ }
+ }
+ }
+
+ private void noteBatchedScanStart() {
+ // note the end of a previous scan set
+ if (mNotedBatchedScanWorkSource != null &&
+ (mNotedBatchedScanWorkSource.equals(mBatchedScanWorkSource) == false ||
+ mNotedBatchedScanCsph != mBatchedScanCsph)) {
+ try {
+ mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ } finally {
+ mNotedBatchedScanWorkSource = null;
+ mNotedBatchedScanCsph = 0;
+ }
+ }
+ // note the start of the new
+ try {
+ mBatteryStats.noteWifiBatchedScanStartedFromSource(mBatchedScanWorkSource,
+ mBatchedScanCsph);
+ mNotedBatchedScanWorkSource = mBatchedScanWorkSource;
+ mNotedBatchedScanCsph = mBatchedScanCsph;
+ } catch (RemoteException e) {
+ log(e.toString());
+ }
+ }
+
+ private void noteBatchedScanStop() {
+ if (mNotedBatchedScanWorkSource != null) {
+ try {
+ mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource);
+ } catch (RemoteException e) {
+ log(e.toString());
+ } finally {
+ mNotedBatchedScanWorkSource = null;
+ mNotedBatchedScanCsph = 0;
+ }
+ }
+ }
+
+ private void startScanNative(int type) {
+ mWifiNative.scan(type);
+ mScanResultIsPending = true;
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setSupplicantRunning(boolean enable) {
+ if (enable) {
+ sendMessage(CMD_START_SUPPLICANT);
+ } else {
+ sendMessage(CMD_STOP_SUPPLICANT);
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
+ if (enable) {
+ sendMessage(CMD_START_AP, wifiConfig);
+ } else {
+ sendMessage(CMD_STOP_AP);
+ }
+ }
+
+ public void setWifiApConfiguration(WifiConfiguration config) {
+ mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
+ }
+
+ public WifiConfiguration syncGetWifiApConfiguration() {
+ Message resultMsg = mWifiApConfigChannel.sendMessageSynchronously(CMD_REQUEST_AP_CONFIG);
+ WifiConfiguration ret = (WifiConfiguration) resultMsg.obj;
+ resultMsg.recycle();
+ return ret;
+ }
+
+ /**
+ * TODO: doc
+ */
+ public int syncGetWifiState() {
+ return mWifiState.get();
+ }
+
+ /**
+ * TODO: doc
+ */
+ public String syncGetWifiStateByName() {
+ switch (mWifiState.get()) {
+ case WIFI_STATE_DISABLING:
+ return "disabling";
+ case WIFI_STATE_DISABLED:
+ return "disabled";
+ case WIFI_STATE_ENABLING:
+ return "enabling";
+ case WIFI_STATE_ENABLED:
+ return "enabled";
+ case WIFI_STATE_UNKNOWN:
+ return "unknown state";
+ default:
+ return "[invalid state]";
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public int syncGetWifiApState() {
+ return mWifiApState.get();
+ }
+
+ /**
+ * TODO: doc
+ */
+ public String syncGetWifiApStateByName() {
+ switch (mWifiApState.get()) {
+ case WIFI_AP_STATE_DISABLING:
+ return "disabling";
+ case WIFI_AP_STATE_DISABLED:
+ return "disabled";
+ case WIFI_AP_STATE_ENABLING:
+ return "enabling";
+ case WIFI_AP_STATE_ENABLED:
+ return "enabled";
+ case WIFI_AP_STATE_FAILED:
+ return "failed";
+ default:
+ return "[invalid state]";
+ }
+ }
+
+ /**
+ * Get status information for the current connection, if any.
+ * @return a {@link WifiInfo} object containing information about the current connection
+ *
+ */
+ public WifiInfo syncRequestConnectionInfo() {
+ return mWifiInfo;
+ }
+
+ public DhcpResults syncGetDhcpResults() {
+ synchronized (mDhcpResultsLock) {
+ return new DhcpResults(mDhcpResults);
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setDriverStart(boolean enable) {
+ if (enable) {
+ sendMessage(CMD_START_DRIVER);
+ } else {
+ sendMessage(CMD_STOP_DRIVER);
+ }
+ }
+
+ /**
+ * TODO: doc
+ */
+ public void setOperationalMode(int mode) {
+ if (DBG) log("setting operational mode to " + String.valueOf(mode));
+ sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0);
+ }
+
+ /**
+ * TODO: doc
+ */
+ public List<ScanResult> syncGetScanResultsList() {
+ synchronized (mScanResultCache) {
+ List<ScanResult> scanList = new ArrayList<ScanResult>();
+ for(ScanResult result: mScanResults) {
+ scanList.add(new ScanResult(result));
+ }
+ return scanList;
+ }
+ }
+
+ /**
+ * Disconnect from Access Point
+ */
+ public void disconnectCommand() {
+ sendMessage(CMD_DISCONNECT);
+ }
+
+ /**
+ * Initiate a reconnection to AP
+ */
+ public void reconnectCommand() {
+ sendMessage(CMD_RECONNECT);
+ }
+
+ /**
+ * Initiate a re-association to AP
+ */
+ public void reassociateCommand() {
+ sendMessage(CMD_REASSOCIATE);
+ }
+
+ /**
+ * Reload networks and then reconnect; helps load correct data for TLS networks
+ */
+
+ public void reloadTlsNetworksAndReconnect() {
+ sendMessage(CMD_RELOAD_TLS_AND_RECONNECT);
+ }
+
+ /**
+ * Add a network synchronously
+ *
+ * @return network id of the new network
+ */
+ public int syncAddOrUpdateNetwork(AsyncChannel channel, WifiConfiguration config) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_ADD_OR_UPDATE_NETWORK, config);
+ int result = resultMsg.arg1;
+ resultMsg.recycle();
+ return result;
+ }
+
+ public List<WifiConfiguration> syncGetConfiguredNetworks(AsyncChannel channel) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_GET_CONFIGURED_NETWORKS);
+ List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Delete a network
+ *
+ * @param networkId id of the network to be removed
+ */
+ public boolean syncRemoveNetwork(AsyncChannel channel, int networkId) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_NETWORK, networkId);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Enable a network
+ *
+ * @param netId network id of the network
+ * @param disableOthers true, if all other networks have to be disabled
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ */
+ public boolean syncEnableNetwork(AsyncChannel channel, int netId, boolean disableOthers) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_ENABLE_NETWORK, netId,
+ disableOthers ? 1 : 0);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Disable a network
+ *
+ * @param netId network id of the network
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ */
+ public boolean syncDisableNetwork(AsyncChannel channel, int netId) {
+ Message resultMsg = channel.sendMessageSynchronously(WifiManager.DISABLE_NETWORK, netId);
+ boolean result = (resultMsg.arg1 != WifiManager.DISABLE_NETWORK_FAILED);
+ resultMsg.recycle();
+ return result;
+ }
+
+ /**
+ * Blacklist a BSSID. This will avoid the AP if there are
+ * alternate APs to connect
+ *
+ * @param bssid BSSID of the network
+ */
+ public void addToBlacklist(String bssid) {
+ sendMessage(CMD_BLACKLIST_NETWORK, bssid);
+ }
+
+ /**
+ * Clear the blacklist list
+ *
+ */
+ public void clearBlacklist() {
+ sendMessage(CMD_CLEAR_BLACKLIST);
+ }
+
+ public void enableRssiPolling(boolean enabled) {
+ sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
+ }
+
+ public void enableBackgroundScanCommand(boolean enabled) {
+ sendMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0);
+ }
+
+ public void enableAllNetworks() {
+ sendMessage(CMD_ENABLE_ALL_NETWORKS);
+ }
+
+ /**
+ * Start filtering Multicast v4 packets
+ */
+ public void startFilteringMulticastV4Packets() {
+ mFilteringMulticastV4Packets.set(true);
+ sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0);
+ }
+
+ /**
+ * Stop filtering Multicast v4 packets
+ */
+ public void stopFilteringMulticastV4Packets() {
+ mFilteringMulticastV4Packets.set(false);
+ sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0);
+ }
+
+ /**
+ * Start filtering Multicast v4 packets
+ */
+ public void startFilteringMulticastV6Packets() {
+ sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0);
+ }
+
+ /**
+ * Stop filtering Multicast v4 packets
+ */
+ public void stopFilteringMulticastV6Packets() {
+ sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0);
+ }
+
+ /**
+ * Set high performance mode of operation.
+ * Enabling would set active power mode and disable suspend optimizations;
+ * disabling would set auto power mode and enable suspend optimizations
+ * @param enable true if enable, false otherwise
+ */
+ public void setHighPerfModeEnabled(boolean enable) {
+ sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0);
+ }
+
+ /**
+ * Set the country code
+ * @param countryCode following ISO 3166 format
+ * @param persist {@code true} if the setting should be remembered.
+ */
+ public void setCountryCode(String countryCode, boolean persist) {
+ if (persist) {
+ mPersistedCountryCode = countryCode;
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE,
+ countryCode);
+ }
+ sendMessage(CMD_SET_COUNTRY_CODE, countryCode);
+ mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, countryCode);
+ }
+
+ /**
+ * Set the operational frequency band
+ * @param band
+ * @param persist {@code true} if the setting should be remembered.
+ */
+ public void setFrequencyBand(int band, boolean persist) {
+ if (persist) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_FREQUENCY_BAND,
+ band);
+ }
+ sendMessage(CMD_SET_FREQUENCY_BAND, band, 0);
+ }
+
+ /**
+ * Enable TDLS for a specific MAC address
+ */
+ public void enableTdls(String remoteMacAddress, boolean enable) {
+ int enabler = enable ? 1 : 0;
+ sendMessage(CMD_ENABLE_TDLS, enabler, 0, remoteMacAddress);
+ }
+
+ /**
+ * Returns the operational frequency band
+ */
+ public int getFrequencyBand() {
+ return mFrequencyBand.get();
+ }
+
+ /**
+ * Returns the wifi configuration file
+ */
+ public String getConfigFile() {
+ return mWifiConfigStore.getConfigFile();
+ }
+
+ /**
+ * Send a message indicating bluetooth adapter connection state changed
+ */
+ public void sendBluetoothAdapterStateChange(int state) {
+ sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0);
+ }
+
+ /**
+ * Save configuration on supplicant
+ *
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
+ *
+ * TODO: deprecate this
+ */
+ public boolean syncSaveConfig(AsyncChannel channel) {
+ Message resultMsg = channel.sendMessageSynchronously(CMD_SAVE_CONFIG);
+ boolean result = (resultMsg.arg1 != FAILURE);
+ resultMsg.recycle();
+ return result;
+ }
+
+ public void updateBatteryWorkSource(WorkSource newSource) {
+ synchronized (mRunningWifiUids) {
+ try {
+ if (newSource != null) {
+ mRunningWifiUids.set(newSource);
+ }
+ if (mIsRunning) {
+ if (mReportedRunning) {
+ // If the work source has changed since last time, need
+ // to remove old work from battery stats.
+ if (mLastRunningWifiUids.diff(mRunningWifiUids)) {
+ mBatteryStats.noteWifiRunningChanged(mLastRunningWifiUids,
+ mRunningWifiUids);
+ mLastRunningWifiUids.set(mRunningWifiUids);
+ }
+ } else {
+ // Now being started, report it.
+ mBatteryStats.noteWifiRunning(mRunningWifiUids);
+ mLastRunningWifiUids.set(mRunningWifiUids);
+ mReportedRunning = true;
+ }
+ } else {
+ if (mReportedRunning) {
+ // Last reported we were running, time to stop.
+ mBatteryStats.noteWifiStopped(mLastRunningWifiUids);
+ mLastRunningWifiUids.clear();
+ mReportedRunning = false;
+ }
+ }
+ mWakeLock.setWorkSource(newSource);
+ } catch (RemoteException ignore) {
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ mSupplicantStateTracker.dump(fd, pw, args);
+ pw.println("mLinkProperties " + mLinkProperties);
+ pw.println("mWifiInfo " + mWifiInfo);
+ pw.println("mDhcpResults " + mDhcpResults);
+ pw.println("mNetworkInfo " + mNetworkInfo);
+ pw.println("mLastSignalLevel " + mLastSignalLevel);
+ pw.println("mLastBssid " + mLastBssid);
+ pw.println("mLastNetworkId " + mLastNetworkId);
+ pw.println("mReconnectCount " + mReconnectCount);
+ pw.println("mOperationalMode " + mOperationalMode);
+ pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
+ pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+ pw.println("Supplicant status " + mWifiNative.status());
+ pw.println("mEnableBackgroundScan " + mEnableBackgroundScan);
+ pw.println();
+ mWifiConfigStore.dump(fd, pw, args);
+ }
+
+ /*********************************************************
+ * Internal private functions
+ ********************************************************/
+
+ private void handleScreenStateChanged(boolean screenOn) {
+ if (DBG) log("handleScreenStateChanged: " + screenOn);
+ enableRssiPolling(screenOn);
+ if (mBackgroundScanSupported) {
+ enableBackgroundScanCommand(screenOn == false);
+ }
+
+ if (screenOn) enableAllNetworks();
+ if (mUserWantsSuspendOpt.get()) {
+ if (screenOn) {
+ sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0);
+ } else {
+ //Allow 2s for suspend optimizations to be set
+ mSuspendWakeLock.acquire(2000);
+ sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0);
+ }
+ }
+ mScreenBroadcastReceived.set(true);
+ }
+
+ private void checkAndSetConnectivityInstance() {
+ if (mCm == null) {
+ mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+ }
+
+ private boolean startTethering(ArrayList<String> available) {
+
+ boolean wifiAvailable = false;
+
+ checkAndSetConnectivityInstance();
+
+ String[] wifiRegexs = mCm.getTetherableWifiRegexs();
+
+ for (String intf : available) {
+ for (String regex : wifiRegexs) {
+ if (intf.matches(regex)) {
+
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNwService.getInterfaceConfig(intf);
+ if (ifcg != null) {
+ /* IP/netmask: 192.168.43.1/255.255.255.0 */
+ ifcg.setLinkAddress(new LinkAddress(
+ NetworkUtils.numericToInetAddress("192.168.43.1"), 24));
+ ifcg.setInterfaceUp();
+
+ mNwService.setInterfaceConfig(intf, ifcg);
+ }
+ } catch (Exception e) {
+ loge("Error configuring interface " + intf + ", :" + e);
+ return false;
+ }
+
+ if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ loge("Error tethering on " + intf);
+ return false;
+ }
+ mTetherInterfaceName = intf;
+ return true;
+ }
+ }
+ }
+ // We found no interfaces to tether
+ return false;
+ }
+
+ private void stopTethering() {
+
+ checkAndSetConnectivityInstance();
+
+ /* Clear the interface config to allow dhcp correctly configure new
+ ip settings */
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNwService.getInterfaceConfig(mTetherInterfaceName);
+ if (ifcg != null) {
+ ifcg.setLinkAddress(
+ new LinkAddress(NetworkUtils.numericToInetAddress("0.0.0.0"), 0));
+ mNwService.setInterfaceConfig(mTetherInterfaceName, ifcg);
+ }
+ } catch (Exception e) {
+ loge("Error resetting interface " + mTetherInterfaceName + ", :" + e);
+ }
+
+ if (mCm.untether(mTetherInterfaceName) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ loge("Untether initiate failed!");
+ }
+ }
+
+ private boolean isWifiTethered(ArrayList<String> active) {
+
+ checkAndSetConnectivityInstance();
+
+ String[] wifiRegexs = mCm.getTetherableWifiRegexs();
+ for (String intf : active) {
+ for (String regex : wifiRegexs) {
+ if (intf.matches(regex)) {
+ return true;
+ }
+ }
+ }
+ // We found no interfaces that are tethered
+ return false;
+ }
+
+ /**
+ * Set the country code from the system setting value, if any.
+ */
+ private void setCountryCode() {
+ String countryCode = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE);
+ if (countryCode != null && !countryCode.isEmpty()) {
+ setCountryCode(countryCode, false);
+ } else {
+ //use driver default
+ }
+ }
+
+ /**
+ * Set the frequency band from the system setting value, if any.
+ */
+ private void setFrequencyBand() {
+ int band = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_FREQUENCY_BAND, WifiManager.WIFI_FREQUENCY_BAND_AUTO);
+ setFrequencyBand(band, false);
+ }
+
+ private void setSuspendOptimizationsNative(int reason, boolean enabled) {
+ if (DBG) log("setSuspendOptimizationsNative: " + reason + " " + enabled);
+ if (enabled) {
+ mSuspendOptNeedsDisabled &= ~reason;
+ /* None of dhcp, screen or highperf need it disabled and user wants it enabled */
+ if (mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get()) {
+ mWifiNative.setSuspendOptimizations(true);
+ }
+ } else {
+ mSuspendOptNeedsDisabled |= reason;
+ mWifiNative.setSuspendOptimizations(false);
+ }
+ }
+
+ private void setSuspendOptimizations(int reason, boolean enabled) {
+ if (DBG) log("setSuspendOptimizations: " + reason + " " + enabled);
+ if (enabled) {
+ mSuspendOptNeedsDisabled &= ~reason;
+ } else {
+ mSuspendOptNeedsDisabled |= reason;
+ }
+ if (DBG) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+ }
+
+ private void setWifiState(int wifiState) {
+ final int previousWifiState = mWifiState.get();
+
+ try {
+ if (wifiState == WIFI_STATE_ENABLED) {
+ mBatteryStats.noteWifiOn();
+ } else if (wifiState == WIFI_STATE_DISABLED) {
+ mBatteryStats.noteWifiOff();
+ }
+ } catch (RemoteException e) {
+ loge("Failed to note battery stats in wifi");
+ }
+
+ mWifiState.set(wifiState);
+
+ if (DBG) log("setWifiState: " + syncGetWifiStateByName());
+
+ final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
+ intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void setWifiApState(int wifiApState) {
+ final int previousWifiApState = mWifiApState.get();
+
+ try {
+ if (wifiApState == WIFI_AP_STATE_ENABLED) {
+ mBatteryStats.noteWifiOn();
+ } else if (wifiApState == WIFI_AP_STATE_DISABLED) {
+ mBatteryStats.noteWifiOff();
+ }
+ } catch (RemoteException e) {
+ loge("Failed to note battery stats in wifi");
+ }
+
+ // Update state
+ mWifiApState.set(wifiApState);
+
+ if (DBG) log("setWifiApState: " + syncGetWifiApStateByName());
+
+ final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
+ intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private static final String ID_STR = "id=";
+ private static final String BSSID_STR = "bssid=";
+ private static final String FREQ_STR = "freq=";
+ private static final String LEVEL_STR = "level=";
+ private static final String TSF_STR = "tsf=";
+ private static final String FLAGS_STR = "flags=";
+ private static final String SSID_STR = "ssid=";
+ private static final String DELIMITER_STR = "====";
+ private static final String END_STR = "####";
+
+ /**
+ * Format:
+ *
+ * id=1
+ * bssid=68:7f:76:d7:1a:6e
+ * freq=2412
+ * level=-44
+ * tsf=1344626243700342
+ * flags=[WPA2-PSK-CCMP][WPS][ESS]
+ * ssid=zfdy
+ * ====
+ * id=2
+ * bssid=68:5f:74:d7:1a:6f
+ * freq=5180
+ * level=-73
+ * tsf=1344626243700373
+ * flags=[WPA2-PSK-CCMP][WPS][ESS]
+ * ssid=zuby
+ * ====
+ */
+ private void setScanResults() {
+ String bssid = "";
+ int level = 0;
+ int freq = 0;
+ long tsf = 0;
+ String flags = "";
+ WifiSsid wifiSsid = null;
+ String scanResults;
+ String tmpResults;
+ StringBuffer scanResultsBuf = new StringBuffer();
+ int sid = 0;
+
+ while (true) {
+ tmpResults = mWifiNative.scanResults(sid);
+ if (TextUtils.isEmpty(tmpResults)) break;
+ scanResultsBuf.append(tmpResults);
+ scanResultsBuf.append("\n");
+ String[] lines = tmpResults.split("\n");
+ sid = -1;
+ for (int i=lines.length - 1; i >= 0; i--) {
+ if (lines[i].startsWith(END_STR)) {
+ break;
+ } else if (lines[i].startsWith(ID_STR)) {
+ try {
+ sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;
+ } catch (NumberFormatException e) {
+ // Nothing to do
+ }
+ break;
+ }
+ }
+ if (sid == -1) break;
+ }
+
+ scanResults = scanResultsBuf.toString();
+ if (TextUtils.isEmpty(scanResults)) {
+ return;
+ }
+
+ // note that all these splits and substrings keep references to the original
+ // huge string buffer while the amount we really want is generally pretty small
+ // so make copies instead (one example b/11087956 wasted 400k of heap here).
+ synchronized(mScanResultCache) {
+ mScanResults = new ArrayList<ScanResult>();
+ String[] lines = scanResults.split("\n");
+ final int bssidStrLen = BSSID_STR.length();
+ final int flagLen = FLAGS_STR.length();
+
+ for (String line : lines) {
+ if (line.startsWith(BSSID_STR)) {
+ bssid = new String(line.getBytes(), bssidStrLen, line.length() - bssidStrLen);
+ } else if (line.startsWith(FREQ_STR)) {
+ try {
+ freq = Integer.parseInt(line.substring(FREQ_STR.length()));
+ } catch (NumberFormatException e) {
+ freq = 0;
+ }
+ } else if (line.startsWith(LEVEL_STR)) {
+ try {
+ level = Integer.parseInt(line.substring(LEVEL_STR.length()));
+ /* some implementations avoid negative values by adding 256
+ * so we need to adjust for that here.
+ */
+ if (level > 0) level -= 256;
+ } catch(NumberFormatException e) {
+ level = 0;
+ }
+ } else if (line.startsWith(TSF_STR)) {
+ try {
+ tsf = Long.parseLong(line.substring(TSF_STR.length()));
+ } catch (NumberFormatException e) {
+ tsf = 0;
+ }
+ } else if (line.startsWith(FLAGS_STR)) {
+ flags = new String(line.getBytes(), flagLen, line.length() - flagLen);
+ } else if (line.startsWith(SSID_STR)) {
+ wifiSsid = WifiSsid.createFromAsciiEncoded(
+ line.substring(SSID_STR.length()));
+ } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {
+ if (bssid != null) {
+ String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+ String key = bssid + ssid;
+ ScanResult scanResult = mScanResultCache.get(key);
+ if (scanResult != null) {
+ scanResult.level = level;
+ scanResult.wifiSsid = wifiSsid;
+ // Keep existing API
+ scanResult.SSID = (wifiSsid != null) ? wifiSsid.toString() :
+ WifiSsid.NONE;
+ scanResult.capabilities = flags;
+ scanResult.frequency = freq;
+ scanResult.timestamp = tsf;
+ } else {
+ scanResult =
+ new ScanResult(
+ wifiSsid, bssid, flags, level, freq, tsf);
+ mScanResultCache.put(key, scanResult);
+ }
+ mScanResults.add(scanResult);
+ }
+ bssid = null;
+ level = 0;
+ freq = 0;
+ tsf = 0;
+ flags = "";
+ wifiSsid = null;
+ }
+ }
+ }
+ }
+
+ /*
+ * Fetch RSSI and linkspeed on current connection
+ */
+ private void fetchRssiAndLinkSpeedNative() {
+ int newRssi = -1;
+ int newLinkSpeed = -1;
+
+ String signalPoll = mWifiNative.signalPoll();
+
+ if (signalPoll != null) {
+ String[] lines = signalPoll.split("\n");
+ for (String line : lines) {
+ String[] prop = line.split("=");
+ if (prop.length < 2) continue;
+ try {
+ if (prop[0].equals("RSSI")) {
+ newRssi = Integer.parseInt(prop[1]);
+ } else if (prop[0].equals("LINKSPEED")) {
+ newLinkSpeed = Integer.parseInt(prop[1]);
+ }
+ } catch (NumberFormatException e) {
+ //Ignore, defaults on rssi and linkspeed are assigned
+ }
+ }
+ }
+
+ if (newRssi != -1 && MIN_RSSI < newRssi && newRssi < MAX_RSSI) { // screen out invalid values
+ /* some implementations avoid negative values by adding 256
+ * so we need to adjust for that here.
+ */
+ if (newRssi > 0) newRssi -= 256;
+ mWifiInfo.setRssi(newRssi);
+ /*
+ * Rather then sending the raw RSSI out every time it
+ * changes, we precalculate the signal level that would
+ * be displayed in the status bar, and only send the
+ * broadcast if that much more coarse-grained number
+ * changes. This cuts down greatly on the number of
+ * broadcasts, at the cost of not informing others
+ * interested in RSSI of all the changes in signal
+ * level.
+ */
+ int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, WifiManager.RSSI_LEVELS);
+ if (newSignalLevel != mLastSignalLevel) {
+ sendRssiChangeBroadcast(newRssi);
+ }
+ mLastSignalLevel = newSignalLevel;
+ } else {
+ mWifiInfo.setRssi(MIN_RSSI);
+ }
+
+ if (newLinkSpeed != -1) {
+ mWifiInfo.setLinkSpeed(newLinkSpeed);
+ }
+ }
+
+ /*
+ * Fetch TX packet counters on current connection
+ */
+ private void fetchPktcntNative(RssiPacketCountInfo info) {
+ String pktcntPoll = mWifiNative.pktcntPoll();
+
+ if (pktcntPoll != null) {
+ String[] lines = pktcntPoll.split("\n");
+ for (String line : lines) {
+ String[] prop = line.split("=");
+ if (prop.length < 2) continue;
+ try {
+ if (prop[0].equals("TXGOOD")) {
+ info.txgood = Integer.parseInt(prop[1]);
+ } else if (prop[0].equals("TXBAD")) {
+ info.txbad = Integer.parseInt(prop[1]);
+ }
+ } catch (NumberFormatException e) {
+ //Ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates mLinkProperties by merging information from various sources.
+ *
+ * This is needed because the information in mLinkProperties comes from multiple sources (DHCP,
+ * netlink, static configuration, ...). When one of these sources of information has updated
+ * link properties, we can't just assign them to mLinkProperties or we'd lose track of the
+ * information that came from other sources. Instead, when one of those sources has new
+ * information, we update the object that tracks the information from that source and then
+ * call this method to apply the change to mLinkProperties.
+ *
+ * The information in mLinkProperties is currently obtained as follows:
+ * - Interface name: set in the constructor.
+ * - IPv4 and IPv6 addresses: netlink, via mInterfaceObserver.
+ * - IPv4 routes, DNS servers, and domains: DHCP.
+ * - HTTP proxy: the wifi config store.
+ */
+ private void updateLinkProperties() {
+ LinkProperties newLp = new LinkProperties();
+
+ // Interface name and proxy are locally configured.
+ newLp.setInterfaceName(mInterfaceName);
+ newLp.setHttpProxy(mWifiConfigStore.getProxyProperties(mLastNetworkId));
+
+ // IPv4 and IPv6 addresses come from netlink.
+ newLp.setLinkAddresses(mNetlinkLinkProperties.getLinkAddresses());
+
+ // For now, routing and DNS only come from DHCP or static configuration. In the future,
+ // we'll need to merge IPv6 DNS servers and domains coming from netlink.
+ synchronized (mDhcpResultsLock) {
+ // Even when we're using static configuration, we don't need to look at the config
+ // store, because static IP configuration also populates mDhcpResults.
+ if ((mDhcpResults != null) && (mDhcpResults.linkProperties != null)) {
+ LinkProperties lp = mDhcpResults.linkProperties;
+ for (RouteInfo route: lp.getRoutes()) {
+ newLp.addRoute(route);
+ }
+ for (InetAddress dns: lp.getDnses()) {
+ newLp.addDns(dns);
+ }
+ newLp.setDomains(lp.getDomains());
+ }
+ }
+
+ // If anything has changed, and we're already connected, send out a notification.
+ // If we're still connecting, apps will be notified when we connect.
+ if (!newLp.equals(mLinkProperties)) {
+ if (DBG) {
+ log("Link configuration changed for netId: " + mLastNetworkId
+ + " old: " + mLinkProperties + "new: " + newLp);
+ }
+ mLinkProperties = newLp;
+ if (getNetworkDetailedState() == DetailedState.CONNECTED) {
+ sendLinkConfigurationChangedBroadcast();
+ }
+ }
+ }
+
+ /**
+ * Clears all our link properties.
+ */
+ private void clearLinkProperties() {
+ // If the network used DHCP, clear the LinkProperties we stored in the config store.
+ if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
+ mWifiConfigStore.clearLinkProperties(mLastNetworkId);
+ }
+
+ // Clear the link properties obtained from DHCP and netlink.
+ synchronized(mDhcpResultsLock) {
+ if (mDhcpResults != null && mDhcpResults.linkProperties != null) {
+ mDhcpResults.linkProperties.clear();
+ }
+ }
+ mNetlinkLinkProperties.clear();
+
+ // Now clear the merged link properties.
+ mLinkProperties.clear();
+ }
+
+ private int getMaxDhcpRetries() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
+ DEFAULT_MAX_DHCP_RETRIES);
+ }
+
+ private void sendScanResultsAvailableBroadcast() {
+ noteScanEnd();
+ Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendRssiChangeBroadcast(final int newRssi) {
+ Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendNetworkStateChangeBroadcast(String bssid) {
+ Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
+ intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties (mLinkProperties));
+ if (bssid != null)
+ intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
+ if (mNetworkInfo.getDetailedState() == DetailedState.VERIFYING_POOR_LINK ||
+ mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
+ intent.putExtra(WifiManager.EXTRA_WIFI_INFO, new WifiInfo(mWifiInfo));
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendLinkConfigurationChangedBroadcast() {
+ Intent intent = new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties(mLinkProperties));
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
+ Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ /**
+ * Record the detailed state of a network.
+ * @param state the new {@code DetailedState}
+ */
+ private void setNetworkDetailedState(NetworkInfo.DetailedState state) {
+ if (DBG) {
+ log("setDetailed state, old ="
+ + mNetworkInfo.getDetailedState() + " and new state=" + state);
+ }
+
+ if (state != mNetworkInfo.getDetailedState()) {
+ mNetworkInfo.setDetailedState(state, null, mWifiInfo.getSSID());
+ }
+ }
+
+ private DetailedState getNetworkDetailedState() {
+ return mNetworkInfo.getDetailedState();
+ }
+
+
+ private SupplicantState handleSupplicantStateChange(Message message) {
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ // Supplicant state change
+ // [31-13] Reserved for future use
+ // [8 - 0] Supplicant state (as defined in SupplicantState.java)
+ // 50023 supplicant_state_changed (custom|1|5)
+ mWifiInfo.setSupplicantState(state);
+ // Network id is only valid when we start connecting
+ if (SupplicantState.isConnecting(state)) {
+ mWifiInfo.setNetworkId(stateChangeResult.networkId);
+ } else {
+ mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+ }
+
+ mWifiInfo.setBSSID(stateChangeResult.BSSID);
+ mWifiInfo.setSSID(stateChangeResult.wifiSsid);
+
+ mSupplicantStateTracker.sendMessage(Message.obtain(message));
+
+ return state;
+ }
+
+ /**
+ * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
+ * using the interface, stopping DHCP & disabling interface
+ */
+ private void handleNetworkDisconnect() {
+ if (DBG) log("Stopping DHCP and clearing IP");
+
+ stopDhcp();
+
+ try {
+ mNwService.clearInterfaceAddresses(mInterfaceName);
+ mNwService.disableIpv6(mInterfaceName);
+ } catch (Exception e) {
+ loge("Failed to clear addresses or disable ipv6" + e);
+ }
+
+ /* Reset data structures */
+ mWifiInfo.setInetAddress(null);
+ mWifiInfo.setBSSID(null);
+ mWifiInfo.setSSID(null);
+ mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+ mWifiInfo.setRssi(MIN_RSSI);
+ mWifiInfo.setLinkSpeed(-1);
+ mWifiInfo.setMeteredHint(false);
+
+ setNetworkDetailedState(DetailedState.DISCONNECTED);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
+
+ /* Clear network properties */
+ clearLinkProperties();
+
+ /* send event to CM & network change broadcast */
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
+ mLastBssid= null;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ }
+
+ private void handleSupplicantConnectionLoss() {
+ /* Socket connection can be lost when we do a graceful shutdown
+ * or when the driver is hung. Ensure supplicant is stopped here.
+ */
+ mWifiMonitor.killSupplicant(mP2pSupported);
+ sendSupplicantConnectionChangedBroadcast(false);
+ setWifiState(WIFI_STATE_DISABLED);
+ }
+
+ void handlePreDhcpSetup() {
+ mDhcpActive = true;
+ if (!mBluetoothConnectionActive) {
+ /*
+ * There are problems setting the Wi-Fi driver's power
+ * mode to active when bluetooth coexistence mode is
+ * enabled or sense.
+ * <p>
+ * We set Wi-Fi to active mode when
+ * obtaining an IP address because we've found
+ * compatibility issues with some routers with low power
+ * mode.
+ * <p>
+ * In order for this active power mode to properly be set,
+ * we disable coexistence mode until we're done with
+ * obtaining an IP address. One exception is if we
+ * are currently connected to a headset, since disabling
+ * coexistence would interrupt that connection.
+ */
+ // Disable the coexistence mode
+ mWifiNative.setBluetoothCoexistenceMode(
+ mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+ }
+
+ /* Disable power save and suspend optimizations during DHCP */
+ // Note: The order here is important for now. Brcm driver changes
+ // power settings when we control suspend mode optimizations.
+ // TODO: Remove this comment when the driver is fixed.
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
+ mWifiNative.setPowerSave(false);
+
+ stopBatchedScan();
+
+ /* P2p discovery breaks dhcp, shut it down in order to get through this */
+ Message msg = new Message();
+ msg.what = WifiP2pService.BLOCK_DISCOVERY;
+ msg.arg1 = WifiP2pService.ENABLED;
+ msg.arg2 = DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE;
+ msg.obj = mDhcpStateMachine;
+ mWifiP2pChannel.sendMessage(msg);
+ }
+
+
+ void startDhcp() {
+ if (mDhcpStateMachine == null) {
+ mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
+ mContext, WifiStateMachine.this, mInterfaceName);
+
+ }
+ mDhcpStateMachine.registerForPreDhcpNotification();
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
+ }
+
+ void stopDhcp() {
+ if (mDhcpStateMachine != null) {
+ /* In case we were in middle of DHCP operation restore back powermode */
+ handlePostDhcpSetup();
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
+ }
+ }
+
+ void handlePostDhcpSetup() {
+ /* Restore power save and suspend optimizations */
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
+ mWifiNative.setPowerSave(true);
+
+ mWifiP2pChannel.sendMessage(WifiP2pService.BLOCK_DISCOVERY, WifiP2pService.DISABLED);
+
+ // Set the coexistence mode back to its default value
+ mWifiNative.setBluetoothCoexistenceMode(
+ mWifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+
+ mDhcpActive = false;
+
+ startBatchedScan();
+ }
+
+ private void handleSuccessfulIpConfiguration(DhcpResults dhcpResults) {
+ mLastSignalLevel = -1; // force update of signal strength
+ mReconnectCount = 0; //Reset IP failure tracking
+ synchronized (mDhcpResultsLock) {
+ mDhcpResults = dhcpResults;
+ }
+ LinkProperties linkProperties = dhcpResults.linkProperties;
+ mWifiConfigStore.setLinkProperties(mLastNetworkId, new LinkProperties(linkProperties));
+ InetAddress addr = null;
+ Iterator<InetAddress> addrs = linkProperties.getAddresses().iterator();
+ if (addrs.hasNext()) {
+ addr = addrs.next();
+ }
+ mWifiInfo.setInetAddress(addr);
+ mWifiInfo.setMeteredHint(dhcpResults.hasMeteredHint());
+ updateLinkProperties();
+ }
+
+ private void handleFailedIpConfiguration() {
+ loge("IP configuration failed");
+
+ mWifiInfo.setInetAddress(null);
+ mWifiInfo.setMeteredHint(false);
+ /**
+ * If we've exceeded the maximum number of retries for DHCP
+ * to a given network, disable the network
+ */
+ int maxRetries = getMaxDhcpRetries();
+ // maxRetries == 0 means keep trying forever
+ if (maxRetries > 0 && ++mReconnectCount > maxRetries) {
+ loge("Failed " +
+ mReconnectCount + " times, Disabling " + mLastNetworkId);
+ mWifiConfigStore.disableNetwork(mLastNetworkId,
+ WifiConfiguration.DISABLED_DHCP_FAILURE);
+ mReconnectCount = 0;
+ }
+
+ /* DHCP times out after about 30 seconds, we do a
+ * disconnect and an immediate reconnect to try again
+ */
+ mWifiNative.disconnect();
+ mWifiNative.reconnect();
+ }
+
+ /* Current design is to not set the config on a running hostapd but instead
+ * stop and start tethering when user changes config on a running access point
+ *
+ * TODO: Add control channel setup through hostapd that allows changing config
+ * on a running daemon
+ */
+ private void startSoftApWithConfig(final WifiConfiguration config) {
+ // start hostapd on a seperate thread
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ mNwService.startAccessPoint(config, mInterfaceName);
+ } catch (Exception e) {
+ loge("Exception in softap start " + e);
+ try {
+ mNwService.stopAccessPoint(mInterfaceName);
+ mNwService.startAccessPoint(config, mInterfaceName);
+ } catch (Exception e1) {
+ loge("Exception in softap re-start " + e1);
+ sendMessage(CMD_START_AP_FAILURE);
+ return;
+ }
+ }
+ if (DBG) log("Soft AP start successful");
+ sendMessage(CMD_START_AP_SUCCESS);
+ }
+ }).start();
+ }
+
+ /********************************************************
+ * HSM states
+ *******************************************************/
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mWifiP2pChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ } else {
+ loge("WifiP2pService connection failure, error=" + message.arg1);
+ }
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ loge("WifiP2pService channel lost, message.arg1 =" + message.arg1);
+ //TODO: Re-establish connection to state machine after a delay
+ //mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger());
+ break;
+ case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
+ mBluetoothConnectionActive = (message.arg1 !=
+ BluetoothAdapter.STATE_DISCONNECTED);
+ break;
+ /* Synchronous call returns */
+ case CMD_PING_SUPPLICANT:
+ case CMD_ENABLE_NETWORK:
+ case CMD_ADD_OR_UPDATE_NETWORK:
+ case CMD_REMOVE_NETWORK:
+ case CMD_SAVE_CONFIG:
+ replyToMessage(message, message.what, FAILURE);
+ break;
+ case CMD_GET_CONFIGURED_NETWORKS:
+ replyToMessage(message, message.what, (List<WifiConfiguration>) null);
+ break;
+ case CMD_ENABLE_RSSI_POLL:
+ mEnableRssiPolling = (message.arg1 == 1);
+ break;
+ case CMD_ENABLE_BACKGROUND_SCAN:
+ mEnableBackgroundScan = (message.arg1 == 1);
+ break;
+ case CMD_SET_HIGH_PERF_MODE:
+ if (message.arg1 == 1) {
+ setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, false);
+ } else {
+ setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
+ }
+ break;
+ case CMD_BOOT_COMPLETED:
+ String countryCode = mPersistedCountryCode;
+ if (TextUtils.isEmpty(countryCode) == false) {
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE,
+ countryCode);
+ // it may be that the state transition that should send this info
+ // to the driver happened between mPersistedCountryCode getting set
+ // and now, so simply persisting it here would mean we have sent
+ // nothing to the driver. Send the cmd so it might be set now.
+ sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode);
+ }
+ break;
+ case CMD_SET_BATCHED_SCAN:
+ recordBatchedScanSettings(message.arg1, message.arg2, (Bundle)message.obj);
+ break;
+ case CMD_POLL_BATCHED_SCAN:
+ handleBatchedScanPollRequest();
+ break;
+ case CMD_START_NEXT_BATCHED_SCAN:
+ startNextBatchedScan();
+ break;
+ /* Discard */
+ case CMD_START_SCAN:
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT_FAILED:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_DELAYED_STOP_DRIVER:
+ case CMD_DRIVER_START_TIMED_OUT:
+ case CMD_START_AP:
+ case CMD_START_AP_SUCCESS:
+ case CMD_START_AP_FAILURE:
+ case CMD_STOP_AP:
+ case CMD_TETHER_STATE_CHANGE:
+ case CMD_TETHER_NOTIFICATION_TIMED_OUT:
+ case CMD_DISCONNECT:
+ case CMD_RECONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RELOAD_TLS_AND_RECONNECT:
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ case CMD_BLACKLIST_NETWORK:
+ case CMD_CLEAR_BLACKLIST:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_RSSI_POLL:
+ case CMD_ENABLE_ALL_NETWORKS:
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ /* Handled by WifiApConfigStore */
+ case CMD_SET_AP_CONFIG:
+ case CMD_SET_AP_CONFIG_COMPLETED:
+ case CMD_REQUEST_AP_CONFIG:
+ case CMD_RESPONSE_AP_CONFIG:
+ case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+ case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+ case CMD_NO_NETWORKS_PERIODIC_SCAN:
+ case CMD_DISABLE_P2P_RSP:
+ break;
+ case DhcpStateMachine.CMD_ON_QUIT:
+ mDhcpStateMachine = null;
+ break;
+ case CMD_SET_SUSPEND_OPT_ENABLED:
+ if (message.arg1 == 1) {
+ mSuspendWakeLock.release();
+ setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
+ } else {
+ setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
+ }
+ break;
+ case WifiMonitor.DRIVER_HUNG_EVENT:
+ setSupplicantRunning(false);
+ setSupplicantRunning(true);
+ break;
+ case WifiManager.CONNECT_NETWORK:
+ replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.FORGET_NETWORK:
+ replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.SAVE_NETWORK:
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.START_WPS:
+ replyToMessage(message, WifiManager.WPS_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.CANCEL_WPS:
+ replyToMessage(message, WifiManager.CANCEL_WPS_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.DISABLE_NETWORK:
+ replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiManager.RSSI_PKTCNT_FETCH:
+ replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_FAILED,
+ WifiManager.BUSY);
+ break;
+ case WifiP2pService.P2P_CONNECTION_CHANGED:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ mP2pConnected.set(info.isConnected());
+ break;
+ case WifiP2pService.DISCONNECT_WIFI_REQUEST:
+ mTemporarilyDisconnectWifi = (message.arg1 == 1);
+ replyToMessage(message, WifiP2pService.DISCONNECT_WIFI_RESPONSE);
+ break;
+ case CMD_IP_ADDRESS_UPDATED:
+ // addLinkAddress is a no-op if called more than once with the same address.
+ if (mNetlinkLinkProperties.addLinkAddress((LinkAddress) message.obj)) {
+ updateLinkProperties();
+ }
+ break;
+ case CMD_IP_ADDRESS_REMOVED:
+ if (mNetlinkLinkProperties.removeLinkAddress((LinkAddress) message.obj)) {
+ updateLinkProperties();
+ }
+ break;
+ default:
+ loge("Error! unhandled message" + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ class InitialState extends State {
+ @Override
+ public void enter() {
+ mWifiNative.unloadDriver();
+
+ if (mWifiP2pChannel == null) {
+ mWifiP2pChannel = new AsyncChannel();
+ mWifiP2pChannel.connect(mContext, getHandler(), mWifiP2pManager.getMessenger());
+ }
+
+ if (mWifiApConfigChannel == null) {
+ mWifiApConfigChannel = new AsyncChannel();
+ WifiApConfigStore wifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
+ mContext, getHandler());
+ wifiApConfigStore.loadApConfiguration();
+ mWifiApConfigChannel.connectSync(mContext, getHandler(),
+ wifiApConfigStore.getMessenger());
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_START_SUPPLICANT:
+ if (mWifiNative.loadDriver()) {
+ try {
+ mNwService.wifiFirmwareReload(mInterfaceName, "STA");
+ } catch (Exception e) {
+ loge("Failed to reload STA firmware " + e);
+ // continue
+ }
+
+ try {
+ // A runtime crash can leave the interface up and
+ // this affects connectivity when supplicant starts up.
+ // Ensure interface is down before a supplicant start.
+ mNwService.setInterfaceDown(mInterfaceName);
+ // Set privacy extensions
+ mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+
+ // IPv6 is enabled only as long as access point is connected since:
+ // - IPv6 addresses and routes stick around after disconnection
+ // - kernel is unaware when connected and fails to start IPv6 negotiation
+ // - kernel can start autoconfiguration when 802.1x is not complete
+ mNwService.disableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ loge("Unable to change interface settings: " + re);
+ } catch (IllegalStateException ie) {
+ loge("Unable to change interface settings: " + ie);
+ }
+
+ /* Stop a running supplicant after a runtime restart
+ * Avoids issues with drivers that do not handle interface down
+ * on a running supplicant properly.
+ */
+ mWifiMonitor.killSupplicant(mP2pSupported);
+ if(mWifiNative.startSupplicant(mP2pSupported)) {
+ setWifiState(WIFI_STATE_ENABLING);
+ if (DBG) log("Supplicant start successful");
+ mWifiMonitor.startMonitoring();
+ transitionTo(mSupplicantStartingState);
+ } else {
+ loge("Failed to start supplicant!");
+ }
+ } else {
+ loge("Failed to load driver");
+ }
+ break;
+ case CMD_START_AP:
+ if (mWifiNative.loadDriver()) {
+ setWifiApState(WIFI_AP_STATE_ENABLING);
+ transitionTo(mSoftApStartingState);
+ } else {
+ loge("Failed to load driver for softap");
+ }
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class SupplicantStartingState extends State {
+ private void initializeWpsDetails() {
+ String detail;
+ detail = SystemProperties.get("ro.product.name", "");
+ if (!mWifiNative.setDeviceName(detail)) {
+ loge("Failed to set device name " + detail);
+ }
+ detail = SystemProperties.get("ro.product.manufacturer", "");
+ if (!mWifiNative.setManufacturer(detail)) {
+ loge("Failed to set manufacturer " + detail);
+ }
+ detail = SystemProperties.get("ro.product.model", "");
+ if (!mWifiNative.setModelName(detail)) {
+ loge("Failed to set model name " + detail);
+ }
+ detail = SystemProperties.get("ro.product.model", "");
+ if (!mWifiNative.setModelNumber(detail)) {
+ loge("Failed to set model number " + detail);
+ }
+ detail = SystemProperties.get("ro.serialno", "");
+ if (!mWifiNative.setSerialNumber(detail)) {
+ loge("Failed to set serial number " + detail);
+ }
+ if (!mWifiNative.setConfigMethods("physical_display virtual_push_button")) {
+ loge("Failed to set WPS config methods");
+ }
+ if (!mWifiNative.setDeviceType(mPrimaryDeviceType)) {
+ loge("Failed to set primary device type " + mPrimaryDeviceType);
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ if (DBG) log("Supplicant connection established");
+ setWifiState(WIFI_STATE_ENABLED);
+ mSupplicantRestartCount = 0;
+ /* Reset the supplicant state to indicate the supplicant
+ * state is not known at this time */
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ /* Initialize data structures */
+ mLastBssid = null;
+ mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ mLastSignalLevel = -1;
+
+ mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
+ mWifiConfigStore.loadAndEnableAllNetworks();
+ initializeWpsDetails();
+
+ sendSupplicantConnectionChangedBroadcast(true);
+ transitionTo(mDriverStartedState);
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ if (++mSupplicantRestartCount <= SUPPLICANT_RESTART_TRIES) {
+ loge("Failed to setup control channel, restart supplicant");
+ mWifiMonitor.killSupplicant(mP2pSupported);
+ transitionTo(mInitialState);
+ sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
+ } else {
+ loge("Failed " + mSupplicantRestartCount +
+ " times to start supplicant, unload driver");
+ mSupplicantRestartCount = 0;
+ setWifiState(WIFI_STATE_UNKNOWN);
+ transitionTo(mInitialState);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class SupplicantStartedState extends State {
+ @Override
+ public void enter() {
+ /* Wifi is available as long as we have a connection to supplicant */
+ mNetworkInfo.setIsAvailable(true);
+
+ int defaultInterval = mContext.getResources().getInteger(
+ R.integer.config_wifi_supplicant_scan_interval);
+
+ mSupplicantScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
+ defaultInterval);
+
+ mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */
+ if (mP2pSupported) {
+ transitionTo(mWaitForP2pDisableState);
+ } else {
+ transitionTo(mSupplicantStoppingState);
+ }
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT: /* Supplicant connection lost */
+ loge("Connection lost, restart supplicant");
+ handleSupplicantConnectionLoss();
+ handleNetworkDisconnect();
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ if (mP2pSupported) {
+ transitionTo(mWaitForP2pDisableState);
+ } else {
+ transitionTo(mInitialState);
+ }
+ sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
+ break;
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ setScanResults();
+ sendScanResultsAvailableBroadcast();
+ mScanResultIsPending = false;
+ break;
+ case CMD_PING_SUPPLICANT:
+ boolean ok = mWifiNative.ping();
+ replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+ break;
+ /* Cannot start soft AP while in client mode */
+ case CMD_START_AP:
+ loge("Failed to start soft AP with a running supplicant");
+ setWifiApState(WIFI_AP_STATE_FAILED);
+ break;
+ case CMD_SET_OPERATIONAL_MODE:
+ mOperationalMode = message.arg1;
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mNetworkInfo.setIsAvailable(false);
+ }
+ }
+
+ class SupplicantStoppingState extends State {
+ @Override
+ public void enter() {
+ /* Send any reset commands to supplicant before shutting it down */
+ handleNetworkDisconnect();
+ if (mDhcpStateMachine != null) {
+ mDhcpStateMachine.doQuit();
+ }
+
+ if (DBG) log("stopping supplicant");
+ mWifiMonitor.stopSupplicant();
+
+ /* Send ourselves a delayed message to indicate failure after a wait time */
+ sendMessageDelayed(obtainMessage(CMD_STOP_SUPPLICANT_FAILED,
+ ++mSupplicantStopFailureToken, 0), SUPPLICANT_RESTART_INTERVAL_MSECS);
+ setWifiState(WIFI_STATE_DISABLING);
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ loge("Supplicant connection received while stopping");
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ if (DBG) log("Supplicant connection lost");
+ handleSupplicantConnectionLoss();
+ transitionTo(mInitialState);
+ break;
+ case CMD_STOP_SUPPLICANT_FAILED:
+ if (message.arg1 == mSupplicantStopFailureToken) {
+ loge("Timed out on a supplicant stop, kill and proceed");
+ handleSupplicantConnectionLoss();
+ transitionTo(mInitialState);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStartingState extends State {
+ private int mTries;
+ @Override
+ public void enter() {
+ mTries = 1;
+ /* Send ourselves a delayed message to start driver a second time */
+ sendMessageDelayed(obtainMessage(CMD_DRIVER_START_TIMED_OUT,
+ ++mDriverStartToken, 0), DRIVER_START_TIME_OUT_MSECS);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ SupplicantState state = handleSupplicantStateChange(message);
+ /* If suplicant is exiting out of INTERFACE_DISABLED state into
+ * a state that indicates driver has started, it is ready to
+ * receive driver commands
+ */
+ if (SupplicantState.isDriverActive(state)) {
+ transitionTo(mDriverStartedState);
+ }
+ break;
+ case CMD_DRIVER_START_TIMED_OUT:
+ if (message.arg1 == mDriverStartToken) {
+ if (mTries >= 2) {
+ loge("Failed to start driver after " + mTries);
+ transitionTo(mDriverStoppedState);
+ } else {
+ loge("Driver start failed, retrying");
+ mWakeLock.acquire();
+ mWifiNative.startDriver();
+ mWakeLock.release();
+
+ ++mTries;
+ /* Send ourselves a delayed message to start driver again */
+ sendMessageDelayed(obtainMessage(CMD_DRIVER_START_TIMED_OUT,
+ ++mDriverStartToken, 0), DRIVER_START_TIME_OUT_MSECS);
+ }
+ }
+ break;
+ /* Queue driver commands & connection events */
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_START_SCAN:
+ case CMD_DISCONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RECONNECT:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStartedState extends State {
+ @Override
+ public void enter() {
+ mIsRunning = true;
+ mInDelayedStop = false;
+ mDelayedStopCounter++;
+ updateBatteryWorkSource(null);
+ /**
+ * Enable bluetooth coexistence scan mode when bluetooth connection is active.
+ * When this mode is on, some of the low-level scan parameters used by the
+ * driver are changed to reduce interference with bluetooth
+ */
+ mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
+ /* set country code */
+ setCountryCode();
+ /* set frequency band of operation */
+ setFrequencyBand();
+ /* initialize network state */
+ setNetworkDetailedState(DetailedState.DISCONNECTED);
+
+ /* Remove any filtering on Multicast v6 at start */
+ mWifiNative.stopFilteringMulticastV6Packets();
+
+ /* Reset Multicast v4 filtering state */
+ if (mFilteringMulticastV4Packets.get()) {
+ mWifiNative.startFilteringMulticastV4Packets();
+ } else {
+ mWifiNative.stopFilteringMulticastV4Packets();
+ }
+
+ mDhcpActive = false;
+
+ startBatchedScan();
+
+ if (mOperationalMode != CONNECT_MODE) {
+ mWifiNative.disconnect();
+ mWifiConfigStore.disableAllNetworks();
+ if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+ setWifiState(WIFI_STATE_DISABLED);
+ }
+ transitionTo(mScanModeState);
+ } else {
+ /* Driver stop may have disabled networks, enable right after start */
+ mWifiConfigStore.enableAllNetworks();
+
+ if (DBG) log("Attempting to reconnect to wifi network ..");
+ mWifiNative.reconnect();
+
+ // Status pulls in the current supplicant state and network connection state
+ // events over the monitor connection. This helps framework sync up with
+ // current supplicant state
+ mWifiNative.status();
+ transitionTo(mDisconnectedState);
+ }
+
+ // We may have missed screen update at boot
+ if (mScreenBroadcastReceived.get() == false) {
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(
+ Context.POWER_SERVICE);
+ handleScreenStateChanged(powerManager.isScreenOn());
+ } else {
+ // Set the right suspend mode settings
+ mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
+ && mUserWantsSuspendOpt.get());
+ }
+ mWifiNative.setPowerSave(true);
+
+ if (mP2pSupported) {
+ if (mOperationalMode == CONNECT_MODE) {
+ mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
+ } else {
+ // P2P statemachine starts in disabled state, and is not enabled until
+ // CMD_ENABLE_P2P is sent from here; so, nothing needs to be done to
+ // keep it disabled.
+ }
+ }
+
+ final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_ENABLED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_START_SCAN:
+ noteScanStart(message.arg1, (WorkSource) message.obj);
+ startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
+ break;
+ case CMD_SET_BATCHED_SCAN:
+ if (recordBatchedScanSettings(message.arg1, message.arg2,
+ (Bundle)message.obj)) {
+ startBatchedScan();
+ }
+ break;
+ case CMD_SET_COUNTRY_CODE:
+ String country = (String) message.obj;
+ if (DBG) log("set country code " + country);
+ if (country != null) {
+ country = country.toUpperCase(Locale.ROOT);
+ if (mLastSetCountryCode == null
+ || country.equals(mLastSetCountryCode) == false) {
+ if (mWifiNative.setCountryCode(country)) {
+ mLastSetCountryCode = country;
+ } else {
+ loge("Failed to set country code " + country);
+ }
+ }
+ }
+ break;
+ case CMD_SET_FREQUENCY_BAND:
+ int band = message.arg1;
+ if (DBG) log("set frequency band " + band);
+ if (mWifiNative.setBand(band)) {
+ mFrequencyBand.set(band);
+ // flush old data - like scan results
+ mWifiNative.bssFlush();
+ //Fetch the latest scan results when frequency band is set
+ startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
+ } else {
+ loge("Failed to set frequency band " + band);
+ }
+ break;
+ case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
+ mBluetoothConnectionActive = (message.arg1 !=
+ BluetoothAdapter.STATE_DISCONNECTED);
+ mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
+ break;
+ case CMD_STOP_DRIVER:
+ int mode = message.arg1;
+
+ /* Already doing a delayed stop */
+ if (mInDelayedStop) {
+ if (DBG) log("Already in delayed stop");
+ break;
+ }
+ /* disconnect right now, but leave the driver running for a bit */
+ mWifiConfigStore.disableAllNetworks();
+
+ mInDelayedStop = true;
+ mDelayedStopCounter++;
+ if (DBG) log("Delayed stop message " + mDelayedStopCounter);
+
+ /* send regular delayed shut down */
+ Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null);
+ driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter);
+ mDriverStopIntent = PendingIntent.getBroadcast(mContext,
+ DRIVER_STOP_REQUEST, driverStopIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+ + mDriverStopDelayMs, mDriverStopIntent);
+ break;
+ case CMD_START_DRIVER:
+ if (mInDelayedStop) {
+ mInDelayedStop = false;
+ mDelayedStopCounter++;
+ mAlarmManager.cancel(mDriverStopIntent);
+ if (DBG) log("Delayed stop ignored due to start");
+ if (mOperationalMode == CONNECT_MODE) {
+ mWifiConfigStore.enableAllNetworks();
+ }
+ }
+ break;
+ case CMD_DELAYED_STOP_DRIVER:
+ if (DBG) log("delayed stop " + message.arg1 + " " + mDelayedStopCounter);
+ if (message.arg1 != mDelayedStopCounter) break;
+ if (getCurrentState() != mDisconnectedState) {
+ mWifiNative.disconnect();
+ handleNetworkDisconnect();
+ }
+ mWakeLock.acquire();
+ mWifiNative.stopDriver();
+ mWakeLock.release();
+ if (mP2pSupported) {
+ transitionTo(mWaitForP2pDisableState);
+ } else {
+ transitionTo(mDriverStoppingState);
+ }
+ break;
+ case CMD_START_PACKET_FILTERING:
+ if (message.arg1 == MULTICAST_V6) {
+ mWifiNative.startFilteringMulticastV6Packets();
+ } else if (message.arg1 == MULTICAST_V4) {
+ mWifiNative.startFilteringMulticastV4Packets();
+ } else {
+ loge("Illegal arugments to CMD_START_PACKET_FILTERING");
+ }
+ break;
+ case CMD_STOP_PACKET_FILTERING:
+ if (message.arg1 == MULTICAST_V6) {
+ mWifiNative.stopFilteringMulticastV6Packets();
+ } else if (message.arg1 == MULTICAST_V4) {
+ mWifiNative.stopFilteringMulticastV4Packets();
+ } else {
+ loge("Illegal arugments to CMD_STOP_PACKET_FILTERING");
+ }
+ break;
+ case CMD_SET_SUSPEND_OPT_ENABLED:
+ if (message.arg1 == 1) {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
+ mSuspendWakeLock.release();
+ } else {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
+ }
+ break;
+ case CMD_SET_HIGH_PERF_MODE:
+ if (message.arg1 == 1) {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, false);
+ } else {
+ setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
+ }
+ break;
+ case CMD_ENABLE_TDLS:
+ if (message.obj != null) {
+ String remoteAddress = (String) message.obj;
+ boolean enable = (message.arg1 == 1);
+ mWifiNative.startTdls(remoteAddress, enable);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ @Override
+ public void exit() {
+ mIsRunning = false;
+ updateBatteryWorkSource(null);
+ mScanResults = new ArrayList<ScanResult>();
+
+ stopBatchedScan();
+
+ final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ noteScanEnd(); // wrap up any pending request.
+
+ mLastSetCountryCode = null;
+ }
+ }
+
+ class WaitForP2pDisableState extends State {
+ private State mTransitionToState;
+ @Override
+ public void enter() {
+ switch (getCurrentMessage().what) {
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ mTransitionToState = mInitialState;
+ break;
+ case CMD_DELAYED_STOP_DRIVER:
+ mTransitionToState = mDriverStoppingState;
+ break;
+ case CMD_STOP_SUPPLICANT:
+ mTransitionToState = mSupplicantStoppingState;
+ break;
+ default:
+ mTransitionToState = mDriverStoppingState;
+ break;
+ }
+ mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiStateMachine.CMD_DISABLE_P2P_RSP:
+ transitionTo(mTransitionToState);
+ break;
+ /* Defer wifi start/shut and driver commands */
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_START_SCAN:
+ case CMD_DISCONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RECONNECT:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStoppingState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ SupplicantState state = handleSupplicantStateChange(message);
+ if (state == SupplicantState.INTERFACE_DISABLED) {
+ transitionTo(mDriverStoppedState);
+ }
+ break;
+ /* Queue driver commands */
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_START_SCAN:
+ case CMD_DISCONNECT:
+ case CMD_REASSOCIATE:
+ case CMD_RECONNECT:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DriverStoppedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = stateChangeResult.state;
+ // A WEXT bug means that we can be back to driver started state
+ // unexpectedly
+ if (SupplicantState.isDriverActive(state)) {
+ transitionTo(mDriverStartedState);
+ }
+ break;
+ case CMD_START_DRIVER:
+ mWakeLock.acquire();
+ mWifiNative.startDriver();
+ mWakeLock.release();
+ transitionTo(mDriverStartingState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ScanModeState extends State {
+ private int mLastOperationMode;
+ @Override
+ public void enter() {
+ mLastOperationMode = mOperationalMode;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 == CONNECT_MODE) {
+
+ if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+ setWifiState(WIFI_STATE_ENABLED);
+ // Load and re-enable networks when going back to enabled state
+ // This is essential for networks to show up after restore
+ mWifiConfigStore.loadAndEnableAllNetworks();
+ mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P);
+ } else {
+ mWifiConfigStore.enableAllNetworks();
+ }
+
+ mWifiNative.reconnect();
+
+ mOperationalMode = CONNECT_MODE;
+ transitionTo(mDisconnectedState);
+ } else {
+ // Nothing to do
+ return HANDLED;
+ }
+ break;
+ // Handle scan. All the connection related commands are
+ // handled only in ConnectModeState
+ case CMD_START_SCAN:
+ noteScanStart(message.arg1, (WorkSource) message.obj);
+ startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ConnectModeState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ WifiConfiguration config;
+ boolean ok;
+ switch(message.what) {
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ mSupplicantStateTracker.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT);
+ break;
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ SupplicantState state = handleSupplicantStateChange(message);
+ // A driver/firmware hang can now put the interface in a down state.
+ // We detect the interface going down and recover from it
+ if (!SupplicantState.isDriverActive(state)) {
+ if (mNetworkInfo.getState() != NetworkInfo.State.DISCONNECTED) {
+ handleNetworkDisconnect();
+ }
+ log("Detected an interface down, restart driver");
+ transitionTo(mDriverStoppedState);
+ sendMessage(CMD_START_DRIVER);
+ break;
+ }
+
+ // Supplicant can fail to report a NETWORK_DISCONNECTION_EVENT
+ // when authentication times out after a successful connection,
+ // we can figure this from the supplicant state. If supplicant
+ // state is DISCONNECTED, but the mNetworkInfo says we are not
+ // disconnected, we need to handle a disconnection
+ if (state == SupplicantState.DISCONNECTED &&
+ mNetworkInfo.getState() != NetworkInfo.State.DISCONNECTED) {
+ if (DBG) log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
+ handleNetworkDisconnect();
+ transitionTo(mDisconnectedState);
+ }
+ break;
+ case WifiP2pService.DISCONNECT_WIFI_REQUEST:
+ if (message.arg1 == 1) {
+ mWifiNative.disconnect();
+ mTemporarilyDisconnectWifi = true;
+ } else {
+ mWifiNative.reconnect();
+ mTemporarilyDisconnectWifi = false;
+ }
+ break;
+ case CMD_ADD_OR_UPDATE_NETWORK:
+ config = (WifiConfiguration) message.obj;
+ replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK,
+ mWifiConfigStore.addOrUpdateNetwork(config));
+ break;
+ case CMD_REMOVE_NETWORK:
+ ok = mWifiConfigStore.removeNetwork(message.arg1);
+ replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+ break;
+ case CMD_ENABLE_NETWORK:
+ ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
+ replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+ break;
+ case CMD_ENABLE_ALL_NETWORKS:
+ long time = android.os.SystemClock.elapsedRealtime();
+ if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
+ mWifiConfigStore.enableAllNetworks();
+ mLastEnableAllNetworksTime = time;
+ }
+ break;
+ case WifiManager.DISABLE_NETWORK:
+ if (mWifiConfigStore.disableNetwork(message.arg1,
+ WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) {
+ replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ case CMD_BLACKLIST_NETWORK:
+ mWifiNative.addToBlacklist((String)message.obj);
+ break;
+ case CMD_CLEAR_BLACKLIST:
+ mWifiNative.clearBlacklist();
+ break;
+ case CMD_SAVE_CONFIG:
+ ok = mWifiConfigStore.saveConfig();
+ replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
+
+ // Inform the backup manager about a data change
+ IBackupManager ibm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (ibm != null) {
+ try {
+ ibm.dataChanged("com.android.providers.settings");
+ } catch (Exception e) {
+ // Try again later
+ }
+ }
+ break;
+ case CMD_GET_CONFIGURED_NETWORKS:
+ replyToMessage(message, message.what,
+ mWifiConfigStore.getConfiguredNetworks());
+ break;
+ /* Do a redundant disconnect without transition */
+ case CMD_DISCONNECT:
+ mWifiNative.disconnect();
+ break;
+ case CMD_RECONNECT:
+ mWifiNative.reconnect();
+ break;
+ case CMD_REASSOCIATE:
+ mWifiNative.reassociate();
+ break;
+ case CMD_RELOAD_TLS_AND_RECONNECT:
+ if (mWifiConfigStore.needsUnlockedKeyStore()) {
+ logd("Reconnecting to give a chance to un-connected TLS networks");
+ mWifiNative.disconnect();
+ mWifiNative.reconnect();
+ }
+ break;
+ case WifiManager.CONNECT_NETWORK:
+ /* The connect message can contain a network id passed as arg1 on message or
+ * or a config passed as obj on message.
+ * For a new network, a config is passed to create and connect.
+ * For an existing network, a network id is passed
+ */
+ int netId = message.arg1;
+ config = (WifiConfiguration) message.obj;
+
+ /* Save the network config */
+ if (config != null) {
+ NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+ netId = result.getNetworkId();
+ }
+
+ if (mWifiConfigStore.selectNetwork(netId) &&
+ mWifiNative.reconnect()) {
+ /* The state tracker handles enabling networks upon completion/failure */
+ mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
+ replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
+ /* Expect a disconnection from the old connection */
+ transitionTo(mDisconnectingState);
+ } else {
+ loge("Failed to connect config: " + config + " netId: " + netId);
+ replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+ WifiManager.ERROR);
+ break;
+ }
+ break;
+ case WifiManager.SAVE_NETWORK:
+ config = (WifiConfiguration) message.obj;
+ NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+ } else {
+ loge("Failed to save network");
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ case WifiManager.FORGET_NETWORK:
+ if (mWifiConfigStore.forgetNetwork(message.arg1)) {
+ replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
+ } else {
+ loge("Failed to forget network");
+ replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ case WifiManager.START_WPS:
+ WpsInfo wpsInfo = (WpsInfo) message.obj;
+ WpsResult wpsResult;
+ switch (wpsInfo.setup) {
+ case WpsInfo.PBC:
+ wpsResult = mWifiConfigStore.startWpsPbc(wpsInfo);
+ break;
+ case WpsInfo.KEYPAD:
+ wpsResult = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo);
+ break;
+ case WpsInfo.DISPLAY:
+ wpsResult = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo);
+ break;
+ default:
+ wpsResult = new WpsResult(Status.FAILURE);
+ loge("Invalid setup for WPS");
+ break;
+ }
+ if (wpsResult.status == Status.SUCCESS) {
+ replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
+ transitionTo(mWpsRunningState);
+ } else {
+ loge("Failed to start WPS with config " + wpsInfo.toString());
+ replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
+ }
+ break;
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ if (DBG) log("Network connection established");
+ mLastNetworkId = message.arg1;
+ mLastBssid = (String) message.obj;
+
+ mWifiInfo.setBSSID(mLastBssid);
+ mWifiInfo.setNetworkId(mLastNetworkId);
+ /* send event to CM & network change broadcast */
+ setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ transitionTo(mObtainingIpState);
+ break;
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ if (DBG) log("Network connection lost");
+ handleNetworkDisconnect();
+ transitionTo(mDisconnectedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class L2ConnectedState extends State {
+ @Override
+ public void enter() {
+ mRssiPollToken++;
+ if (mEnableRssiPolling) {
+ sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
+ }
+ }
+
+ @Override
+ public void exit() {
+ handleNetworkDisconnect();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ handlePreDhcpSetup();
+ break;
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ handlePostDhcpSetup();
+ if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
+ if (DBG) log("DHCP successful");
+ handleSuccessfulIpConfiguration((DhcpResults) message.obj);
+ transitionTo(mVerifyingLinkState);
+ } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
+ if (DBG) log("DHCP failed");
+ handleFailedIpConfiguration();
+ transitionTo(mDisconnectingState);
+ }
+ break;
+ case CMD_DISCONNECT:
+ mWifiNative.disconnect();
+ transitionTo(mDisconnectingState);
+ break;
+ case WifiP2pService.DISCONNECT_WIFI_REQUEST:
+ if (message.arg1 == 1) {
+ mWifiNative.disconnect();
+ mTemporarilyDisconnectWifi = true;
+ transitionTo(mDisconnectingState);
+ }
+ break;
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 != CONNECT_MODE) {
+ sendMessage(CMD_DISCONNECT);
+ deferMessage(message);
+ }
+ break;
+ case CMD_START_SCAN:
+ /* Do not attempt to connect when we are already connected */
+ noteScanStart(message.arg1, (WorkSource) message.obj);
+ startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP);
+ break;
+ /* Ignore connection to same network */
+ case WifiManager.CONNECT_NETWORK:
+ int netId = message.arg1;
+ if (mWifiInfo.getNetworkId() == netId) {
+ break;
+ }
+ return NOT_HANDLED;
+ case WifiManager.SAVE_NETWORK:
+ WifiConfiguration config = (WifiConfiguration) message.obj;
+ NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+ if (mWifiInfo.getNetworkId() == result.getNetworkId()) {
+ if (result.hasIpChanged()) {
+ log("Reconfiguring IP on connection");
+ transitionTo(mObtainingIpState);
+ }
+ if (result.hasProxyChanged()) {
+ log("Reconfiguring proxy on connection");
+ updateLinkProperties();
+ }
+ }
+
+ if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+ replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+ } else {
+ loge("Failed to save network");
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+ WifiManager.ERROR);
+ }
+ break;
+ /* Ignore */
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ break;
+ case CMD_RSSI_POLL:
+ if (message.arg1 == mRssiPollToken) {
+ // Get Info and continue polling
+ fetchRssiAndLinkSpeedNative();
+ sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
+ mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
+ } else {
+ // Polling has completed
+ }
+ break;
+ case CMD_ENABLE_RSSI_POLL:
+ mEnableRssiPolling = (message.arg1 == 1);
+ mRssiPollToken++;
+ if (mEnableRssiPolling) {
+ // first poll
+ fetchRssiAndLinkSpeedNative();
+ sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
+ mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
+ }
+ break;
+ case WifiManager.RSSI_PKTCNT_FETCH:
+ RssiPacketCountInfo info = new RssiPacketCountInfo();
+ fetchRssiAndLinkSpeedNative();
+ info.rssi = mWifiInfo.getRssi();
+ fetchPktcntNative(info);
+ replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+
+ return HANDLED;
+ }
+ }
+
+ class ObtainingIpState extends State {
+ @Override
+ public void enter() {
+ if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
+ // TODO: If we're switching between static IP configuration and DHCP, remove the
+ // static configuration first.
+ startDhcp();
+ } else {
+ // stop any running dhcp before assigning static IP
+ stopDhcp();
+ DhcpResults dhcpResults = new DhcpResults(
+ mWifiConfigStore.getLinkProperties(mLastNetworkId));
+ InterfaceConfiguration ifcg = new InterfaceConfiguration();
+ Iterator<LinkAddress> addrs =
+ dhcpResults.linkProperties.getLinkAddresses().iterator();
+ if (!addrs.hasNext()) {
+ loge("Static IP lacks address");
+ sendMessage(CMD_STATIC_IP_FAILURE);
+ } else {
+ ifcg.setLinkAddress(addrs.next());
+ ifcg.setInterfaceUp();
+ try {
+ mNwService.setInterfaceConfig(mInterfaceName, ifcg);
+ if (DBG) log("Static IP configuration succeeded");
+ sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults);
+ } catch (RemoteException re) {
+ loge("Static IP configuration failed: " + re);
+ sendMessage(CMD_STATIC_IP_FAILURE);
+ } catch (IllegalStateException e) {
+ loge("Static IP configuration failed: " + e);
+ sendMessage(CMD_STATIC_IP_FAILURE);
+ }
+ }
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString() + "\n");
+ switch(message.what) {
+ case CMD_STATIC_IP_SUCCESS:
+ handleSuccessfulIpConfiguration((DhcpResults) message.obj);
+ transitionTo(mVerifyingLinkState);
+ break;
+ case CMD_STATIC_IP_FAILURE:
+ handleFailedIpConfiguration();
+ transitionTo(mDisconnectingState);
+ break;
+ case WifiManager.SAVE_NETWORK:
+ deferMessage(message);
+ break;
+ /* Defer any power mode changes since we must keep active power mode at DHCP */
+ case CMD_SET_HIGH_PERF_MODE:
+ deferMessage(message);
+ break;
+ /* Defer scan request since we should not switch to other channels at DHCP */
+ case CMD_START_SCAN:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class VerifyingLinkState extends State {
+ @Override
+ public void enter() {
+ log(getName() + " enter");
+ setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+ //stay here
+ log(getName() + " POOR_LINK_DETECTED: no transition");
+ break;
+ case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+ log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");
+ // Send out a broadcast with the CAPTIVE_PORTAL_CHECK to preserve
+ // existing behaviour. The captive portal check really happens after we
+ // transition into DetailedState.CONNECTED.
+ setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
+ mWifiConfigStore.updateStatus(mLastNetworkId,
+ DetailedState.CAPTIVE_PORTAL_CHECK);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
+ // NOTE: This might look like an odd place to enable IPV6 but this is in
+ // response to transitioning into GOOD_LINK_DETECTED. Similarly, we disable
+ // ipv6 when we transition into POOR_LINK_DETECTED in mConnectedState.
+ try {
+ mNwService.enableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ loge("Failed to enable IPv6: " + re);
+ } catch (IllegalStateException e) {
+ loge("Failed to enable IPv6: " + e);
+ }
+
+ log(getName() + " GOOD_LINK_DETECTED: transition to CONNECTED");
+ setNetworkDetailedState(DetailedState.CONNECTED);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+ transitionTo(mConnectedState);
+
+ break;
+ default:
+ if (DBG) log(getName() + " what=" + message.what + " NOT_HANDLED");
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class ConnectedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
+ if (DBG) log("Watchdog reports poor link");
+ try {
+ mNwService.disableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ loge("Failed to disable IPv6: " + re);
+ } catch (IllegalStateException e) {
+ loge("Failed to disable IPv6: " + e);
+ }
+ /* Report a disconnect */
+ setNetworkDetailedState(DetailedState.DISCONNECTED);
+ mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
+ sendNetworkStateChangeBroadcast(mLastBssid);
+
+ transitionTo(mVerifyingLinkState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ /* Request a CS wakelock during transition to mobile */
+ checkAndSetConnectivityInstance();
+ mCm.requestNetworkTransitionWakelock(getName());
+ }
+ }
+
+ class DisconnectingState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 != CONNECT_MODE) {
+ deferMessage(message);
+ }
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ /* If we get a SUPPLICANT_STATE_CHANGE_EVENT before NETWORK_DISCONNECTION_EVENT
+ * we have missed the network disconnection, transition to mDisconnectedState
+ * and handle the rest of the events there
+ */
+ deferMessage(message);
+ handleNetworkDisconnect();
+ transitionTo(mDisconnectedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class DisconnectedState extends State {
+ private boolean mAlarmEnabled = false;
+ /* This is set from the overlay config file or from a secure setting.
+ * A value of 0 disables scanning in the framework.
+ */
+ private long mFrameworkScanIntervalMs;
+
+ private void setScanAlarm(boolean enabled) {
+ if (enabled == mAlarmEnabled) return;
+ if (enabled) {
+ if (mFrameworkScanIntervalMs > 0) {
+ mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + mFrameworkScanIntervalMs,
+ mFrameworkScanIntervalMs,
+ mScanIntent);
+ mAlarmEnabled = true;
+ }
+ } else {
+ mAlarmManager.cancel(mScanIntent);
+ mAlarmEnabled = false;
+ }
+ }
+
+ @Override
+ public void enter() {
+ // We dont scan frequently if this is a temporary disconnect
+ // due to p2p
+ if (mTemporarilyDisconnectWifi) {
+ mWifiP2pChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_RESPONSE);
+ return;
+ }
+
+ mFrameworkScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
+ mDefaultFrameworkScanIntervalMs);
+ /*
+ * We initiate background scanning if it is enabled, otherwise we
+ * initiate an infrequent scan that wakes up the device to ensure
+ * a user connects to an access point on the move
+ */
+ if (mEnableBackgroundScan) {
+ /* If a regular scan result is pending, do not initiate background
+ * scan until the scan results are returned. This is needed because
+ * initiating a background scan will cancel the regular scan and
+ * scan results will not be returned until background scanning is
+ * cleared
+ */
+ if (!mScanResultIsPending) {
+ mWifiNative.enableBackgroundScan(true);
+ }
+ } else {
+ setScanAlarm(true);
+ }
+
+ /**
+ * If we have no networks saved, the supplicant stops doing the periodic scan.
+ * The scans are useful to notify the user of the presence of an open network.
+ * Note that these are not wake up scans.
+ */
+ if (!mP2pConnected.get() && mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case CMD_NO_NETWORKS_PERIODIC_SCAN:
+ if (mP2pConnected.get()) break;
+ if (message.arg1 == mPeriodicScanToken &&
+ mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ sendMessage(CMD_START_SCAN, UNKNOWN_SCAN_SOURCE, 0, (WorkSource) null);
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
+ break;
+ case WifiManager.FORGET_NETWORK:
+ case CMD_REMOVE_NETWORK:
+ // Set up a delayed message here. After the forget/remove is handled
+ // the handled delayed message will determine if there is a need to
+ // scan and continue
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ ret = NOT_HANDLED;
+ break;
+ case CMD_SET_OPERATIONAL_MODE:
+ if (message.arg1 != CONNECT_MODE) {
+ mOperationalMode = message.arg1;
+
+ mWifiConfigStore.disableAllNetworks();
+ if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+ mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ);
+ setWifiState(WIFI_STATE_DISABLED);
+ }
+
+ transitionTo(mScanModeState);
+ }
+ break;
+ case CMD_ENABLE_BACKGROUND_SCAN:
+ mEnableBackgroundScan = (message.arg1 == 1);
+ if (mEnableBackgroundScan) {
+ mWifiNative.enableBackgroundScan(true);
+ setScanAlarm(false);
+ } else {
+ mWifiNative.enableBackgroundScan(false);
+ setScanAlarm(true);
+ }
+ break;
+ /* Ignore network disconnect */
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state));
+ /* ConnectModeState does the rest of the handling */
+ ret = NOT_HANDLED;
+ break;
+ case CMD_START_SCAN:
+ /* Disable background scan temporarily during a regular scan */
+ if (mEnableBackgroundScan) {
+ mWifiNative.enableBackgroundScan(false);
+ }
+ /* Handled in parent state */
+ ret = NOT_HANDLED;
+ break;
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ /* Re-enable background scan when a pending scan result is received */
+ if (mEnableBackgroundScan && mScanResultIsPending) {
+ mWifiNative.enableBackgroundScan(true);
+ }
+ /* Handled in parent state */
+ ret = NOT_HANDLED;
+ break;
+ case WifiP2pService.P2P_CONNECTION_CHANGED:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ mP2pConnected.set(info.isConnected());
+ if (mP2pConnected.get()) {
+ int defaultInterval = mContext.getResources().getInteger(
+ R.integer.config_wifi_scan_interval_p2p_connected);
+ long scanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
+ defaultInterval);
+ mWifiNative.setScanInterval((int) scanIntervalMs/1000);
+ } else if (mWifiConfigStore.getConfiguredNetworks().size() == 0) {
+ if (DBG) log("Turn on scanning after p2p disconnected");
+ sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
+ ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
+ }
+ case CMD_RECONNECT:
+ case CMD_REASSOCIATE:
+ if (mTemporarilyDisconnectWifi) {
+ // Drop a third party reconnect/reassociate if STA is
+ // temporarily disconnected for p2p
+ break;
+ } else {
+ // ConnectModeState handles it
+ ret = NOT_HANDLED;
+ }
+ break;
+ default:
+ ret = NOT_HANDLED;
+ }
+ return ret;
+ }
+
+ @Override
+ public void exit() {
+ /* No need for a background scan upon exit from a disconnected state */
+ if (mEnableBackgroundScan) {
+ mWifiNative.enableBackgroundScan(false);
+ }
+ setScanAlarm(false);
+ }
+ }
+
+ class WpsRunningState extends State {
+ //Tracks the source to provide a reply
+ private Message mSourceMessage;
+ @Override
+ public void enter() {
+ mSourceMessage = Message.obtain(getCurrentMessage());
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiMonitor.WPS_SUCCESS_EVENT:
+ // Ignore intermediate success, wait for full connection
+ break;
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ deferMessage(message);
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
+ WifiManager.WPS_OVERLAP_ERROR);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiMonitor.WPS_FAIL_EVENT:
+ //arg1 has the reason for the failure
+ replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiMonitor.WPS_TIMEOUT_EVENT:
+ replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
+ WifiManager.WPS_TIMED_OUT);
+ mSourceMessage.recycle();
+ mSourceMessage = null;
+ transitionTo(mDisconnectedState);
+ break;
+ case WifiManager.START_WPS:
+ replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS);
+ break;
+ case WifiManager.CANCEL_WPS:
+ if (mWifiNative.cancelWps()) {
+ replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED);
+ } else {
+ replyToMessage(message, WifiManager.CANCEL_WPS_FAILED, WifiManager.ERROR);
+ }
+ transitionTo(mDisconnectedState);
+ break;
+ /* Defer all commands that can cause connections to a different network
+ * or put the state machine out of connect mode
+ */
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case WifiManager.CONNECT_NETWORK:
+ case CMD_ENABLE_NETWORK:
+ case CMD_RECONNECT:
+ case CMD_REASSOCIATE:
+ deferMessage(message);
+ break;
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ if (DBG) log("Network connection lost");
+ handleNetworkDisconnect();
+ break;
+ case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+ if (DBG) log("Ignore Assoc reject event during WPS Connection");
+ break;
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ // Disregard auth failure events during WPS connection. The
+ // EAP sequence is retried several times, and there might be
+ // failures (especially for wps pin). We will get a WPS_XXX
+ // event at the end of the sequence anyway.
+ if (DBG) log("Ignore auth failure during WPS connection");
+ break;
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ //Throw away supplicant state changes when WPS is running.
+ //We will start getting supplicant state changes once we get
+ //a WPS success or failure
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mWifiConfigStore.enableAllNetworks();
+ mWifiConfigStore.loadConfiguredNetworks();
+ }
+ }
+
+ class SoftApStartingState extends State {
+ @Override
+ public void enter() {
+ final Message message = getCurrentMessage();
+ if (message.what == CMD_START_AP) {
+ final WifiConfiguration config = (WifiConfiguration) message.obj;
+
+ if (config == null) {
+ mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);
+ } else {
+ mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
+ startSoftApWithConfig(config);
+ }
+ } else {
+ throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ case CMD_TETHER_STATE_CHANGE:
+ deferMessage(message);
+ break;
+ case WifiStateMachine.CMD_RESPONSE_AP_CONFIG:
+ WifiConfiguration config = (WifiConfiguration) message.obj;
+ if (config != null) {
+ startSoftApWithConfig(config);
+ } else {
+ loge("Softap config is null!");
+ sendMessage(CMD_START_AP_FAILURE);
+ }
+ break;
+ case CMD_START_AP_SUCCESS:
+ setWifiApState(WIFI_AP_STATE_ENABLED);
+ transitionTo(mSoftApStartedState);
+ break;
+ case CMD_START_AP_FAILURE:
+ setWifiApState(WIFI_AP_STATE_FAILED);
+ transitionTo(mInitialState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class SoftApStartedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_STOP_AP:
+ if (DBG) log("Stopping Soft AP");
+ /* We have not tethered at this point, so we just shutdown soft Ap */
+ try {
+ mNwService.stopAccessPoint(mInterfaceName);
+ } catch(Exception e) {
+ loge("Exception in stopAccessPoint()");
+ }
+ setWifiApState(WIFI_AP_STATE_DISABLED);
+ transitionTo(mInitialState);
+ break;
+ case CMD_START_AP:
+ // Ignore a start on a running access point
+ break;
+ /* Fail client mode operation when soft AP is enabled */
+ case CMD_START_SUPPLICANT:
+ loge("Cannot start supplicant with a running soft AP");
+ setWifiState(WIFI_STATE_UNKNOWN);
+ break;
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+ if (startTethering(stateChange.available)) {
+ transitionTo(mTetheringState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class TetheringState extends State {
+ @Override
+ public void enter() {
+ /* Send ourselves a delayed message to shut down if tethering fails to notify */
+ sendMessageDelayed(obtainMessage(CMD_TETHER_NOTIFICATION_TIMED_OUT,
+ ++mTetherToken, 0), TETHER_NOTIFICATION_TIME_OUT_MSECS);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+ if (isWifiTethered(stateChange.active)) {
+ transitionTo(mTetheredState);
+ }
+ return HANDLED;
+ case CMD_TETHER_NOTIFICATION_TIMED_OUT:
+ if (message.arg1 == mTetherToken) {
+ loge("Failed to get tether update, shutdown soft access point");
+ transitionTo(mSoftApStartedState);
+ // Needs to be first thing handled
+ sendMessageAtFrontOfQueue(CMD_STOP_AP);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class TetheredState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+ if (!isWifiTethered(stateChange.active)) {
+ loge("Tethering reports wifi as untethered!, shut down soft Ap");
+ setHostApRunning(null, false);
+ setHostApRunning(null, true);
+ }
+ return HANDLED;
+ case CMD_STOP_AP:
+ if (DBG) log("Untethering before stopping AP");
+ setWifiApState(WIFI_AP_STATE_DISABLING);
+ stopTethering();
+ transitionTo(mUntetheringState);
+ // More work to do after untethering
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class UntetheringState extends State {
+ @Override
+ public void enter() {
+ /* Send ourselves a delayed message to shut down if tethering fails to notify */
+ sendMessageDelayed(obtainMessage(CMD_TETHER_NOTIFICATION_TIMED_OUT,
+ ++mTetherToken, 0), TETHER_NOTIFICATION_TIME_OUT_MSECS);
+
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ case CMD_TETHER_STATE_CHANGE:
+ TetherStateChange stateChange = (TetherStateChange) message.obj;
+
+ /* Wait till wifi is untethered */
+ if (isWifiTethered(stateChange.active)) break;
+
+ transitionTo(mSoftApStartedState);
+ break;
+ case CMD_TETHER_NOTIFICATION_TIMED_OUT:
+ if (message.arg1 == mTetherToken) {
+ loge("Failed to get tether update, force stop access point");
+ transitionTo(mSoftApStartedState);
+ }
+ break;
+ case CMD_START_SUPPLICANT:
+ case CMD_STOP_SUPPLICANT:
+ case CMD_START_AP:
+ case CMD_STOP_AP:
+ case CMD_START_DRIVER:
+ case CMD_STOP_DRIVER:
+ case CMD_SET_OPERATIONAL_MODE:
+ case CMD_SET_COUNTRY_CODE:
+ case CMD_SET_FREQUENCY_BAND:
+ case CMD_START_PACKET_FILTERING:
+ case CMD_STOP_PACKET_FILTERING:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ //State machine initiated requests can have replyTo set to null indicating
+ //there are no recepients, we ignore those reply actions
+ private void replyToMessage(Message msg, int what) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessageWithArg2(msg);
+ dstMsg.what = what;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, int arg1) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessageWithArg2(msg);
+ dstMsg.what = what;
+ dstMsg.arg1 = arg1;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, Object obj) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessageWithArg2(msg);
+ dstMsg.what = what;
+ dstMsg.obj = obj;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ /**
+ * arg2 on the source message has a unique id that needs to be retained in replies
+ * to match the request
+
+ * see WifiManager for details
+ */
+ private Message obtainMessageWithArg2(Message srcMsg) {
+ Message msg = Message.obtain();
+ msg.arg2 = srcMsg.arg2;
+ return msg;
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiWatchdogStateMachine.java b/services/java/com/android/server/wifi/WifiWatchdogStateMachine.java
new file mode 100644
index 0000000..725036a
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiWatchdogStateMachine.java
@@ -0,0 +1,1210 @@
+/*
+ * Copyright (C) 2011 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.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.wifi.RssiPacketCountInfo;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.LruCache;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+
+/**
+ * WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi
+ * connects at L2 layer, the beacons from access point reach the device and it
+ * can maintain a connection, but the application connectivity can be flaky (due
+ * to bigger packet size exchange).
+ * <p>
+ * We now monitor the quality of the last hop on WiFi using packet loss ratio as
+ * an indicator to decide if the link is good enough to switch to Wi-Fi as the
+ * uplink.
+ * <p>
+ * When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the
+ * instant packet loss, and record it as per-AP loss-to-rssi statistics. When
+ * the instant packet loss is higher than a threshold, the WiFi watchdog sends a
+ * poor link notification to avoid WiFi connection temporarily.
+ * <p>
+ * While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to
+ * bring the WiFi connection back. Once the RSSI is high enough to achieve a
+ * lower packet loss, a good link detection is sent such that the WiFi
+ * connection become available again.
+ * <p>
+ * BSSID roaming has been taken into account. When user is moving across
+ * multiple APs, the WiFi watchdog will detect that and keep watching the
+ * currently connected AP.
+ * <p>
+ * Power impact should be minimal since much of the measurement relies on
+ * passive statistics already being tracked at the driver and the polling is
+ * done when screen is turned on and the RSSI is in a certain range.
+ *
+ * @hide
+ */
+public class WifiWatchdogStateMachine extends StateMachine {
+
+ private static final boolean DBG = false;
+
+ private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
+
+ /* Internal events */
+ private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1;
+ private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2;
+ private static final int EVENT_RSSI_CHANGE = BASE + 3;
+ private static final int EVENT_SUPPLICANT_STATE_CHANGE = BASE + 4;
+ private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5;
+ private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6;
+ private static final int EVENT_BSSID_CHANGE = BASE + 7;
+ private static final int EVENT_SCREEN_ON = BASE + 8;
+ private static final int EVENT_SCREEN_OFF = BASE + 9;
+
+ /* Internal messages */
+ private static final int CMD_RSSI_FETCH = BASE + 11;
+
+ /* Notifications from/to WifiStateMachine */
+ static final int POOR_LINK_DETECTED = BASE + 21;
+ static final int GOOD_LINK_DETECTED = BASE + 22;
+
+ /*
+ * RSSI levels as used by notification icon
+ * Level 4 -55 <= RSSI
+ * Level 3 -66 <= RSSI < -55
+ * Level 2 -77 <= RSSI < -67
+ * Level 1 -88 <= RSSI < -78
+ * Level 0 RSSI < -88
+ */
+
+ /**
+ * WiFi link statistics is monitored and recorded actively below this threshold.
+ * <p>
+ * Larger threshold is more adaptive but increases sampling cost.
+ */
+ private static final int LINK_MONITOR_LEVEL_THRESHOLD = WifiManager.RSSI_LEVELS - 1;
+
+ /**
+ * Remember packet loss statistics of how many BSSIDs.
+ * <p>
+ * Larger size is usually better but requires more space.
+ */
+ private static final int BSSID_STAT_CACHE_SIZE = 20;
+
+ /**
+ * RSSI range of a BSSID statistics.
+ * Within the range, (RSSI -> packet loss %) mappings are stored.
+ * <p>
+ * Larger range is usually better but requires more space.
+ */
+ private static final int BSSID_STAT_RANGE_LOW_DBM = -105;
+
+ /**
+ * See {@link #BSSID_STAT_RANGE_LOW_DBM}.
+ */
+ private static final int BSSID_STAT_RANGE_HIGH_DBM = -45;
+
+ /**
+ * How many consecutive empty data point to trigger a empty-cache detection.
+ * In this case, a preset/default loss value (function on RSSI) is used.
+ * <p>
+ * In normal uses, some RSSI values may never be seen due to channel randomness.
+ * However, the size of such empty RSSI chunk in normal use is generally 1~2.
+ */
+ private static final int BSSID_STAT_EMPTY_COUNT = 3;
+
+ /**
+ * Sample interval for packet loss statistics, in msec.
+ * <p>
+ * Smaller interval is more accurate but increases sampling cost (battery consumption).
+ */
+ private static final long LINK_SAMPLING_INTERVAL_MS = 1 * 1000;
+
+ /**
+ * Coefficients (alpha) for moving average for packet loss tracking.
+ * Must be within (0.0, 1.0).
+ * <p>
+ * Equivalent number of samples: N = 2 / alpha - 1 .
+ * We want the historic loss to base on more data points to be statistically reliable.
+ * We want the current instant loss to base on less data points to be responsive.
+ */
+ private static final double EXP_COEFFICIENT_RECORD = 0.1;
+
+ /**
+ * See {@link #EXP_COEFFICIENT_RECORD}.
+ */
+ private static final double EXP_COEFFICIENT_MONITOR = 0.5;
+
+ /**
+ * Thresholds for sending good/poor link notifications, in packet loss %.
+ * Good threshold must be smaller than poor threshold.
+ * Use smaller poor threshold to avoid WiFi more aggressively.
+ * Use smaller good threshold to bring back WiFi more conservatively.
+ * <p>
+ * When approaching the boundary, loss ratio jumps significantly within a few dBs.
+ * 50% loss threshold is a good balance between accuracy and reponsiveness.
+ * <=10% good threshold is a safe value to avoid jumping back to WiFi too easily.
+ */
+ private static final double POOR_LINK_LOSS_THRESHOLD = 0.5;
+
+ /**
+ * See {@link #POOR_LINK_LOSS_THRESHOLD}.
+ */
+ private static final double GOOD_LINK_LOSS_THRESHOLD = 0.1;
+
+ /**
+ * Number of samples to confirm before sending a poor link notification.
+ * Response time = confirm_count * sample_interval .
+ * <p>
+ * A smaller threshold improves response speed but may suffer from randomness.
+ * According to experiments, 3~5 are good values to achieve a balance.
+ * These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}.
+ */
+ private static final int POOR_LINK_SAMPLE_COUNT = 3;
+
+ /**
+ * Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness.
+ * <p>
+ * According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive.
+ */
+ private static final double POOR_LINK_MIN_VOLUME = 2.0 * LINK_SAMPLING_INTERVAL_MS / 1000.0;
+
+ /**
+ * When a poor link is detected, we scan over this range (based on current
+ * poor link RSSI) for a target RSSI that satisfies a target packet loss.
+ * Refer to {@link #GOOD_LINK_TARGET}.
+ * <p>
+ * We want range_min not too small to avoid jumping back to WiFi too easily.
+ */
+ private static final int GOOD_LINK_RSSI_RANGE_MIN = 3;
+
+ /**
+ * See {@link #GOOD_LINK_RSSI_RANGE_MIN}.
+ */
+ private static final int GOOD_LINK_RSSI_RANGE_MAX = 20;
+
+ /**
+ * Adaptive good link target to avoid flapping.
+ * When a poor link is detected, a good link target is calculated as follows:
+ * <p>
+ * targetRSSI = min { rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i],
+ * where rssi is within the above GOOD_LINK_RSSI_RANGE.
+ * targetCount = sample_count[i] .
+ * <p>
+ * While WiFi is being avoided, we keep monitoring its signal strength.
+ * Good link notification is sent when we see current RSSI >= targetRSSI
+ * for targetCount consecutive times.
+ * <p>
+ * Index i is incremented each time after a poor link detection.
+ * Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago.
+ * <p>
+ * Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping.
+ * In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve.
+ * Avoid using it unless flapping is really bad (say, last poor link is < 1 min ago).
+ */
+ private static final GoodLinkTarget[] GOOD_LINK_TARGET = {
+ /* rssi_adj, sample_count, reduce_time */
+ new GoodLinkTarget( 0, 3, 30 * 60000 ),
+ new GoodLinkTarget( 3, 5, 5 * 60000 ),
+ new GoodLinkTarget( 6, 10, 1 * 60000 ),
+ new GoodLinkTarget( 9, 30, 0 * 60000 ),
+ };
+
+ /**
+ * The max time to avoid a BSSID, to prevent avoiding forever.
+ * If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i]
+ * <p>
+ * It is unusual to experience high packet loss at high RSSI. Something unusual must be
+ * happening (e.g. strong interference). For higher signal strengths, we set the avoidance
+ * time to be low to allow for quick turn around from temporary interference.
+ * <p>
+ * See {@link BssidStatistics#poorLinkDetected}.
+ */
+ private static final MaxAvoidTime[] MAX_AVOID_TIME = {
+ /* max_time, min_rssi */
+ new MaxAvoidTime( 30 * 60000, -200 ),
+ new MaxAvoidTime( 5 * 60000, -70 ),
+ new MaxAvoidTime( 0 * 60000, -55 ),
+ };
+
+ /* Framework related */
+ private Context mContext;
+ private ContentResolver mContentResolver;
+ private WifiManager mWifiManager;
+ private IntentFilter mIntentFilter;
+ private BroadcastReceiver mBroadcastReceiver;
+ private AsyncChannel mWsmChannel = new AsyncChannel();
+ private WifiInfo mWifiInfo;
+ private LinkProperties mLinkProperties;
+
+ /* System settingss related */
+ private static boolean sWifiOnly = false;
+ private boolean mPoorNetworkDetectionEnabled;
+
+ /* Poor link detection related */
+ private LruCache<String, BssidStatistics> mBssidCache =
+ new LruCache<String, BssidStatistics>(BSSID_STAT_CACHE_SIZE);
+ private int mRssiFetchToken = 0;
+ private int mCurrentSignalLevel;
+ private BssidStatistics mCurrentBssid;
+ private VolumeWeightedEMA mCurrentLoss;
+ private boolean mIsScreenOn = true;
+ private static double sPresetLoss[];
+
+ /* WiFi watchdog state machine related */
+ private DefaultState mDefaultState = new DefaultState();
+ private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
+ private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
+ private NotConnectedState mNotConnectedState = new NotConnectedState();
+ private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState();
+ private ConnectedState mConnectedState = new ConnectedState();
+ private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
+ private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState();
+ private OnlineState mOnlineState = new OnlineState();
+
+ /**
+ * STATE MAP
+ * Default
+ * / \
+ * Disabled Enabled
+ * / \ \
+ * NotConnected Verifying Connected
+ * /---------\
+ * (all other states)
+ */
+ private WifiWatchdogStateMachine(Context context) {
+ super("WifiWatchdogStateMachine");
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mWsmChannel.connectSync(mContext, getHandler(),
+ mWifiManager.getWifiStateMachineMessenger());
+
+ setupNetworkReceiver();
+
+ // the content observer to listen needs a handler
+ registerForSettingsChanges();
+ registerForWatchdogToggle();
+ addState(mDefaultState);
+ addState(mWatchdogDisabledState, mDefaultState);
+ addState(mWatchdogEnabledState, mDefaultState);
+ addState(mNotConnectedState, mWatchdogEnabledState);
+ addState(mVerifyingLinkState, mWatchdogEnabledState);
+ addState(mConnectedState, mWatchdogEnabledState);
+ addState(mOnlineWatchState, mConnectedState);
+ addState(mLinkMonitoringState, mConnectedState);
+ addState(mOnlineState, mConnectedState);
+
+ if (isWatchdogEnabled()) {
+ setInitialState(mNotConnectedState);
+ } else {
+ setInitialState(mWatchdogDisabledState);
+ }
+ setLogRecSize(25);
+ setLogOnlyTransitions(true);
+ updateSettings();
+ }
+
+ public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
+ ContentResolver contentResolver = context.getContentResolver();
+
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
+
+ // Watchdog is always enabled. Poor network detection can be seperately turned on/off
+ // TODO: Remove this setting & clean up state machine since we always have
+ // watchdog in an enabled state
+ putSettingsGlobalBoolean(contentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
+
+ WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
+ wwsm.start();
+ return wwsm;
+ }
+
+ private void setupNetworkReceiver() {
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+ obtainMessage(EVENT_RSSI_CHANGE,
+ intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget();
+ } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
+ sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent);
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
+ } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ sendMessage(EVENT_SCREEN_ON);
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ sendMessage(EVENT_SCREEN_OFF);
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra(
+ WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
+ }
+ }
+ };
+
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+ }
+
+ /**
+ * Observes the watchdog on/off setting, and takes action when changed.
+ */
+ private void registerForWatchdogToggle() {
+ ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendMessage(EVENT_WATCHDOG_TOGGLED);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_ON),
+ false, contentObserver);
+ }
+
+ /**
+ * Observes watchdogs secure setting changes.
+ */
+ private void registerForSettingsChanges() {
+ ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED),
+ false, contentObserver);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mWifiInfo: [" + mWifiInfo + "]");
+ pw.println("mLinkProperties: [" + mLinkProperties + "]");
+ pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
+ pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]");
+ }
+
+ private boolean isWatchdogEnabled() {
+ boolean ret = getSettingsGlobalBoolean(
+ mContentResolver, Settings.Global.WIFI_WATCHDOG_ON, true);
+ if (DBG) logd("Watchdog enabled " + ret);
+ return ret;
+ }
+
+ private void updateSettings() {
+ if (DBG) logd("Updating secure settings");
+
+ // disable poor network avoidance
+ if (sWifiOnly) {
+ logd("Disabling poor network avoidance for wi-fi only device");
+ mPoorNetworkDetectionEnabled = false;
+ } else {
+ mPoorNetworkDetectionEnabled = getSettingsGlobalBoolean(mContentResolver,
+ Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+ WifiManager.DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED);
+ }
+ }
+
+ /**
+ * Default state, guard for unhandled messages.
+ */
+ class DefaultState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_SETTINGS_CHANGE:
+ updateSettings();
+ if (DBG) logd("Updating wifi-watchdog secure settings");
+ break;
+ case EVENT_RSSI_CHANGE:
+ mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+ break;
+ case EVENT_WIFI_RADIO_STATE_CHANGE:
+ case EVENT_NETWORK_STATE_CHANGE:
+ case EVENT_SUPPLICANT_STATE_CHANGE:
+ case EVENT_BSSID_CHANGE:
+ case CMD_RSSI_FETCH:
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ // ignore
+ break;
+ case EVENT_SCREEN_ON:
+ mIsScreenOn = true;
+ break;
+ case EVENT_SCREEN_OFF:
+ mIsScreenOn = false;
+ break;
+ default:
+ loge("Unhandled message " + msg + " in state " + getCurrentState().getName());
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * WiFi watchdog is disabled by the setting.
+ */
+ class WatchdogDisabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_TOGGLED:
+ if (isWatchdogEnabled())
+ transitionTo(mNotConnectedState);
+ return HANDLED;
+ case EVENT_NETWORK_STATE_CHANGE:
+ Intent intent = (Intent) msg.obj;
+ NetworkInfo networkInfo = (NetworkInfo)
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+
+ switch (networkInfo.getDetailedState()) {
+ case VERIFYING_POOR_LINK:
+ if (DBG) logd("Watchdog disabled, verify link");
+ sendLinkStatusNotification(true);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /**
+ * WiFi watchdog is enabled by the setting.
+ */
+ class WatchdogEnabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Intent intent;
+ switch (msg.what) {
+ case EVENT_WATCHDOG_TOGGLED:
+ if (!isWatchdogEnabled())
+ transitionTo(mWatchdogDisabledState);
+ break;
+
+ case EVENT_NETWORK_STATE_CHANGE:
+ intent = (Intent) msg.obj;
+ NetworkInfo networkInfo =
+ (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ if (DBG) logd("Network state change " + networkInfo.getDetailedState());
+
+ mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+ updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null);
+
+ switch (networkInfo.getDetailedState()) {
+ case VERIFYING_POOR_LINK:
+ mLinkProperties = (LinkProperties) intent.getParcelableExtra(
+ WifiManager.EXTRA_LINK_PROPERTIES);
+ if (mPoorNetworkDetectionEnabled) {
+ if (mWifiInfo == null || mCurrentBssid == null) {
+ loge("Ignore, wifiinfo " + mWifiInfo +" bssid " + mCurrentBssid);
+ sendLinkStatusNotification(true);
+ } else {
+ transitionTo(mVerifyingLinkState);
+ }
+ } else {
+ sendLinkStatusNotification(true);
+ }
+ break;
+ case CONNECTED:
+ transitionTo(mOnlineWatchState);
+ break;
+ default:
+ transitionTo(mNotConnectedState);
+ break;
+ }
+ break;
+
+ case EVENT_SUPPLICANT_STATE_CHANGE:
+ intent = (Intent) msg.obj;
+ SupplicantState supplicantState = (SupplicantState) intent.getParcelableExtra(
+ WifiManager.EXTRA_NEW_STATE);
+ if (supplicantState == SupplicantState.COMPLETED) {
+ mWifiInfo = mWifiManager.getConnectionInfo();
+ updateCurrentBssid(mWifiInfo.getBSSID());
+ }
+ break;
+
+ case EVENT_WIFI_RADIO_STATE_CHANGE:
+ if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) {
+ transitionTo(mNotConnectedState);
+ }
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+
+ return HANDLED;
+ }
+ }
+
+ /**
+ * WiFi is disconnected.
+ */
+ class NotConnectedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+ }
+
+ /**
+ * WiFi is connected, but waiting for good link detection message.
+ */
+ class VerifyingLinkState extends State {
+
+ private int mSampleCount;
+
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mSampleCount = 0;
+ mCurrentBssid.newLinkDetected();
+ sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_SETTINGS_CHANGE:
+ updateSettings();
+ if (!mPoorNetworkDetectionEnabled) {
+ sendLinkStatusNotification(true);
+ }
+ break;
+
+ case EVENT_BSSID_CHANGE:
+ transitionTo(mVerifyingLinkState);
+ break;
+
+ case CMD_RSSI_FETCH:
+ if (msg.arg1 == mRssiFetchToken) {
+ mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);
+ sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
+ LINK_SAMPLING_INTERVAL_MS);
+ }
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;
+ int rssi = info.rssi;
+ if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi);
+
+ long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime();
+ if (time <= 0) {
+ // max avoidance time is met
+ if (DBG) logd("Max avoid time elapsed");
+ sendLinkStatusNotification(true);
+ } else {
+ if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) {
+ if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) {
+ // link is good again
+ if (DBG) logd("Good link detected, rssi=" + rssi);
+ mCurrentBssid.mBssidAvoidTimeMax = 0;
+ sendLinkStatusNotification(true);
+ }
+ } else {
+ mSampleCount = 0;
+ if (DBG) logd("Link is still poor, time left=" + time);
+ }
+ }
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ if (DBG) logd("RSSI_FETCH_FAILED");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * WiFi is connected and link is verified.
+ */
+ class ConnectedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_SETTINGS_CHANGE:
+ updateSettings();
+ if (mPoorNetworkDetectionEnabled) {
+ transitionTo(mOnlineWatchState);
+ } else {
+ transitionTo(mOnlineState);
+ }
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ /**
+ * RSSI is high enough and don't need link monitoring.
+ */
+ class OnlineWatchState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ if (mPoorNetworkDetectionEnabled) {
+ // treat entry as an rssi change
+ handleRssiChange();
+ } else {
+ transitionTo(mOnlineState);
+ }
+ }
+
+ private void handleRssiChange() {
+ if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD && mCurrentBssid != null) {
+ transitionTo(mLinkMonitoringState);
+ } else {
+ // stay here
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RSSI_CHANGE:
+ mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+ handleRssiChange();
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * Keep sampling the link and monitor any poor link situation.
+ */
+ class LinkMonitoringState extends State {
+
+ private int mSampleCount;
+
+ private int mLastRssi;
+ private int mLastTxGood;
+ private int mLastTxBad;
+
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mSampleCount = 0;
+ mCurrentLoss = new VolumeWeightedEMA(EXP_COEFFICIENT_MONITOR);
+ sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RSSI_CHANGE:
+ mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+ if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) {
+ // stay here;
+ } else {
+ // we don't need frequent RSSI monitoring any more
+ transitionTo(mOnlineWatchState);
+ }
+ break;
+
+ case EVENT_BSSID_CHANGE:
+ transitionTo(mLinkMonitoringState);
+ break;
+
+ case CMD_RSSI_FETCH:
+ if (!mIsScreenOn) {
+ transitionTo(mOnlineState);
+ } else if (msg.arg1 == mRssiFetchToken) {
+ mWsmChannel.sendMessage(WifiManager.RSSI_PKTCNT_FETCH);
+ sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
+ LINK_SAMPLING_INTERVAL_MS);
+ }
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED:
+ RssiPacketCountInfo info = (RssiPacketCountInfo) msg.obj;
+ int rssi = info.rssi;
+ int mrssi = (mLastRssi + rssi) / 2;
+ int txbad = info.txbad;
+ int txgood = info.txgood;
+ if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi + " mrssi=" + mrssi + " txbad="
+ + txbad + " txgood=" + txgood);
+
+ // skip the first data point as we want incremental values
+ long now = SystemClock.elapsedRealtime();
+ if (now - mCurrentBssid.mLastTimeSample < LINK_SAMPLING_INTERVAL_MS * 2) {
+
+ // update packet loss statistics
+ int dbad = txbad - mLastTxBad;
+ int dgood = txgood - mLastTxGood;
+ int dtotal = dbad + dgood;
+
+ if (dtotal > 0) {
+ // calculate packet loss in the last sampling interval
+ double loss = ((double) dbad) / ((double) dtotal);
+
+ mCurrentLoss.update(loss, dtotal);
+
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Incremental loss=" + dbad + "/" + dtotal + " Current loss="
+ + df.format(mCurrentLoss.mValue * 100) + "% volume="
+ + df.format(mCurrentLoss.mVolume));
+ }
+
+ mCurrentBssid.updateLoss(mrssi, loss, dtotal);
+
+ // check for high packet loss and send poor link notification
+ if (mCurrentLoss.mValue > POOR_LINK_LOSS_THRESHOLD
+ && mCurrentLoss.mVolume > POOR_LINK_MIN_VOLUME) {
+ if (++mSampleCount >= POOR_LINK_SAMPLE_COUNT)
+ if (mCurrentBssid.poorLinkDetected(rssi)) {
+ sendLinkStatusNotification(false);
+ ++mRssiFetchToken;
+ }
+ } else {
+ mSampleCount = 0;
+ }
+ }
+ }
+
+ mCurrentBssid.mLastTimeSample = now;
+ mLastTxBad = txbad;
+ mLastTxGood = txgood;
+ mLastRssi = rssi;
+ break;
+
+ case WifiManager.RSSI_PKTCNT_FETCH_FAILED:
+ // can happen if we are waiting to get a disconnect notification
+ if (DBG) logd("RSSI_FETCH_FAILED");
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ /**
+ * Child state of ConnectedState indicating that we are online and there is nothing to do.
+ */
+ class OnlineState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_SCREEN_ON:
+ mIsScreenOn = true;
+ if (mPoorNetworkDetectionEnabled)
+ transitionTo(mOnlineWatchState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private void updateCurrentBssid(String bssid) {
+ if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null"));
+
+ // if currently not connected, then set current BSSID to null
+ if (bssid == null) {
+ if (mCurrentBssid == null) return;
+ mCurrentBssid = null;
+ if (DBG) logd("BSSID changed");
+ sendMessage(EVENT_BSSID_CHANGE);
+ return;
+ }
+
+ // if it is already the current BSSID, then done
+ if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return;
+
+ // search for the new BSSID in the cache, add to cache if not found
+ mCurrentBssid = mBssidCache.get(bssid);
+ if (mCurrentBssid == null) {
+ mCurrentBssid = new BssidStatistics(bssid);
+ mBssidCache.put(bssid, mCurrentBssid);
+ }
+
+ // send BSSID change notification
+ if (DBG) logd("BSSID changed");
+ sendMessage(EVENT_BSSID_CHANGE);
+ }
+
+ private int calculateSignalLevel(int rssi) {
+ int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS);
+ if (DBG)
+ logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel);
+ return signalLevel;
+ }
+
+ private void sendLinkStatusNotification(boolean isGood) {
+ if (DBG) logd("########################################");
+ if (isGood) {
+ mWsmChannel.sendMessage(GOOD_LINK_DETECTED);
+ if (mCurrentBssid != null) {
+ mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime();
+ }
+ if (DBG) logd("Good link notification is sent");
+ } else {
+ mWsmChannel.sendMessage(POOR_LINK_DETECTED);
+ if (mCurrentBssid != null) {
+ mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime();
+ }
+ logd("Poor link notification is sent");
+ }
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value as a
+ * boolean. Note that internally setting values are always stored as
+ * strings; this function converts the string to a boolean for you. The
+ * default value will be returned if the setting is not defined or not a
+ * valid boolean.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ * @return The setting's current value, or 'def' if it is not defined or not
+ * a valid boolean.
+ */
+ private static boolean getSettingsGlobalBoolean(ContentResolver cr, String name, boolean def) {
+ return Settings.Global.getInt(cr, name, def ? 1 : 0) == 1;
+ }
+
+ /**
+ * Convenience function for updating a single settings value as an integer.
+ * This will either create a new entry in the table if the given name does
+ * not exist, or modify the value of the existing row with that name. Note
+ * that internally setting values are always stored as strings, so this
+ * function converts the given value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ private static boolean putSettingsGlobalBoolean(ContentResolver cr, String name, boolean value) {
+ return Settings.Global.putInt(cr, name, value ? 1 : 0);
+ }
+
+ /**
+ * Bundle of good link count parameters
+ */
+ private static class GoodLinkTarget {
+ public final int RSSI_ADJ_DBM;
+ public final int SAMPLE_COUNT;
+ public final int REDUCE_TIME_MS;
+ public GoodLinkTarget(int adj, int count, int time) {
+ RSSI_ADJ_DBM = adj;
+ SAMPLE_COUNT = count;
+ REDUCE_TIME_MS = time;
+ }
+ }
+
+ /**
+ * Bundle of max avoidance time parameters
+ */
+ private static class MaxAvoidTime {
+ public final int TIME_MS;
+ public final int MIN_RSSI_DBM;
+ public MaxAvoidTime(int time, int rssi) {
+ TIME_MS = time;
+ MIN_RSSI_DBM = rssi;
+ }
+ }
+
+ /**
+ * Volume-weighted Exponential Moving Average (V-EMA)
+ * - volume-weighted: each update has its own weight (number of packets)
+ * - exponential: O(1) time and O(1) space for both update and query
+ * - moving average: reflect most recent results and expire old ones
+ */
+ private class VolumeWeightedEMA {
+ private double mValue;
+ private double mVolume;
+ private double mProduct;
+ private final double mAlpha;
+
+ public VolumeWeightedEMA(double coefficient) {
+ mValue = 0.0;
+ mVolume = 0.0;
+ mProduct = 0.0;
+ mAlpha = coefficient;
+ }
+
+ public void update(double newValue, int newVolume) {
+ if (newVolume <= 0) return;
+ // core update formulas
+ double newProduct = newValue * newVolume;
+ mProduct = mAlpha * newProduct + (1 - mAlpha) * mProduct;
+ mVolume = mAlpha * newVolume + (1 - mAlpha) * mVolume;
+ mValue = mProduct / mVolume;
+ }
+ }
+
+ /**
+ * Record (RSSI -> pakce loss %) mappings of one BSSID
+ */
+ private class BssidStatistics {
+
+ /* MAC address of this BSSID */
+ private final String mBssid;
+
+ /* RSSI -> packet loss % mappings */
+ private VolumeWeightedEMA[] mEntries;
+ private int mRssiBase;
+ private int mEntriesSize;
+
+ /* Target to send good link notification, set when poor link is detected */
+ private int mGoodLinkTargetRssi;
+ private int mGoodLinkTargetCount;
+
+ /* Index of GOOD_LINK_TARGET array */
+ private int mGoodLinkTargetIndex;
+
+ /* Timestamps of some last events */
+ private long mLastTimeSample;
+ private long mLastTimeGood;
+ private long mLastTimePoor;
+
+ /* Max time to avoid this BSSID */
+ private long mBssidAvoidTimeMax;
+
+ /**
+ * Constructor
+ *
+ * @param bssid is the address of this BSSID
+ */
+ public BssidStatistics(String bssid) {
+ this.mBssid = bssid;
+ mRssiBase = BSSID_STAT_RANGE_LOW_DBM;
+ mEntriesSize = BSSID_STAT_RANGE_HIGH_DBM - BSSID_STAT_RANGE_LOW_DBM + 1;
+ mEntries = new VolumeWeightedEMA[mEntriesSize];
+ for (int i = 0; i < mEntriesSize; i++)
+ mEntries[i] = new VolumeWeightedEMA(EXP_COEFFICIENT_RECORD);
+ }
+
+ /**
+ * Update this BSSID cache
+ *
+ * @param rssi is the RSSI
+ * @param value is the new instant loss value at this RSSI
+ * @param volume is the volume for this single update
+ */
+ public void updateLoss(int rssi, double value, int volume) {
+ if (volume <= 0) return;
+ int index = rssi - mRssiBase;
+ if (index < 0 || index >= mEntriesSize) return;
+ mEntries[index].update(value, volume);
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Cache updated: loss[" + rssi + "]=" + df.format(mEntries[index].mValue * 100)
+ + "% volume=" + df.format(mEntries[index].mVolume));
+ }
+ }
+
+ /**
+ * Get preset loss if the cache has insufficient data, observed from experiments.
+ *
+ * @param rssi is the input RSSI
+ * @return preset loss of the given RSSI
+ */
+ public double presetLoss(int rssi) {
+ if (rssi <= -90) return 1.0;
+ if (rssi > 0) return 0.0;
+
+ if (sPresetLoss == null) {
+ // pre-calculate all preset losses only once, then reuse them
+ final int size = 90;
+ sPresetLoss = new double[size];
+ for (int i = 0; i < size; i++) sPresetLoss[i] = 1.0 / Math.pow(90 - i, 1.5);
+ }
+ return sPresetLoss[-rssi];
+ }
+
+ /**
+ * A poor link is detected, calculate a target RSSI to bring WiFi back.
+ *
+ * @param rssi is the current RSSI
+ * @return true iff the current BSSID should be avoided
+ */
+ public boolean poorLinkDetected(int rssi) {
+ if (DBG) logd("Poor link detected, rssi=" + rssi);
+
+ long now = SystemClock.elapsedRealtime();
+ long lastGood = now - mLastTimeGood;
+ long lastPoor = now - mLastTimePoor;
+
+ // reduce the difficulty of good link target if last avoidance was long time ago
+ while (mGoodLinkTargetIndex > 0
+ && lastPoor >= GOOD_LINK_TARGET[mGoodLinkTargetIndex - 1].REDUCE_TIME_MS)
+ mGoodLinkTargetIndex--;
+ mGoodLinkTargetCount = GOOD_LINK_TARGET[mGoodLinkTargetIndex].SAMPLE_COUNT;
+
+ // scan for a target RSSI at which the link is good
+ int from = rssi + GOOD_LINK_RSSI_RANGE_MIN;
+ int to = rssi + GOOD_LINK_RSSI_RANGE_MAX;
+ mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD);
+ mGoodLinkTargetRssi += GOOD_LINK_TARGET[mGoodLinkTargetIndex].RSSI_ADJ_DBM;
+ if (mGoodLinkTargetIndex < GOOD_LINK_TARGET.length - 1) mGoodLinkTargetIndex++;
+
+ // calculate max avoidance time to prevent avoiding forever
+ int p = 0, pmax = MAX_AVOID_TIME.length - 1;
+ while (p < pmax && rssi >= MAX_AVOID_TIME[p + 1].MIN_RSSI_DBM) p++;
+ long avoidMax = MAX_AVOID_TIME[p].TIME_MS;
+
+ // don't avoid if max avoidance time is 0 (RSSI is super high)
+ if (avoidMax <= 0) return false;
+
+ // set max avoidance time, send poor link notification
+ mBssidAvoidTimeMax = now + avoidMax;
+
+ if (DBG) logd("goodRssi=" + mGoodLinkTargetRssi + " goodCount=" + mGoodLinkTargetCount
+ + " lastGood=" + lastGood + " lastPoor=" + lastPoor + " avoidMax=" + avoidMax);
+
+ return true;
+ }
+
+ /**
+ * A new BSSID is connected, recalculate target RSSI threshold
+ */
+ public void newLinkDetected() {
+ // if this BSSID is currently being avoided, the reuse those values
+ if (mBssidAvoidTimeMax > 0) {
+ if (DBG) logd("Previous avoidance still in effect, rssi=" + mGoodLinkTargetRssi
+ + " count=" + mGoodLinkTargetCount);
+ return;
+ }
+
+ // calculate a new RSSI threshold for new link verifying
+ int from = BSSID_STAT_RANGE_LOW_DBM;
+ int to = BSSID_STAT_RANGE_HIGH_DBM;
+ mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD);
+ mGoodLinkTargetCount = 1;
+ mBssidAvoidTimeMax = SystemClock.elapsedRealtime() + MAX_AVOID_TIME[0].TIME_MS;
+ if (DBG) logd("New link verifying target set, rssi=" + mGoodLinkTargetRssi + " count="
+ + mGoodLinkTargetCount);
+ }
+
+ /**
+ * Return the first RSSI within the range where loss[rssi] < threshold
+ *
+ * @param from start scanning from this RSSI
+ * @param to stop scanning at this RSSI
+ * @param threshold target threshold for scanning
+ * @return target RSSI
+ */
+ public int findRssiTarget(int from, int to, double threshold) {
+ from -= mRssiBase;
+ to -= mRssiBase;
+ int emptyCount = 0;
+ int d = from < to ? 1 : -1;
+ for (int i = from; i != to; i += d)
+ // don't use a data point if it volume is too small (statistically unreliable)
+ if (i >= 0 && i < mEntriesSize && mEntries[i].mVolume > 1.0) {
+ emptyCount = 0;
+ if (mEntries[i].mValue < threshold) {
+ // scan target found
+ int rssi = mRssiBase + i;
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Scan target found: rssi=" + rssi + " threshold="
+ + df.format(threshold * 100) + "% value="
+ + df.format(mEntries[i].mValue * 100) + "% volume="
+ + df.format(mEntries[i].mVolume));
+ }
+ return rssi;
+ }
+ } else if (++emptyCount >= BSSID_STAT_EMPTY_COUNT) {
+ // cache has insufficient data around this RSSI, use preset loss instead
+ int rssi = mRssiBase + i;
+ double lossPreset = presetLoss(rssi);
+ if (lossPreset < threshold) {
+ if (DBG) {
+ DecimalFormat df = new DecimalFormat("#.##");
+ logd("Scan target found: rssi=" + rssi + " threshold="
+ + df.format(threshold * 100) + "% value="
+ + df.format(lossPreset * 100) + "% volume=preset");
+ }
+ return rssi;
+ }
+ }
+
+ return mRssiBase + to;
+ }
+ }
+}
diff --git a/services/java/com/android/server/wifi/p2p/WifiP2pService.java b/services/java/com/android/server/wifi/p2p/WifiP2pService.java
new file mode 100644
index 0000000..a00882d
--- /dev/null
+++ b/services/java/com/android/server/wifi/p2p/WifiP2pService.java
@@ -0,0 +1,2959 @@
+/*
+ * Copyright (C) 2011 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.p2p;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.DhcpResults;
+import android.net.DhcpStateMachine;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkInfo;
+import android.net.NetworkUtils;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.IWifiP2pManager;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pGroupList;
+import android.net.wifi.p2p.WifiP2pGroupList.GroupDeleteListener;
+import android.net.wifi.p2p.WifiP2pInfo;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.wifi.WifiMonitor;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiStateMachine;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+
+/**
+ * WifiP2pService includes a state machine to perform Wi-Fi p2p operations. Applications
+ * communicate with this service to issue device discovery and connectivity requests
+ * through the WifiP2pManager interface. The state machine communicates with the wifi
+ * driver through wpa_supplicant and handles the event responses through WifiMonitor.
+ *
+ * Note that the term Wifi when used without a p2p suffix refers to the client mode
+ * of Wifi operation
+ * @hide
+ */
+public class WifiP2pService extends IWifiP2pManager.Stub {
+ private static final String TAG = "WifiP2pService";
+ private static final boolean DBG = false;
+ private static final String NETWORKTYPE = "WIFI_P2P";
+
+ private Context mContext;
+ private String mInterface;
+ private Notification mNotification;
+
+ INetworkManagementService mNwService;
+ private DhcpStateMachine mDhcpStateMachine;
+
+ private P2pStateMachine mP2pStateMachine;
+ private AsyncChannel mReplyChannel = new AsyncChannel();
+ private AsyncChannel mWifiChannel;
+
+ private static final Boolean JOIN_GROUP = true;
+ private static final Boolean FORM_GROUP = false;
+
+ private static final Boolean RELOAD = true;
+ private static final Boolean NO_RELOAD = false;
+
+ /* Two minutes comes from the wpa_supplicant setting */
+ private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
+ private static int mGroupCreatingTimeoutIndex = 0;
+
+ private static final int DISABLE_P2P_WAIT_TIME_MS = 5 * 1000;
+ private static int mDisableP2pTimeoutIndex = 0;
+
+ /* Set a two minute discover timeout to avoid STA scans from being blocked */
+ private static final int DISCOVER_TIMEOUT_S = 120;
+
+ /* Idle time after a peer is gone when the group is torn down */
+ private static final int GROUP_IDLE_TIME_S = 10;
+
+ private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE;
+
+ /* Delayed message to timeout group creation */
+ public static final int GROUP_CREATING_TIMED_OUT = BASE + 1;
+
+ /* User accepted a peer request */
+ private static final int PEER_CONNECTION_USER_ACCEPT = BASE + 2;
+ /* User rejected a peer request */
+ private static final int PEER_CONNECTION_USER_REJECT = BASE + 3;
+ /* User wants to disconnect wifi in favour of p2p */
+ private static final int DROP_WIFI_USER_ACCEPT = BASE + 4;
+ /* User wants to keep his wifi connection and drop p2p */
+ private static final int DROP_WIFI_USER_REJECT = BASE + 5;
+ /* Delayed message to timeout p2p disable */
+ public static final int DISABLE_P2P_TIMED_OUT = BASE + 6;
+
+
+ /* Commands to the WifiStateMachine */
+ public static final int P2P_CONNECTION_CHANGED = BASE + 11;
+
+ /* These commands are used to temporarily disconnect wifi when we detect
+ * a frequency conflict which would make it impossible to have with p2p
+ * and wifi active at the same time.
+ *
+ * If the user chooses to disable wifi temporarily, we keep wifi disconnected
+ * until the p2p connection is done and terminated at which point we will
+ * bring back wifi up
+ *
+ * DISCONNECT_WIFI_REQUEST
+ * msg.arg1 = 1 enables temporary disconnect and 0 disables it.
+ */
+ public static final int DISCONNECT_WIFI_REQUEST = BASE + 12;
+ public static final int DISCONNECT_WIFI_RESPONSE = BASE + 13;
+
+ public static final int SET_MIRACAST_MODE = BASE + 14;
+
+ // During dhcp (and perhaps other times) we can't afford to drop packets
+ // but Discovery will switch our channel enough we will.
+ // msg.arg1 = ENABLED for blocking, DISABLED for resumed.
+ // msg.arg2 = msg to send when blocked
+ // msg.obj = StateMachine to send to when blocked
+ public static final int BLOCK_DISCOVERY = BASE + 15;
+
+ // set country code
+ public static final int SET_COUNTRY_CODE = BASE + 16;
+
+ public static final int ENABLED = 1;
+ public static final int DISABLED = 0;
+
+ private final boolean mP2pSupported;
+
+ private WifiP2pDevice mThisDevice = new WifiP2pDevice();
+
+ /* When a group has been explicitly created by an app, we persist the group
+ * even after all clients have been disconnected until an explicit remove
+ * is invoked */
+ private boolean mAutonomousGroup;
+
+ /* Invitation to join an existing p2p group */
+ private boolean mJoinExistingGroup;
+
+ /* Track whether we are in p2p discovery. This is used to avoid sending duplicate
+ * broadcasts
+ */
+ private boolean mDiscoveryStarted;
+ /* Track whether servcice/peer discovery is blocked in favor of other wifi actions
+ * (notably dhcp)
+ */
+ private boolean mDiscoveryBlocked;
+
+ // Supplicant doesn't like setting the same country code multiple times (it may drop
+ // current connected network), so we save the country code here to avoid redundency
+ private String mLastSetCountryCode;
+
+ /*
+ * remember if we were in a scan when it had to be stopped
+ */
+ private boolean mDiscoveryPostponed = false;
+
+ private NetworkInfo mNetworkInfo;
+
+ private boolean mTempoarilyDisconnectedWifi = false;
+
+ /* The transaction Id of service discovery request */
+ private byte mServiceTransactionId = 0;
+
+ /* Service discovery request ID of wpa_supplicant.
+ * null means it's not set yet. */
+ private String mServiceDiscReqId;
+
+ /* clients(application) information list. */
+ private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
+
+ /* Is chosen as a unique range to avoid conflict with
+ the range defined in Tethering.java */
+ private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
+ private static final String SERVER_ADDRESS = "192.168.49.1";
+
+ /**
+ * Error code definition.
+ * see the Table.8 in the WiFi Direct specification for the detail.
+ */
+ public static enum P2pStatus {
+ /* Success. */
+ SUCCESS,
+
+ /* The target device is currently unavailable. */
+ INFORMATION_IS_CURRENTLY_UNAVAILABLE,
+
+ /* Protocol error. */
+ INCOMPATIBLE_PARAMETERS,
+
+ /* The target device reached the limit of the number of the connectable device.
+ * For example, device limit or group limit is set. */
+ LIMIT_REACHED,
+
+ /* Protocol error. */
+ INVALID_PARAMETER,
+
+ /* Unable to accommodate request. */
+ UNABLE_TO_ACCOMMODATE_REQUEST,
+
+ /* Previous protocol error, or disruptive behavior. */
+ PREVIOUS_PROTOCOL_ERROR,
+
+ /* There is no common channels the both devices can use. */
+ NO_COMMON_CHANNEL,
+
+ /* Unknown p2p group. For example, Device A tries to invoke the previous persistent group,
+ * but device B has removed the specified credential already. */
+ UNKNOWN_P2P_GROUP,
+
+ /* Both p2p devices indicated an intent of 15 in group owner negotiation. */
+ BOTH_GO_INTENT_15,
+
+ /* Incompatible provisioning method. */
+ INCOMPATIBLE_PROVISIONING_METHOD,
+
+ /* Rejected by user */
+ REJECTED_BY_USER,
+
+ /* Unknown error */
+ UNKNOWN;
+
+ public static P2pStatus valueOf(int error) {
+ switch(error) {
+ case 0 :
+ return SUCCESS;
+ case 1:
+ return INFORMATION_IS_CURRENTLY_UNAVAILABLE;
+ case 2:
+ return INCOMPATIBLE_PARAMETERS;
+ case 3:
+ return LIMIT_REACHED;
+ case 4:
+ return INVALID_PARAMETER;
+ case 5:
+ return UNABLE_TO_ACCOMMODATE_REQUEST;
+ case 6:
+ return PREVIOUS_PROTOCOL_ERROR;
+ case 7:
+ return NO_COMMON_CHANNEL;
+ case 8:
+ return UNKNOWN_P2P_GROUP;
+ case 9:
+ return BOTH_GO_INTENT_15;
+ case 10:
+ return INCOMPATIBLE_PROVISIONING_METHOD;
+ case 11:
+ return REJECTED_BY_USER;
+ default:
+ return UNKNOWN;
+ }
+ }
+ }
+
+ public WifiP2pService(Context context) {
+ mContext = context;
+
+ //STOPSHIP: get this from native side
+ mInterface = "p2p0";
+ mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");
+
+ mP2pSupported = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_DIRECT);
+
+ mThisDevice.primaryDeviceType = mContext.getResources().getString(
+ com.android.internal.R.string.config_wifi_p2p_device_type);
+
+ mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported);
+ mP2pStateMachine.start();
+ }
+
+ public void connectivityServiceReady() {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNwService = INetworkManagementService.Stub.asInterface(b);
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
+ "WifiP2pService");
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
+ "WifiP2pService");
+ }
+
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "WifiP2pService");
+ }
+
+ /**
+ * Get a reference to handler. This is used by a client to establish
+ * an AsyncChannel communication with WifiP2pService
+ */
+ public Messenger getMessenger() {
+ enforceAccessPermission();
+ enforceChangePermission();
+ return new Messenger(mP2pStateMachine.getHandler());
+ }
+
+ /** This is used to provide information to drivers to optimize performance depending
+ * on the current mode of operation.
+ * 0 - disabled
+ * 1 - source operation
+ * 2 - sink operation
+ *
+ * As an example, the driver could reduce the channel dwell time during scanning
+ * when acting as a source or sink to minimize impact on miracast.
+ */
+ public void setMiracastMode(int mode) {
+ enforceConnectivityInternalPermission();
+ mP2pStateMachine.sendMessage(SET_MIRACAST_MODE, mode);
+ }
+
+ @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 WifiP2pService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ mP2pStateMachine.dump(fd, pw, args);
+ pw.println("mAutonomousGroup " + mAutonomousGroup);
+ pw.println("mJoinExistingGroup " + mJoinExistingGroup);
+ pw.println("mDiscoveryStarted " + mDiscoveryStarted);
+ pw.println("mNetworkInfo " + mNetworkInfo);
+ pw.println("mTempoarilyDisconnectedWifi " + mTempoarilyDisconnectedWifi);
+ pw.println("mServiceDiscReqId " + mServiceDiscReqId);
+ pw.println();
+ }
+
+
+ /**
+ * Handles interaction with WifiStateMachine
+ */
+ private class P2pStateMachine extends StateMachine {
+
+ private DefaultState mDefaultState = new DefaultState();
+ private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState();
+ private P2pDisablingState mP2pDisablingState = new P2pDisablingState();
+ private P2pDisabledState mP2pDisabledState = new P2pDisabledState();
+ private P2pEnablingState mP2pEnablingState = new P2pEnablingState();
+ private P2pEnabledState mP2pEnabledState = new P2pEnabledState();
+ // Inactive is when p2p is enabled with no connectivity
+ private InactiveState mInactiveState = new InactiveState();
+ private GroupCreatingState mGroupCreatingState = new GroupCreatingState();
+ private UserAuthorizingInviteRequestState mUserAuthorizingInviteRequestState
+ = new UserAuthorizingInviteRequestState();
+ private UserAuthorizingNegotiationRequestState mUserAuthorizingNegotiationRequestState
+ = new UserAuthorizingNegotiationRequestState();
+ private ProvisionDiscoveryState mProvisionDiscoveryState = new ProvisionDiscoveryState();
+ private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState();
+ private FrequencyConflictState mFrequencyConflictState =new FrequencyConflictState();
+
+ private GroupCreatedState mGroupCreatedState = new GroupCreatedState();
+ private UserAuthorizingJoinState mUserAuthorizingJoinState = new UserAuthorizingJoinState();
+ private OngoingGroupRemovalState mOngoingGroupRemovalState = new OngoingGroupRemovalState();
+
+ private WifiNative mWifiNative = new WifiNative(mInterface);
+ private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative);
+
+ private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
+ /* During a connection, supplicant can tell us that a device was lost. From a supplicant's
+ * perspective, the discovery stops during connection and it purges device since it does
+ * not get latest updates about the device without being in discovery state.
+ *
+ * From the framework perspective, the device is still there since we are connecting or
+ * connected to it. so we keep these devices in a separate list, so that they are removed
+ * when connection is cancelled or lost
+ */
+ private final WifiP2pDeviceList mPeersLostDuringConnection = new WifiP2pDeviceList();
+ private final WifiP2pGroupList mGroups = new WifiP2pGroupList(null,
+ new GroupDeleteListener() {
+ @Override
+ public void onDeleteGroup(int netId) {
+ if (DBG) logd("called onDeleteGroup() netId=" + netId);
+ mWifiNative.removeNetwork(netId);
+ mWifiNative.saveConfig();
+ sendP2pPersistentGroupsChangedBroadcast();
+ }
+ });
+ private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
+ private WifiP2pGroup mGroup;
+
+ // Saved WifiP2pConfig for an ongoing peer connection. This will never be null.
+ // The deviceAddress will be an empty string when the device is inactive
+ // or if it is connected without any ongoing join request
+ private WifiP2pConfig mSavedPeerConfig = new WifiP2pConfig();
+
+ // Saved WifiP2pGroup from invitation request
+ private WifiP2pGroup mSavedP2pGroup;
+
+ P2pStateMachine(String name, boolean p2pSupported) {
+ super(name);
+
+ addState(mDefaultState);
+ addState(mP2pNotSupportedState, mDefaultState);
+ addState(mP2pDisablingState, mDefaultState);
+ addState(mP2pDisabledState, mDefaultState);
+ addState(mP2pEnablingState, mDefaultState);
+ addState(mP2pEnabledState, mDefaultState);
+ addState(mInactiveState, mP2pEnabledState);
+ addState(mGroupCreatingState, mP2pEnabledState);
+ addState(mUserAuthorizingInviteRequestState, mGroupCreatingState);
+ addState(mUserAuthorizingNegotiationRequestState, mGroupCreatingState);
+ addState(mProvisionDiscoveryState, mGroupCreatingState);
+ addState(mGroupNegotiationState, mGroupCreatingState);
+ addState(mFrequencyConflictState, mGroupCreatingState);
+ addState(mGroupCreatedState, mP2pEnabledState);
+ addState(mUserAuthorizingJoinState, mGroupCreatedState);
+ addState(mOngoingGroupRemovalState, mGroupCreatedState);
+
+ if (p2pSupported) {
+ setInitialState(mP2pDisabledState);
+ } else {
+ setInitialState(mP2pNotSupportedState);
+ }
+ setLogRecSize(50);
+ setLogOnlyTransitions(true);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ if (DBG) logd("Full connection with WifiStateMachine established");
+ mWifiChannel = (AsyncChannel) message.obj;
+ } else {
+ loge("Full connection failure, error = " + message.arg1);
+ mWifiChannel = null;
+ }
+ break;
+
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ if (message.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+ loge("Send failed, client connection lost");
+ } else {
+ loge("Client connection lost with reason: " + message.arg1);
+ }
+ mWifiChannel = null;
+ break;
+
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+ AsyncChannel ac = new AsyncChannel();
+ ac.connect(mContext, getHandler(), message.replyTo);
+ break;
+ case BLOCK_DISCOVERY:
+ mDiscoveryBlocked = (message.arg1 == ENABLED ? true : false);
+ // always reset this - we went to a state that doesn't support discovery so
+ // it would have stopped regardless
+ mDiscoveryPostponed = false;
+ if (mDiscoveryBlocked) {
+ try {
+ StateMachine m = (StateMachine)message.obj;
+ m.sendMessage(message.arg2);
+ } catch (Exception e) {
+ loge("unable to send BLOCK_DISCOVERY response: " + e);
+ }
+ }
+ break;
+ case WifiP2pManager.DISCOVER_PEERS:
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CONNECT:
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CANCEL_CONNECT:
+ replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CREATE_GROUP:
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_GROUP:
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.SET_DEVICE_NAME:
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.SET_WFD_INFO:
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REQUEST_PEERS:
+ replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
+ new WifiP2pDeviceList(mPeers));
+ break;
+ case WifiP2pManager.REQUEST_CONNECTION_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
+ new WifiP2pInfo(mWifiP2pInfo));
+ break;
+ case WifiP2pManager.REQUEST_GROUP_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
+ mGroup != null ? new WifiP2pGroup(mGroup) : null);
+ break;
+ case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
+ replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
+ new WifiP2pGroupList(mGroups, null));
+ break;
+ case WifiP2pManager.START_WPS:
+ replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ // Ignore
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ case WifiMonitor.SCAN_RESULTS_EVENT:
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ case WifiMonitor.NETWORK_CONNECTION_EVENT:
+ case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+ case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+ case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+ case WifiMonitor.WPS_SUCCESS_EVENT:
+ case WifiMonitor.WPS_FAIL_EVENT:
+ case WifiMonitor.WPS_OVERLAP_EVENT:
+ case WifiMonitor.WPS_TIMEOUT_EVENT:
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ case WifiMonitor.P2P_FIND_STOPPED_EVENT:
+ case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+ case PEER_CONNECTION_USER_ACCEPT:
+ case PEER_CONNECTION_USER_REJECT:
+ case DISCONNECT_WIFI_RESPONSE:
+ case DROP_WIFI_USER_ACCEPT:
+ case DROP_WIFI_USER_REJECT:
+ case GROUP_CREATING_TIMED_OUT:
+ case DISABLE_P2P_TIMED_OUT:
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ case DhcpStateMachine.CMD_ON_QUIT:
+ case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
+ case SET_MIRACAST_MODE:
+ case WifiP2pManager.START_LISTEN:
+ case WifiP2pManager.STOP_LISTEN:
+ case WifiP2pManager.SET_CHANNEL:
+ case SET_COUNTRY_CODE:
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ // Enable is lazy and has no response
+ break;
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ // If we end up handling in default, p2p is not enabled
+ mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+ break;
+ /* unexpected group created, remove */
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ loge("Unexpected group creation, remove " + mGroup);
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ break;
+ // A group formation failure is always followed by
+ // a group removed event. Flushing things at group formation
+ // failure causes supplicant issues. Ignore right now.
+ case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+ break;
+ default:
+ loge("Unhandled message " + message);
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pNotSupportedState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case WifiP2pManager.DISCOVER_PEERS:
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CONNECT:
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CANCEL_CONNECT:
+ replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CREATE_GROUP:
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_GROUP:
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.SET_DEVICE_NAME:
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.SET_WFD_INFO:
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.START_WPS:
+ replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.START_LISTEN:
+ replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.STOP_LISTEN:
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pDisablingState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ sendMessageDelayed(obtainMessage(DISABLE_P2P_TIMED_OUT,
+ ++mDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ if (DBG) logd("p2p socket connection lost");
+ transitionTo(mP2pDisabledState);
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ deferMessage(message);
+ break;
+ case DISABLE_P2P_TIMED_OUT:
+ if (mGroupCreatingTimeoutIndex == message.arg1) {
+ loge("P2p disable timed out");
+ transitionTo(mP2pDisabledState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+ }
+ }
+
+ class P2pDisabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ try {
+ mNwService.setInterfaceUp(mInterface);
+ } catch (RemoteException re) {
+ loge("Unable to change interface settings: " + re);
+ } catch (IllegalStateException ie) {
+ loge("Unable to change interface settings: " + ie);
+ }
+ mWifiMonitor.startMonitoring();
+ transitionTo(mP2pEnablingState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pEnablingState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.SUP_CONNECTION_EVENT:
+ if (DBG) logd("P2p socket connection successful");
+ transitionTo(mInactiveState);
+ break;
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ loge("P2p socket connection failed");
+ transitionTo(mP2pDisabledState);
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ deferMessage(message);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class P2pEnabledState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ sendP2pStateChangedBroadcast(true);
+ mNetworkInfo.setIsAvailable(true);
+ sendP2pConnectionChangedBroadcast();
+ initializeP2pSettings();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.SUP_DISCONNECTION_EVENT:
+ loge("Unexpected loss of p2p socket connection");
+ transitionTo(mP2pDisabledState);
+ break;
+ case WifiStateMachine.CMD_ENABLE_P2P:
+ //Nothing to do
+ break;
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ if (mPeers.clear()) {
+ sendPeersChangedBroadcast();
+ }
+ if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
+
+ mWifiMonitor.stopMonitoring();
+ transitionTo(mP2pDisablingState);
+ break;
+ case WifiP2pManager.SET_DEVICE_NAME:
+ {
+ WifiP2pDevice d = (WifiP2pDevice) message.obj;
+ if (d != null && setAndPersistDeviceName(d.deviceName)) {
+ if (DBG) logd("set device name " + d.deviceName);
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ }
+ case WifiP2pManager.SET_WFD_INFO:
+ {
+ WifiP2pWfdInfo d = (WifiP2pWfdInfo) message.obj;
+ if (d != null && setWfdInfo(d)) {
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ }
+ case BLOCK_DISCOVERY:
+ boolean blocked = (message.arg1 == ENABLED ? true : false);
+ if (mDiscoveryBlocked == blocked) break;
+ mDiscoveryBlocked = blocked;
+ if (blocked && mDiscoveryStarted) {
+ mWifiNative.p2pStopFind();
+ mDiscoveryPostponed = true;
+ }
+ if (!blocked && mDiscoveryPostponed) {
+ mDiscoveryPostponed = false;
+ mWifiNative.p2pFind(DISCOVER_TIMEOUT_S);
+ }
+ if (blocked) {
+ try {
+ StateMachine m = (StateMachine)message.obj;
+ m.sendMessage(message.arg2);
+ } catch (Exception e) {
+ loge("unable to send BLOCK_DISCOVERY response: " + e);
+ }
+ }
+ break;
+ case WifiP2pManager.DISCOVER_PEERS:
+ if (mDiscoveryBlocked) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ }
+ // do not send service discovery request while normal find operation.
+ clearSupplicantServiceRequest();
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
+ sendP2pDiscoveryChangedBroadcast(true);
+ } else {
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiMonitor.P2P_FIND_STOPPED_EVENT:
+ sendP2pDiscoveryChangedBroadcast(false);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ if (mWifiNative.p2pStopFind()) {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ if (mDiscoveryBlocked) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ }
+ if (DBG) logd(getName() + " discover services");
+ if (!updateSupplicantServiceRequest()) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.NO_SERVICE_REQUESTS);
+ break;
+ }
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
+ WifiP2pDevice device = (WifiP2pDevice) message.obj;
+ if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
+ mPeers.updateSupplicantDetails(device);
+ sendPeersChangedBroadcast();
+ break;
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ device = (WifiP2pDevice) message.obj;
+ // Gets current details for the one removed
+ device = mPeers.remove(device.deviceAddress);
+ if (device != null) {
+ sendPeersChangedBroadcast();
+ }
+ break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " add service");
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj;
+ if (addLocalService(message.replyTo, servInfo)) {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
+ }
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " remove service");
+ servInfo = (WifiP2pServiceInfo)message.obj;
+ removeLocalService(message.replyTo, servInfo);
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ if (DBG) logd(getName() + " clear service");
+ clearLocalServices(message.replyTo);
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " add service request");
+ if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) {
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+ break;
+ }
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " remove service request");
+ removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj);
+ replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ if (DBG) logd(getName() + " clear service request");
+ clearServiceRequests(message.replyTo);
+ replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
+ break;
+ case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+ if (DBG) logd(getName() + " receive service response");
+ List<WifiP2pServiceResponse> sdRespList =
+ (List<WifiP2pServiceResponse>) message.obj;
+ for (WifiP2pServiceResponse resp : sdRespList) {
+ WifiP2pDevice dev =
+ mPeers.get(resp.getSrcDevice().deviceAddress);
+ resp.setSrcDevice(dev);
+ sendServiceResponse(resp);
+ }
+ break;
+ case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+ if (DBG) logd(getName() + " delete persistent group");
+ mGroups.remove(message.arg1);
+ replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
+ break;
+ case SET_MIRACAST_MODE:
+ mWifiNative.setMiracastMode(message.arg1);
+ break;
+ case WifiP2pManager.START_LISTEN:
+ if (DBG) logd(getName() + " start listen mode");
+ mWifiNative.p2pFlush();
+ if (mWifiNative.p2pExtListen(true, 500, 500)) {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
+ }
+ break;
+ case WifiP2pManager.STOP_LISTEN:
+ if (DBG) logd(getName() + " stop listen mode");
+ if (mWifiNative.p2pExtListen(false, 0, 0)) {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
+ }
+ mWifiNative.p2pFlush();
+ break;
+ case WifiP2pManager.SET_CHANNEL:
+ Bundle p2pChannels = (Bundle) message.obj;
+ int lc = p2pChannels.getInt("lc", 0);
+ int oc = p2pChannels.getInt("oc", 0);
+ if (DBG) logd(getName() + " set listen and operating channel");
+ if (mWifiNative.p2pSetChannel(lc, oc)) {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
+ }
+ break;
+ case SET_COUNTRY_CODE:
+ String countryCode = (String) message.obj;
+ countryCode = countryCode.toUpperCase(Locale.ROOT);
+ if (mLastSetCountryCode == null ||
+ countryCode.equals(mLastSetCountryCode) == false) {
+ if (mWifiNative.setCountryCode(countryCode)) {
+ mLastSetCountryCode = countryCode;
+ }
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ sendP2pDiscoveryChangedBroadcast(false);
+ sendP2pStateChangedBroadcast(false);
+ mNetworkInfo.setIsAvailable(false);
+
+ mLastSetCountryCode = null;
+ }
+ }
+
+ class InactiveState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mSavedPeerConfig.invalidate();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiP2pManager.CONNECT:
+ if (DBG) logd(getName() + " sending connect");
+ WifiP2pConfig config = (WifiP2pConfig) message.obj;
+ if (isConfigInvalid(config)) {
+ loge("Dropping connect requeset " + config);
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+ break;
+ }
+
+ mAutonomousGroup = false;
+ mWifiNative.p2pStopFind();
+ if (reinvokePersistentGroup(config)) {
+ transitionTo(mGroupNegotiationState);
+ } else {
+ transitionTo(mProvisionDiscoveryState);
+ }
+ mSavedPeerConfig = config;
+ mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+ break;
+ case WifiP2pManager.STOP_DISCOVERY:
+ if (mWifiNative.p2pStopFind()) {
+ // When discovery stops in inactive state, flush to clear
+ // state peer data
+ mWifiNative.p2pFlush();
+ mServiceDiscReqId = null;
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
+ config = (WifiP2pConfig) message.obj;
+ if (isConfigInvalid(config)) {
+ loge("Dropping GO neg request " + config);
+ break;
+ }
+ mSavedPeerConfig = config;
+ mAutonomousGroup = false;
+ mJoinExistingGroup = false;
+ transitionTo(mUserAuthorizingNegotiationRequestState);
+ break;
+ case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
+ WifiP2pGroup group = (WifiP2pGroup) message.obj;
+ WifiP2pDevice owner = group.getOwner();
+
+ if (owner == null) {
+ loge("Ignored invitation from null owner");
+ break;
+ }
+
+ config = new WifiP2pConfig();
+ config.deviceAddress = group.getOwner().deviceAddress;
+
+ if (isConfigInvalid(config)) {
+ loge("Dropping invitation request " + config);
+ break;
+ }
+ mSavedPeerConfig = config;
+
+ //Check if we have the owner in peer list and use appropriate
+ //wps method. Default is to use PBC.
+ if ((owner = mPeers.get(owner.deviceAddress)) != null) {
+ if (owner.wpsPbcSupported()) {
+ mSavedPeerConfig.wps.setup = WpsInfo.PBC;
+ } else if (owner.wpsKeypadSupported()) {
+ mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
+ } else if (owner.wpsDisplaySupported()) {
+ mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
+ }
+ }
+
+ mAutonomousGroup = false;
+ mJoinExistingGroup = true;
+ transitionTo(mUserAuthorizingInviteRequestState);
+ break;
+ case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ //We let the supplicant handle the provision discovery response
+ //and wait instead for the GO_NEGOTIATION_REQUEST_EVENT.
+ //Handling provision discovery and issuing a p2p_connect before
+ //group negotiation comes through causes issues
+ break;
+ case WifiP2pManager.CREATE_GROUP:
+ mAutonomousGroup = true;
+ int netId = message.arg1;
+ boolean ret = false;
+ if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+ // check if the go persistent group is present.
+ netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
+ if (netId != -1) {
+ ret = mWifiNative.p2pGroupAdd(netId);
+ } else {
+ ret = mWifiNative.p2pGroupAdd(true);
+ }
+ } else {
+ ret = mWifiNative.p2pGroupAdd(false);
+ }
+
+ if (ret) {
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+ WifiP2pManager.ERROR);
+ // remain at this state.
+ }
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ if (DBG) logd(getName() + " group started");
+
+ // We hit this scenario when a persistent group is reinvoked
+ if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+ mAutonomousGroup = false;
+ deferMessage(message);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ loge("Unexpected group creation, remove " + mGroup);
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ }
+ break;
+ case WifiP2pManager.START_LISTEN:
+ if (DBG) logd(getName() + " start listen mode");
+ mWifiNative.p2pFlush();
+ if (mWifiNative.p2pExtListen(true, 500, 500)) {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
+ }
+ break;
+ case WifiP2pManager.STOP_LISTEN:
+ if (DBG) logd(getName() + " stop listen mode");
+ if (mWifiNative.p2pExtListen(false, 0, 0)) {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
+ }
+ mWifiNative.p2pFlush();
+ break;
+ case WifiP2pManager.SET_CHANNEL:
+ Bundle p2pChannels = (Bundle) message.obj;
+ int lc = p2pChannels.getInt("lc", 0);
+ int oc = p2pChannels.getInt("oc", 0);
+ if (DBG) logd(getName() + " set listen and operating channel");
+ if (mWifiNative.p2pSetChannel(lc, oc)) {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class GroupCreatingState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT,
+ ++mGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case GROUP_CREATING_TIMED_OUT:
+ if (mGroupCreatingTimeoutIndex == message.arg1) {
+ if (DBG) logd("Group negotiation timed out");
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ }
+ break;
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ WifiP2pDevice device = (WifiP2pDevice) message.obj;
+ if (!mSavedPeerConfig.deviceAddress.equals(device.deviceAddress)) {
+ if (DBG) {
+ logd("mSavedPeerConfig " + mSavedPeerConfig.deviceAddress +
+ "device " + device.deviceAddress);
+ }
+ // Do the regular device lost handling
+ ret = NOT_HANDLED;
+ break;
+ }
+ // Do nothing
+ if (DBG) logd("Add device to lost list " + device);
+ mPeersLostDuringConnection.updateSupplicantDetails(device);
+ break;
+ case WifiP2pManager.DISCOVER_PEERS:
+ /* Discovery will break negotiation */
+ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CANCEL_CONNECT:
+ //Do a supplicant p2p_cancel which only cancels an ongoing
+ //group negotiation. This will fail for a pending provision
+ //discovery or for a pending user action, but at the framework
+ //level, we always treat cancel as succeeded and enter
+ //an inactive state
+ mWifiNative.p2pCancelConnect();
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
+ break;
+ default:
+ ret = NOT_HANDLED;
+ }
+ return ret;
+ }
+ }
+
+ class UserAuthorizingNegotiationRequestState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyInvitationReceived();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case PEER_CONNECTION_USER_ACCEPT:
+ mWifiNative.p2pStopFind();
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ transitionTo(mGroupNegotiationState);
+ break;
+ case PEER_CONNECTION_USER_REJECT:
+ if (DBG) logd("User rejected negotiation " + mSavedPeerConfig);
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return ret;
+ }
+
+ @Override
+ public void exit() {
+ //TODO: dismiss dialog if not already done
+ }
+ }
+
+ class UserAuthorizingInviteRequestState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyInvitationReceived();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ boolean ret = HANDLED;
+ switch (message.what) {
+ case PEER_CONNECTION_USER_ACCEPT:
+ mWifiNative.p2pStopFind();
+ if (!reinvokePersistentGroup(mSavedPeerConfig)) {
+ // Do negotiation when persistence fails
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ }
+ mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ transitionTo(mGroupNegotiationState);
+ break;
+ case PEER_CONNECTION_USER_REJECT:
+ if (DBG) logd("User rejected invitation " + mSavedPeerConfig);
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return ret;
+ }
+
+ @Override
+ public void exit() {
+ //TODO: dismiss dialog if not already done
+ }
+ }
+
+
+
+ class ProvisionDiscoveryState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ WifiP2pProvDiscEvent provDisc;
+ WifiP2pDevice device;
+ switch (message.what) {
+ case WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
+ provDisc = (WifiP2pProvDiscEvent) message.obj;
+ device = provDisc.device;
+ if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
+
+ if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
+ if (DBG) logd("Found a match " + mSavedPeerConfig);
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ transitionTo(mGroupNegotiationState);
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ provDisc = (WifiP2pProvDiscEvent) message.obj;
+ device = provDisc.device;
+ if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
+
+ if (mSavedPeerConfig.wps.setup == WpsInfo.KEYPAD) {
+ if (DBG) logd("Found a match " + mSavedPeerConfig);
+ /* we already have the pin */
+ if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ transitionTo(mGroupNegotiationState);
+ } else {
+ mJoinExistingGroup = false;
+ transitionTo(mUserAuthorizingNegotiationRequestState);
+ }
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ provDisc = (WifiP2pProvDiscEvent) message.obj;
+ device = provDisc.device;
+ if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
+
+ if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
+ if (DBG) logd("Found a match " + mSavedPeerConfig);
+ mSavedPeerConfig.wps.pin = provDisc.pin;
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ notifyInvitationSent(provDisc.pin, device.deviceAddress);
+ transitionTo(mGroupNegotiationState);
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
+ loge("provision discovery failed");
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class GroupNegotiationState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ // We ignore these right now, since we get a GROUP_STARTED notification
+ // afterwards
+ case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+ case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
+ if (DBG) logd(getName() + " go success");
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ mGroup = (WifiP2pGroup) message.obj;
+ if (DBG) logd(getName() + " group started");
+
+ if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+ /*
+ * update cache information and set network id to mGroup.
+ */
+ updatePersistentNetworks(NO_RELOAD);
+ String devAddr = mGroup.getOwner().deviceAddress;
+ mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
+ mGroup.getNetworkName()));
+ }
+
+ if (mGroup.isGroupOwner()) {
+ /* Setting an idle time out on GO causes issues with certain scenarios
+ * on clients where it can be off-channel for longer and with the power
+ * save modes used.
+ *
+ * TODO: Verify multi-channel scenarios and supplicant behavior are
+ * better before adding a time out in future
+ */
+ //Set group idle timeout of 10 sec, to avoid GO beaconing incase of any
+ //failure during 4-way Handshake.
+ if (!mAutonomousGroup) {
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
+ }
+ startDhcpServer(mGroup.getInterface());
+ } else {
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
+ mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext,
+ P2pStateMachine.this, mGroup.getInterface());
+ // TODO: We should use DHCP state machine PRE message like WifiStateMachine
+ mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
+ WifiP2pDevice groupOwner = mGroup.getOwner();
+ WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
+ if (peer != null) {
+ // update group owner details with peer details found at discovery
+ groupOwner.updateSupplicantDetails(peer);
+ mPeers.updateStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED);
+ sendPeersChangedBroadcast();
+ } else {
+ // A supplicant bug can lead to reporting an invalid
+ // group owner address (all zeroes) at times. Avoid a
+ // crash, but continue group creation since it is not
+ // essential.
+ logw("Unknown group owner " + groupOwner);
+ }
+ }
+ transitionTo(mGroupCreatedState);
+ break;
+ case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
+ P2pStatus status = (P2pStatus) message.obj;
+ if (status == P2pStatus.NO_COMMON_CHANNEL) {
+ transitionTo(mFrequencyConflictState);
+ break;
+ }
+ /* continue with group removal handling */
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ if (DBG) logd(getName() + " go failure");
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
+ // A group formation failure is always followed by
+ // a group removed event. Flushing things at group formation
+ // failure causes supplicant issues. Ignore right now.
+ case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+ status = (P2pStatus) message.obj;
+ if (status == P2pStatus.NO_COMMON_CHANNEL) {
+ transitionTo(mFrequencyConflictState);
+ break;
+ }
+ break;
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ status = (P2pStatus)message.obj;
+ if (status == P2pStatus.SUCCESS) {
+ // invocation was succeeded.
+ // wait P2P_GROUP_STARTED_EVENT.
+ break;
+ }
+ loge("Invitation result " + status);
+ if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+ // target device has already removed the credential.
+ // So, remove this credential accordingly.
+ int netId = mSavedPeerConfig.netId;
+ if (netId >= 0) {
+ if (DBG) logd("Remove unknown client from the list");
+ removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true);
+ }
+
+ // Reinvocation has failed, try group negotiation
+ mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ } else if (status == P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE) {
+
+ // Devices setting persistent_reconnect to 0 in wpa_supplicant
+ // always defer the invocation request and return
+ // "information is currently unable" error.
+ // So, try another way to connect for interoperability.
+ mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ p2pConnectWithPinDisplay(mSavedPeerConfig);
+ } else if (status == P2pStatus.NO_COMMON_CHANNEL) {
+ transitionTo(mFrequencyConflictState);
+ } else {
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class FrequencyConflictState extends State {
+ private AlertDialog mFrequencyConflictDialog;
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyFrequencyConflict();
+ }
+
+ private void notifyFrequencyConflict() {
+ logd("Notify frequency conflict");
+ Resources r = Resources.getSystem();
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setMessage(r.getString(R.string.wifi_p2p_frequency_conflict_message,
+ getDeviceName(mSavedPeerConfig.deviceAddress)))
+ .setPositiveButton(r.getString(R.string.dlg_ok), new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ sendMessage(DROP_WIFI_USER_ACCEPT);
+ }
+ })
+ .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ sendMessage(DROP_WIFI_USER_REJECT);
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface arg0) {
+ sendMessage(DROP_WIFI_USER_REJECT);
+ }
+ })
+ .create();
+
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ mFrequencyConflictDialog = dialog;
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+ case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
+ loge(getName() + "group sucess during freq conflict!");
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ loge(getName() + "group started after freq conflict, handle anyway");
+ deferMessage(message);
+ transitionTo(mGroupNegotiationState);
+ break;
+ case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+ // Ignore failures since we retry again
+ break;
+ case DROP_WIFI_USER_REJECT:
+ // User rejected dropping wifi in favour of p2p
+ handleGroupCreationFailure();
+ transitionTo(mInactiveState);
+ break;
+ case DROP_WIFI_USER_ACCEPT:
+ // User accepted dropping wifi in favour of p2p
+ mWifiChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_REQUEST, 1);
+ mTempoarilyDisconnectedWifi = true;
+ break;
+ case DISCONNECT_WIFI_RESPONSE:
+ // Got a response from wifistatemachine, retry p2p
+ if (DBG) logd(getName() + "Wifi disconnected, retry p2p");
+ transitionTo(mInactiveState);
+ sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ public void exit() {
+ if (mFrequencyConflictDialog != null) mFrequencyConflictDialog.dismiss();
+ }
+ }
+
+ class GroupCreatedState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ // Once connected, peer config details are invalid
+ mSavedPeerConfig.invalidate();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+
+ updateThisDevice(WifiP2pDevice.CONNECTED);
+
+ //DHCP server has already been started if I am a group owner
+ if (mGroup.isGroupOwner()) {
+ setWifiP2pInfoOnGroupFormation(NetworkUtils.numericToInetAddress(SERVER_ADDRESS));
+ }
+
+ // In case of a negotiation group, connection changed is sent
+ // after a client joins. For autonomous, send now
+ if (mAutonomousGroup) {
+ sendP2pConnectionChangedBroadcast();
+ }
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.AP_STA_CONNECTED_EVENT:
+ WifiP2pDevice device = (WifiP2pDevice) message.obj;
+ String deviceAddress = device.deviceAddress;
+ // Clear timeout that was set when group was started.
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+ if (deviceAddress != null) {
+ if (mPeers.get(deviceAddress) != null) {
+ mGroup.addClient(mPeers.get(deviceAddress));
+ } else {
+ mGroup.addClient(deviceAddress);
+ }
+ mPeers.updateStatus(deviceAddress, WifiP2pDevice.CONNECTED);
+ if (DBG) logd(getName() + " ap sta connected");
+ sendPeersChangedBroadcast();
+ } else {
+ loge("Connect on null device address, ignore");
+ }
+ sendP2pConnectionChangedBroadcast();
+ break;
+ case WifiMonitor.AP_STA_DISCONNECTED_EVENT:
+ device = (WifiP2pDevice) message.obj;
+ deviceAddress = device.deviceAddress;
+ if (deviceAddress != null) {
+ mPeers.updateStatus(deviceAddress, WifiP2pDevice.AVAILABLE);
+ if (mGroup.removeClient(deviceAddress)) {
+ if (DBG) logd("Removed client " + deviceAddress);
+ if (!mAutonomousGroup && mGroup.isClientListEmpty()) {
+ logd("Client list empty, remove non-persistent p2p group");
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ // We end up sending connection changed broadcast
+ // when this happens at exit()
+ } else {
+ // Notify when a client disconnects from group
+ sendP2pConnectionChangedBroadcast();
+ }
+ } else {
+ if (DBG) logd("Failed to remove client " + deviceAddress);
+ for (WifiP2pDevice c : mGroup.getClientList()) {
+ if (DBG) logd("client " + c.deviceAddress);
+ }
+ }
+ sendPeersChangedBroadcast();
+ if (DBG) logd(getName() + " ap sta disconnected");
+ } else {
+ loge("Disconnect on unknown device: " + device);
+ }
+ break;
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ DhcpResults dhcpResults = (DhcpResults) message.obj;
+ if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS &&
+ dhcpResults != null) {
+ if (DBG) logd("DhcpResults: " + dhcpResults);
+ setWifiP2pInfoOnGroupFormation(dhcpResults.serverAddress);
+ sendP2pConnectionChangedBroadcast();
+ //Turn on power save on client
+ mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
+ } else {
+ loge("DHCP failed");
+ mWifiNative.p2pGroupRemove(mGroup.getInterface());
+ }
+ break;
+ case WifiP2pManager.REMOVE_GROUP:
+ if (DBG) logd(getName() + " remove group");
+ if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
+ transitionTo(mOngoingGroupRemovalState);
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
+ } else {
+ handleGroupRemoved();
+ transitionTo(mInactiveState);
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
+ /* We do not listen to NETWORK_DISCONNECTION_EVENT for group removal
+ * handling since supplicant actually tries to reconnect after a temporary
+ * disconnect until group idle time out. Eventually, a group removal event
+ * will come when group has been removed.
+ *
+ * When there are connectivity issues during temporary disconnect, the application
+ * will also just remove the group.
+ *
+ * Treating network disconnection as group removal causes race conditions since
+ * supplicant would still maintain the group at that stage.
+ */
+ case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
+ if (DBG) logd(getName() + " group removed");
+ handleGroupRemoved();
+ transitionTo(mInactiveState);
+ break;
+ case WifiMonitor.P2P_DEVICE_LOST_EVENT:
+ device = (WifiP2pDevice) message.obj;
+ //Device loss for a connected device indicates it is not in discovery any more
+ if (mGroup.contains(device)) {
+ if (DBG) logd("Add device to lost list " + device);
+ mPeersLostDuringConnection.updateSupplicantDetails(device);
+ return HANDLED;
+ }
+ // Do the regular device lost handling
+ return NOT_HANDLED;
+ case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+ sendMessage(WifiP2pManager.REMOVE_GROUP);
+ deferMessage(message);
+ break;
+ // This allows any client to join the GO during the
+ // WPS window
+ case WifiP2pManager.START_WPS:
+ WpsInfo wps = (WpsInfo) message.obj;
+ if (wps == null) {
+ replyToMessage(message, WifiP2pManager.START_WPS_FAILED);
+ break;
+ }
+ boolean ret = true;
+ if (wps.setup == WpsInfo.PBC) {
+ ret = mWifiNative.startWpsPbc(mGroup.getInterface(), null);
+ } else {
+ if (wps.pin == null) {
+ String pin = mWifiNative.startWpsPinDisplay(mGroup.getInterface());
+ try {
+ Integer.parseInt(pin);
+ notifyInvitationSent(pin, "any");
+ } catch (NumberFormatException ignore) {
+ ret = false;
+ }
+ } else {
+ ret = mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
+ wps.pin);
+ }
+ }
+ replyToMessage(message, ret ? WifiP2pManager.START_WPS_SUCCEEDED :
+ WifiP2pManager.START_WPS_FAILED);
+ break;
+ case WifiP2pManager.CONNECT:
+ WifiP2pConfig config = (WifiP2pConfig) message.obj;
+ if (isConfigInvalid(config)) {
+ loge("Dropping connect requeset " + config);
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+ break;
+ }
+ logd("Inviting device : " + config.deviceAddress);
+ mSavedPeerConfig = config;
+ if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
+ mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED);
+ sendPeersChangedBroadcast();
+ replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ // TODO: figure out updating the status to declined when invitation is rejected
+ break;
+ case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
+ P2pStatus status = (P2pStatus)message.obj;
+ if (status == P2pStatus.SUCCESS) {
+ // invocation was succeeded.
+ break;
+ }
+ loge("Invitation result " + status);
+ if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+ // target device has already removed the credential.
+ // So, remove this credential accordingly.
+ int netId = mGroup.getNetworkId();
+ if (netId >= 0) {
+ if (DBG) logd("Remove unknown client from the list");
+ if (!removeClientFromList(netId,
+ mSavedPeerConfig.deviceAddress, false)) {
+ // not found the client on the list
+ loge("Already removed the client, ignore");
+ break;
+ }
+ // try invitation.
+ sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+ }
+ }
+ break;
+ case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
+ mSavedPeerConfig = new WifiP2pConfig();
+ mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
+ if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
+ mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
+ } else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) {
+ mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
+ mSavedPeerConfig.wps.pin = provDisc.pin;
+ } else {
+ mSavedPeerConfig.wps.setup = WpsInfo.PBC;
+ }
+ transitionTo(mUserAuthorizingJoinState);
+ break;
+ case WifiMonitor.P2P_GROUP_STARTED_EVENT:
+ loge("Duplicate group creation event notice, ignore");
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ public void exit() {
+ updateThisDevice(WifiP2pDevice.AVAILABLE);
+ resetWifiP2pInfo();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+ sendP2pConnectionChangedBroadcast();
+ }
+ }
+
+ class UserAuthorizingJoinState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ notifyInvitationReceived();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+ case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+ //Ignore more client requests
+ break;
+ case PEER_CONNECTION_USER_ACCEPT:
+ //Stop discovery to avoid failure due to channel switch
+ mWifiNative.p2pStopFind();
+ if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
+ mWifiNative.startWpsPbc(mGroup.getInterface(), null);
+ } else {
+ mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
+ mSavedPeerConfig.wps.pin);
+ }
+ transitionTo(mGroupCreatedState);
+ break;
+ case PEER_CONNECTION_USER_REJECT:
+ if (DBG) logd("User rejected incoming request");
+ transitionTo(mGroupCreatedState);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ //TODO: dismiss dialog if not already done
+ }
+ }
+
+ class OngoingGroupRemovalState extends State {
+ @Override
+ public void enter() {
+ if (DBG) logd(getName());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) logd(getName() + message.toString());
+ switch (message.what) {
+ // Group removal ongoing. Multiple calls
+ // end up removing persisted network. Do nothing.
+ case WifiP2pManager.REMOVE_GROUP:
+ replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
+ break;
+ // Parent state will transition out of this state
+ // when removal is complete
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println("mWifiP2pInfo " + mWifiP2pInfo);
+ pw.println("mGroup " + mGroup);
+ pw.println("mSavedPeerConfig " + mSavedPeerConfig);
+ pw.println("mSavedP2pGroup " + mSavedP2pGroup);
+ pw.println();
+ }
+
+ private void sendP2pStateChangedBroadcast(boolean enabled) {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ if (enabled) {
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+ WifiP2pManager.WIFI_P2P_STATE_ENABLED);
+ } else {
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+ WifiP2pManager.WIFI_P2P_STATE_DISABLED);
+ }
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendP2pDiscoveryChangedBroadcast(boolean started) {
+ if (mDiscoveryStarted == started) return;
+ mDiscoveryStarted = started;
+
+ if (DBG) logd("discovery change broadcast " + started);
+
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started ?
+ WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED :
+ WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendThisDeviceChangedBroadcast() {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendPeersChangedBroadcast() {
+ final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+ intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void sendP2pConnectionChangedBroadcast() {
+ if (DBG) logd("sending p2p connection changed broadcast");
+ Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
+ intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
+ intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, new WifiP2pGroup(mGroup));
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mWifiChannel.sendMessage(WifiP2pService.P2P_CONNECTION_CHANGED,
+ new NetworkInfo(mNetworkInfo));
+ }
+
+ private void sendP2pPersistentGroupsChangedBroadcast() {
+ if (DBG) logd("sending p2p persistent groups changed broadcast");
+ Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ private void startDhcpServer(String intf) {
+ InterfaceConfiguration ifcg = null;
+ try {
+ ifcg = mNwService.getInterfaceConfig(intf);
+ ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
+ SERVER_ADDRESS), 24));
+ ifcg.setInterfaceUp();
+ mNwService.setInterfaceConfig(intf, ifcg);
+ /* This starts the dnsmasq server */
+ mNwService.startTethering(DHCP_RANGE);
+ } catch (Exception e) {
+ loge("Error configuring interface " + intf + ", :" + e);
+ return;
+ }
+
+ logd("Started Dhcp server on " + intf);
+ }
+
+ private void stopDhcpServer(String intf) {
+ try {
+ mNwService.stopTethering();
+ } catch (Exception e) {
+ loge("Error stopping Dhcp server" + e);
+ return;
+ }
+
+ logd("Stopped Dhcp server");
+ }
+
+ private void notifyP2pEnableFailure() {
+ Resources r = Resources.getSystem();
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.wifi_p2p_dialog_title))
+ .setMessage(r.getString(R.string.wifi_p2p_failed_message))
+ .setPositiveButton(r.getString(R.string.ok), null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ private void addRowToDialog(ViewGroup group, int stringId, String value) {
+ Resources r = Resources.getSystem();
+ View row = LayoutInflater.from(mContext).inflate(R.layout.wifi_p2p_dialog_row,
+ group, false);
+ ((TextView) row.findViewById(R.id.name)).setText(r.getString(stringId));
+ ((TextView) row.findViewById(R.id.value)).setText(value);
+ group.addView(row);
+ }
+
+ private void notifyInvitationSent(String pin, String peerAddress) {
+ Resources r = Resources.getSystem();
+
+ final View textEntryView = LayoutInflater.from(mContext)
+ .inflate(R.layout.wifi_p2p_dialog, null);
+
+ ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
+ addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
+ addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
+ .setView(textEntryView)
+ .setPositiveButton(r.getString(R.string.ok), null)
+ .create();
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ private void notifyInvitationReceived() {
+ Resources r = Resources.getSystem();
+ final WpsInfo wps = mSavedPeerConfig.wps;
+ final View textEntryView = LayoutInflater.from(mContext)
+ .inflate(R.layout.wifi_p2p_dialog, null);
+
+ ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
+ addRowToDialog(group, R.string.wifi_p2p_from_message, getDeviceName(
+ mSavedPeerConfig.deviceAddress));
+
+ final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin);
+
+ AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title))
+ .setView(textEntryView)
+ .setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ if (wps.setup == WpsInfo.KEYPAD) {
+ mSavedPeerConfig.wps.pin = pin.getText().toString();
+ }
+ if (DBG) logd(getName() + " accept invitation " + mSavedPeerConfig);
+ sendMessage(PEER_CONNECTION_USER_ACCEPT);
+ }
+ })
+ .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (DBG) logd(getName() + " ignore connect");
+ sendMessage(PEER_CONNECTION_USER_REJECT);
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface arg0) {
+ if (DBG) logd(getName() + " ignore connect");
+ sendMessage(PEER_CONNECTION_USER_REJECT);
+ }
+ })
+ .create();
+
+ //make the enter pin area or the display pin area visible
+ switch (wps.setup) {
+ case WpsInfo.KEYPAD:
+ if (DBG) logd("Enter pin section visible");
+ textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE);
+ break;
+ case WpsInfo.DISPLAY:
+ if (DBG) logd("Shown pin section visible");
+ addRowToDialog(group, R.string.wifi_p2p_show_pin_message, wps.pin);
+ break;
+ default:
+ break;
+ }
+
+ if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_APPLIANCE) ==
+ Configuration.UI_MODE_TYPE_APPLIANCE) {
+ // For appliance devices, add a key listener which accepts.
+ dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
+
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ // TODO: make the actual key come from a config value.
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+ sendMessage(PEER_CONNECTION_USER_ACCEPT);
+ dialog.dismiss();
+ return true;
+ }
+ return false;
+ }
+ });
+ // TODO: add timeout for this dialog.
+ // TODO: update UI in appliance mode to tell user what to do.
+ }
+
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ dialog.show();
+ }
+
+ /**
+ * Synchronize the persistent group list between
+ * wpa_supplicant and mGroups.
+ */
+ private void updatePersistentNetworks(boolean reload) {
+ String listStr = mWifiNative.listNetworks();
+ if (listStr == null) return;
+
+ boolean isSaveRequired = false;
+ String[] lines = listStr.split("\n");
+ if (lines == null) return;
+
+ if (reload) mGroups.clear();
+
+ // Skip the first line, which is a header
+ for (int i = 1; i < lines.length; i++) {
+ String[] result = lines[i].split("\t");
+ if (result == null || result.length < 4) {
+ continue;
+ }
+ // network-id | ssid | bssid | flags
+ int netId = -1;
+ String ssid = result[1];
+ String bssid = result[2];
+ String flags = result[3];
+ try {
+ netId = Integer.parseInt(result[0]);
+ } catch(NumberFormatException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (flags.indexOf("[CURRENT]") != -1) {
+ continue;
+ }
+ if (flags.indexOf("[P2P-PERSISTENT]") == -1) {
+ /*
+ * The unused profile is sometimes remained when the p2p group formation is failed.
+ * So, we clean up the p2p group here.
+ */
+ if (DBG) logd("clean up the unused persistent group. netId=" + netId);
+ mWifiNative.removeNetwork(netId);
+ isSaveRequired = true;
+ continue;
+ }
+
+ if (mGroups.contains(netId)) {
+ continue;
+ }
+
+ WifiP2pGroup group = new WifiP2pGroup();
+ group.setNetworkId(netId);
+ group.setNetworkName(ssid);
+ String mode = mWifiNative.getNetworkVariable(netId, "mode");
+ if (mode != null && mode.equals("3")) {
+ group.setIsGroupOwner(true);
+ }
+ if (bssid.equalsIgnoreCase(mThisDevice.deviceAddress)) {
+ group.setOwner(mThisDevice);
+ } else {
+ WifiP2pDevice device = new WifiP2pDevice();
+ device.deviceAddress = bssid;
+ group.setOwner(device);
+ }
+ mGroups.add(group);
+ isSaveRequired = true;
+ }
+
+ if (reload || isSaveRequired) {
+ mWifiNative.saveConfig();
+ sendP2pPersistentGroupsChangedBroadcast();
+ }
+ }
+
+ /**
+ * A config is valid if it has a peer address that has already been
+ * discovered
+ * @return true if it is invalid, false otherwise
+ */
+ private boolean isConfigInvalid(WifiP2pConfig config) {
+ if (config == null) return true;
+ if (TextUtils.isEmpty(config.deviceAddress)) return true;
+ if (mPeers.get(config.deviceAddress) == null) return true;
+ return false;
+ }
+
+ /* TODO: The supplicant does not provide group capability changes as an event.
+ * Having it pushed as an event would avoid polling for this information right
+ * before a connection
+ */
+ private WifiP2pDevice fetchCurrentDeviceDetails(WifiP2pConfig config) {
+ /* Fetch & update group capability from supplicant on the device */
+ int gc = mWifiNative.getGroupCapability(config.deviceAddress);
+ mPeers.updateGroupCapability(config.deviceAddress, gc);
+ return mPeers.get(config.deviceAddress);
+ }
+
+ /**
+ * Start a p2p group negotiation and display pin if necessary
+ * @param config for the peer
+ */
+ private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
+ WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
+
+ String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
+ try {
+ Integer.parseInt(pin);
+ notifyInvitationSent(pin, config.deviceAddress);
+ } catch (NumberFormatException ignore) {
+ // do nothing if p2pConnect did not return a pin
+ }
+ }
+
+ /**
+ * Reinvoke a persistent group.
+ *
+ * @param config for the peer
+ * @return true on success, false on failure
+ */
+ private boolean reinvokePersistentGroup(WifiP2pConfig config) {
+ WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
+
+ boolean join = dev.isGroupOwner();
+ String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
+ if (DBG) logd("target ssid is " + ssid + " join:" + join);
+
+ if (join && dev.isGroupLimit()) {
+ if (DBG) logd("target device reaches group limit.");
+
+ // if the target group has reached the limit,
+ // try group formation.
+ join = false;
+ } else if (join) {
+ int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
+ if (netId >= 0) {
+ // Skip WPS and start 4way handshake immediately.
+ if (!mWifiNative.p2pGroupAdd(netId)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ if (!join && dev.isDeviceLimit()) {
+ loge("target device reaches the device limit.");
+ return false;
+ }
+
+ if (!join && dev.isInvitationCapable()) {
+ int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+ if (config.netId >= 0) {
+ if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) {
+ netId = config.netId;
+ }
+ } else {
+ netId = mGroups.getNetworkId(dev.deviceAddress);
+ }
+ if (netId < 0) {
+ netId = getNetworkIdFromClientList(dev.deviceAddress);
+ }
+ if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId);
+ if (netId >= 0) {
+ // Invoke the persistent group.
+ if (mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
+ // Save network id. It'll be used when an invitation result event is received.
+ config.netId = netId;
+ return true;
+ } else {
+ loge("p2pReinvoke() failed, update networks");
+ updatePersistentNetworks(RELOAD);
+ return false;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the network id of the group owner profile which has the p2p client with
+ * the specified device address in it's client list.
+ * If more than one persistent group of the same address is present in its client
+ * lists, return the first one.
+ *
+ * @param deviceAddress p2p device address.
+ * @return the network id. if not found, return -1.
+ */
+ private int getNetworkIdFromClientList(String deviceAddress) {
+ if (deviceAddress == null) return -1;
+
+ Collection<WifiP2pGroup> groups = mGroups.getGroupList();
+ for (WifiP2pGroup group : groups) {
+ int netId = group.getNetworkId();
+ String[] p2pClientList = getClientList(netId);
+ if (p2pClientList == null) continue;
+ for (String client : p2pClientList) {
+ if (deviceAddress.equalsIgnoreCase(client)) {
+ return netId;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Return p2p client list associated with the specified network id.
+ * @param netId network id.
+ * @return p2p client list. if not found, return null.
+ */
+ private String[] getClientList(int netId) {
+ String p2pClients = mWifiNative.getNetworkVariable(netId, "p2p_client_list");
+ if (p2pClients == null) {
+ return null;
+ }
+ return p2pClients.split(" ");
+ }
+
+ /**
+ * Remove the specified p2p client from the specified profile.
+ * @param netId network id of the profile.
+ * @param addr p2p client address to be removed.
+ * @param isRemovable if true, remove the specified profile if its client list becomes empty.
+ * @return whether removing the specified p2p client is successful or not.
+ */
+ private boolean removeClientFromList(int netId, String addr, boolean isRemovable) {
+ StringBuilder modifiedClientList = new StringBuilder();
+ String[] currentClientList = getClientList(netId);
+ boolean isClientRemoved = false;
+ if (currentClientList != null) {
+ for (String client : currentClientList) {
+ if (!client.equalsIgnoreCase(addr)) {
+ modifiedClientList.append(" ");
+ modifiedClientList.append(client);
+ } else {
+ isClientRemoved = true;
+ }
+ }
+ }
+ if (modifiedClientList.length() == 0 && isRemovable) {
+ // the client list is empty. so remove it.
+ if (DBG) logd("Remove unknown network");
+ mGroups.remove(netId);
+ return true;
+ }
+
+ if (!isClientRemoved) {
+ // specified p2p client is not found. already removed.
+ return false;
+ }
+
+ if (DBG) logd("Modified client list: " + modifiedClientList);
+ if (modifiedClientList.length() == 0) {
+ modifiedClientList.append("\"\"");
+ }
+ mWifiNative.setNetworkVariable(netId,
+ "p2p_client_list", modifiedClientList.toString());
+ mWifiNative.saveConfig();
+ return true;
+ }
+
+ private void setWifiP2pInfoOnGroupFormation(InetAddress serverInetAddress) {
+ mWifiP2pInfo.groupFormed = true;
+ mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
+ mWifiP2pInfo.groupOwnerAddress = serverInetAddress;
+ }
+
+ private void resetWifiP2pInfo() {
+ mWifiP2pInfo.groupFormed = false;
+ mWifiP2pInfo.isGroupOwner = false;
+ mWifiP2pInfo.groupOwnerAddress = null;
+ }
+
+ private String getDeviceName(String deviceAddress) {
+ WifiP2pDevice d = mPeers.get(deviceAddress);
+ if (d != null) {
+ return d.deviceName;
+ }
+ //Treat the address as name if there is no match
+ return deviceAddress;
+ }
+
+ private String getPersistedDeviceName() {
+ String deviceName = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIFI_P2P_DEVICE_NAME);
+ if (deviceName == null) {
+ /* We use the 4 digits of the ANDROID_ID to have a friendly
+ * default that has low likelihood of collision with a peer */
+ String id = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.ANDROID_ID);
+ return "Android_" + id.substring(0,4);
+ }
+ return deviceName;
+ }
+
+ private boolean setAndPersistDeviceName(String devName) {
+ if (devName == null) return false;
+
+ if (!mWifiNative.setDeviceName(devName)) {
+ loge("Failed to set device name " + devName);
+ return false;
+ }
+
+ mThisDevice.deviceName = devName;
+ mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
+
+ Settings.Global.putString(mContext.getContentResolver(),
+ Settings.Global.WIFI_P2P_DEVICE_NAME, devName);
+ sendThisDeviceChangedBroadcast();
+ return true;
+ }
+
+ private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
+ boolean success;
+
+ if (!wfdInfo.isWfdEnabled()) {
+ success = mWifiNative.setWfdEnable(false);
+ } else {
+ success =
+ mWifiNative.setWfdEnable(true)
+ && mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
+ }
+
+ if (!success) {
+ loge("Failed to set wfd properties");
+ return false;
+ }
+
+ mThisDevice.wfdInfo = wfdInfo;
+ sendThisDeviceChangedBroadcast();
+ return true;
+ }
+
+ private void initializeP2pSettings() {
+ mWifiNative.setPersistentReconnect(true);
+ mThisDevice.deviceName = getPersistedDeviceName();
+ mWifiNative.setDeviceName(mThisDevice.deviceName);
+ // DIRECT-XY-DEVICENAME (XY is randomly generated)
+ mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
+ mWifiNative.setDeviceType(mThisDevice.primaryDeviceType);
+ // Supplicant defaults to using virtual display with display
+ // which refers to a remote display. Use physical_display
+ mWifiNative.setConfigMethods("virtual_push_button physical_display keypad");
+ // STA has higher priority over P2P
+ mWifiNative.setConcurrencyPriority("sta");
+
+ mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
+ updateThisDevice(WifiP2pDevice.AVAILABLE);
+ if (DBG) logd("DeviceAddress: " + mThisDevice.deviceAddress);
+
+ mClientInfoList.clear();
+ mWifiNative.p2pFlush();
+ mWifiNative.p2pServiceFlush();
+ mServiceTransactionId = 0;
+ mServiceDiscReqId = null;
+
+ String countryCode = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIFI_COUNTRY_CODE);
+ if (countryCode != null && !countryCode.isEmpty()) {
+ mP2pStateMachine.sendMessage(SET_COUNTRY_CODE, countryCode);
+ }
+
+ updatePersistentNetworks(RELOAD);
+ }
+
+ private void updateThisDevice(int status) {
+ mThisDevice.status = status;
+ sendThisDeviceChangedBroadcast();
+ }
+
+ private void handleGroupCreationFailure() {
+ resetWifiP2pInfo();
+ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.FAILED, null, null);
+ sendP2pConnectionChangedBroadcast();
+
+ // Remove only the peer we failed to connect to so that other devices discovered
+ // that have not timed out still remain in list for connection
+ boolean peersChanged = mPeers.remove(mPeersLostDuringConnection);
+ if (mPeers.remove(mSavedPeerConfig.deviceAddress) != null) {
+ peersChanged = true;
+ }
+ if (peersChanged) {
+ sendPeersChangedBroadcast();
+ }
+
+ mPeersLostDuringConnection.clear();
+ mServiceDiscReqId = null;
+ sendMessage(WifiP2pManager.DISCOVER_PEERS);
+ }
+
+ private void handleGroupRemoved() {
+ if (mGroup.isGroupOwner()) {
+ stopDhcpServer(mGroup.getInterface());
+ } else {
+ if (DBG) logd("stop DHCP client");
+ mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
+ mDhcpStateMachine.doQuit();
+ mDhcpStateMachine = null;
+ }
+
+ try {
+ mNwService.clearInterfaceAddresses(mGroup.getInterface());
+ } catch (Exception e) {
+ loge("Failed to clear addresses " + e);
+ }
+ NetworkUtils.resetConnections(mGroup.getInterface(), NetworkUtils.RESET_ALL_ADDRESSES);
+
+ // Clear any timeout that was set. This is essential for devices
+ // that reuse the main p2p interface for a created group.
+ mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+
+ boolean peersChanged = false;
+ // Remove only peers part of the group, so that other devices discovered
+ // that have not timed out still remain in list for connection
+ for (WifiP2pDevice d : mGroup.getClientList()) {
+ if (mPeers.remove(d)) peersChanged = true;
+ }
+ if (mPeers.remove(mGroup.getOwner())) peersChanged = true;
+ if (mPeers.remove(mPeersLostDuringConnection)) peersChanged = true;
+ if (peersChanged) {
+ sendPeersChangedBroadcast();
+ }
+
+ mGroup = null;
+ mPeersLostDuringConnection.clear();
+ mServiceDiscReqId = null;
+
+ if (mTempoarilyDisconnectedWifi) {
+ mWifiChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_REQUEST, 0);
+ mTempoarilyDisconnectedWifi = false;
+ }
+ }
+
+ //State machine initiated requests can have replyTo set to null indicating
+ //there are no recipients, we ignore those reply actions
+ private void replyToMessage(Message msg, int what) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessage(msg);
+ dstMsg.what = what;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, int arg1) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessage(msg);
+ dstMsg.what = what;
+ dstMsg.arg1 = arg1;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ private void replyToMessage(Message msg, int what, Object obj) {
+ if (msg.replyTo == null) return;
+ Message dstMsg = obtainMessage(msg);
+ dstMsg.what = what;
+ dstMsg.obj = obj;
+ mReplyChannel.replyToMessage(msg, dstMsg);
+ }
+
+ /* arg2 on the source message has a hash code that needs to be retained in replies
+ * see WifiP2pManager for details */
+ private Message obtainMessage(Message srcMsg) {
+ Message msg = Message.obtain();
+ msg.arg2 = srcMsg.arg2;
+ return msg;
+ }
+
+ @Override
+ protected void logd(String s) {
+ Slog.d(TAG, s);
+ }
+
+ @Override
+ protected void loge(String s) {
+ Slog.e(TAG, s);
+ }
+
+ /**
+ * Update service discovery request to wpa_supplicant.
+ */
+ private boolean updateSupplicantServiceRequest() {
+ clearSupplicantServiceRequest();
+
+ StringBuffer sb = new StringBuffer();
+ for (ClientInfo c: mClientInfoList.values()) {
+ int key;
+ WifiP2pServiceRequest req;
+ for (int i=0; i < c.mReqList.size(); i++) {
+ req = c.mReqList.valueAt(i);
+ if (req != null) {
+ sb.append(req.getSupplicantQuery());
+ }
+ }
+ }
+
+ if (sb.length() == 0) {
+ return false;
+ }
+
+ mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
+ if (mServiceDiscReqId == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clear service discovery request in wpa_supplicant
+ */
+ private void clearSupplicantServiceRequest() {
+ if (mServiceDiscReqId == null) return;
+
+ mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
+ mServiceDiscReqId = null;
+ }
+
+ /* TODO: We could track individual service adds separately and avoid
+ * having to do update all service requests on every new request
+ */
+ private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ ++mServiceTransactionId;
+ //The Wi-Fi p2p spec says transaction id should be non-zero
+ if (mServiceTransactionId == 0) ++mServiceTransactionId;
+ req.setTransactionId(mServiceTransactionId);
+ clientInfo.mReqList.put(mServiceTransactionId, req);
+
+ if (mServiceDiscReqId == null) {
+ return true;
+ }
+
+ return updateSupplicantServiceRequest();
+ }
+
+ private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ //Application does not have transaction id information
+ //go through stored requests to remove
+ boolean removed = false;
+ for (int i=0; i<clientInfo.mReqList.size(); i++) {
+ if (req.equals(clientInfo.mReqList.valueAt(i))) {
+ removed = true;
+ clientInfo.mReqList.removeAt(i);
+ break;
+ }
+ }
+
+ if (!removed) return;
+
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private void clearServiceRequests(Messenger m) {
+
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ if (clientInfo.mReqList.size() == 0) {
+ return;
+ }
+
+ clientInfo.mReqList.clear();
+
+ if (clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove channel information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ if (!clientInfo.mServList.add(servInfo)) {
+ return false;
+ }
+
+ if (!mWifiNative.p2pServiceAdd(servInfo)) {
+ clientInfo.mServList.remove(servInfo);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ mWifiNative.p2pServiceDel(servInfo);
+
+ clientInfo.mServList.remove(servInfo);
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearLocalServices(Messenger m) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
+ mWifiNative.p2pServiceDel(servInfo);
+ }
+
+ clientInfo.mServList.clear();
+ if (clientInfo.mReqList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearClientInfo(Messenger m) {
+ clearLocalServices(m);
+ clearServiceRequests(m);
+ }
+
+ /**
+ * Send the service response to the WifiP2pManager.Channel.
+ *
+ * @param resp
+ */
+ private void sendServiceResponse(WifiP2pServiceResponse resp) {
+ for (ClientInfo c : mClientInfoList.values()) {
+ WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
+ if (req != null) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.RESPONSE_SERVICE;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = resp;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ clearClientInfo(c.mMessenger);
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * We dont get notifications of clients that have gone away.
+ * We detect this actively when services are added and throw
+ * them away.
+ *
+ * TODO: This can be done better with full async channels.
+ */
+ private void clearClientDeadChannels() {
+ ArrayList<Messenger> deadClients = new ArrayList<Messenger>();
+
+ for (ClientInfo c : mClientInfoList.values()) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.PING;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = null;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ deadClients.add(c.mMessenger);
+ }
+ }
+
+ for (Messenger m : deadClients) {
+ clearClientInfo(m);
+ }
+ }
+
+ /**
+ * Return the specified ClientInfo.
+ * @param m Messenger
+ * @param createIfNotExist if true and the specified channel info does not exist,
+ * create new client info.
+ * @return the specified ClientInfo.
+ */
+ private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
+ ClientInfo clientInfo = mClientInfoList.get(m);
+
+ if (clientInfo == null && createIfNotExist) {
+ if (DBG) logd("add a new client");
+ clientInfo = new ClientInfo(m);
+ mClientInfoList.put(m, clientInfo);
+ }
+
+ return clientInfo;
+ }
+
+ }
+
+ /**
+ * Information about a particular client and we track the service discovery requests
+ * and the local services registered by the client.
+ */
+ private class ClientInfo {
+
+ /*
+ * A reference to WifiP2pManager.Channel handler.
+ * The response of this request is notified to WifiP2pManager.Channel handler
+ */
+ private Messenger mMessenger;
+
+ /*
+ * A service discovery request list.
+ */
+ private SparseArray<WifiP2pServiceRequest> mReqList;
+
+ /*
+ * A local service information list.
+ */
+ private List<WifiP2pServiceInfo> mServList;
+
+ private ClientInfo(Messenger m) {
+ mMessenger = m;
+ mReqList = new SparseArray();
+ mServList = new ArrayList<WifiP2pServiceInfo>();
+ }
+ }
+}