diff options
Diffstat (limited to 'services/java/com')
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, * "<code>x</code>" + * 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 " — " + */ + 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>(); + } + } +} |