diff options
Diffstat (limited to 'services/java')
80 files changed, 13610 insertions, 15121 deletions
diff --git a/services/java/Android.mk b/services/java/Android.mk index 934712c..c756d29 100644 --- a/services/java/Android.mk +++ b/services/java/Android.mk @@ -13,7 +13,9 @@ LOCAL_MODULE:= services LOCAL_JAVA_LIBRARIES := android.policy +LOCAL_NO_EMMA_INSTRUMENT := true +LOCAL_NO_EMMA_COMPILE := true + include $(BUILD_JAVA_LIBRARY) include $(BUILD_DROIDDOC) - diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index dc5fd30..3ed6c12 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -1204,6 +1204,7 @@ class AppWidgetService extends IAppWidgetService.Stub // If it's currently showing, call back with the new AppWidgetProviderInfo. for (int j=0; j<M; j++) { AppWidgetId id = p.instances.get(j); + id.views = null; if (id.host != null && id.host.callbacks != null) { try { id.host.callbacks.providerChanged(id.appWidgetId, p.info); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 6e307a5..3db5dc1 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -17,8 +17,8 @@ package com.android.server; import android.app.ActivityManagerNative; -import android.app.ActivityThread; import android.app.AlarmManager; +import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; @@ -399,7 +399,7 @@ class BackupManagerService extends IBackupManager.Stub { public BackupManagerService(Context context) { mContext = context; mPackageManager = context.getPackageManager(); - mPackageManagerBinder = ActivityThread.getPackageManager(); + mPackageManagerBinder = AppGlobals.getPackageManager(); mActivityManager = ActivityManagerNative.getDefault(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); @@ -421,7 +421,7 @@ class BackupManagerService extends IBackupManager.Stub { Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0; // If Encrypted file systems is enabled or disabled, this call will return the // correct directory. - mBaseStateDir = new File(Environment.getDataDirectory(), "backup"); + mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup"); mBaseStateDir.mkdirs(); mDataDir = Environment.getDownloadCacheDirectory(); diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 5cf61bd..e6c32d9 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -210,9 +210,6 @@ class BatteryService extends Binder { boolean logOutlier = false; long dischargeDuration = 0; - shutdownIfNoPower(); - shutdownIfOverTemp(); - mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL; if (mAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; @@ -221,6 +218,19 @@ class BatteryService extends Binder { } else { mPlugType = BATTERY_PLUGGED_NONE; } + + // Let the battery stats keep track of the current level. + try { + mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, + mPlugType, mBatteryLevel, mBatteryTemperature, + mBatteryVoltage); + } catch (RemoteException e) { + // Should never happen. + } + + shutdownIfNoPower(); + shutdownIfOverTemp(); + if (mBatteryStatus != mLastBatteryStatus || mBatteryHealth != mLastBatteryHealth || mBatteryPresent != mLastBatteryPresent || @@ -263,16 +273,6 @@ class BatteryService extends Binder { EventLog.writeEvent(EventLogTags.BATTERY_LEVEL, mBatteryLevel, mBatteryVoltage, mBatteryTemperature); } - if (mBatteryLevel != mLastBatteryLevel && mPlugType == BATTERY_PLUGGED_NONE) { - // If the battery level has changed and we are on battery, update the current level. - // This is used for discharge cycle tracking so this shouldn't be updated while the - // battery is charging. - try { - mBatteryStats.recordCurrentLevel(mBatteryLevel); - } catch (RemoteException e) { - // Should never happen. - } - } if (mBatteryLevelCritical && !mLastBatteryLevelCritical && mPlugType == BATTERY_PLUGGED_NONE) { // We want to make sure we log discharge cycle outliers @@ -342,11 +342,6 @@ class BatteryService extends Binder { Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); - try { - mBatteryStats.setOnBattery(mPlugType == BATTERY_PLUGGED_NONE, mBatteryLevel); - } catch (RemoteException e) { - // Should never happen. - } int icon = getIcon(mBatteryLevel); diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 0de11c6..14b7d3e 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -690,8 +690,6 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { // was lost. Tombstones are expunged by age (see above). if (mAllFiles.blocks > mCachedQuotaBlocks) { - Slog.i(TAG, "Usage (" + mAllFiles.blocks + ") > Quota (" + mCachedQuotaBlocks + ")"); - // Find a fair share amount of space to limit each tag int unsqueezed = mAllFiles.blocks, squeezed = 0; TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values()); diff --git a/services/java/com/android/server/status/StatusBarException.java b/services/java/com/android/server/InputApplication.java index be58f59..38420d4 100644 --- a/services/java/com/android/server/status/StatusBarException.java +++ b/services/java/com/android/server/InputApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * 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. @@ -14,10 +14,20 @@ * limitations under the License. */ -package com.android.server.status; +package com.android.server; -public class StatusBarException extends RuntimeException { - StatusBarException(String msg) { - super(msg); - } +/** + * Describes input-related application properties for use by the input dispatcher. + * + * @hide + */ +public final class InputApplication { + // Application name. + public String name; + + // Dispatching timeout. + public long dispatchingTimeoutNanos; + + // The application window token. + public Object token; } diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java deleted file mode 100644 index 414b69f..0000000 --- a/services/java/com/android/server/InputDevice.java +++ /dev/null @@ -1,1025 +0,0 @@ -/* - * Copyright (C) 2007 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.Slog; -import android.view.Display; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.WindowManagerPolicy; - -import java.io.PrintWriter; - -public class InputDevice { - static final boolean DEBUG_POINTERS = false; - static final boolean DEBUG_HACKS = false; - - /** Amount that trackball needs to move in order to generate a key event. */ - static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; - - /** Maximum number of pointers we will track and report. */ - static final int MAX_POINTERS = 10; - - /** - * Slop distance for jumpy pointer detection. - * The vertical range of the screen divided by this is our epsilon value. - */ - private static final int JUMPY_EPSILON_DIVISOR = 212; - - /** Number of jumpy points to drop for touchscreens that need it. */ - private static final int JUMPY_TRANSITION_DROPS = 3; - private static final int JUMPY_DROP_LIMIT = 3; - - final int id; - final int classes; - final String name; - final AbsoluteInfo absX; - final AbsoluteInfo absY; - final AbsoluteInfo absPressure; - final AbsoluteInfo absSize; - - long mKeyDownTime = 0; - int mMetaKeysState = 0; - - // For use by KeyInputQueue for keeping track of the current touch - // data in the old non-multi-touch protocol. - final int[] curTouchVals = new int[MotionEvent.NUM_SAMPLE_DATA * 2]; - - final MotionState mAbs = new MotionState(0, 0); - final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD, - TRACKBALL_MOVEMENT_THRESHOLD); - - static class MotionState { - int xPrecision; - int yPrecision; - float xMoveScale; - float yMoveScale; - MotionEvent currentMove = null; - boolean changed = false; - boolean everChanged = false; - long mDownTime = 0; - - // The currently assigned pointer IDs, corresponding to the last data. - int[] mPointerIds = new int[MAX_POINTERS]; - - // This is the last generated pointer data, ordered to match - // mPointerIds. - boolean mSkipLastPointers; - int mLastNumPointers = 0; - final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; - - // This is the next set of pointer data being generated. It is not - // in any known order, and will be propagated in to mLastData - // as part of mapping it to the appropriate pointer IDs. - // Note that we have one extra sample of data here, to help clients - // avoid doing bounds checking. - int mNextNumPointers = 0; - final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) - + MotionEvent.NUM_SAMPLE_DATA]; - - // Used to determine whether we dropped bad data, to avoid doing - // it repeatedly. - final boolean[] mDroppedBadPoint = new boolean[MAX_POINTERS]; - - // Used to count the number of jumpy points dropped. - private int mJumpyPointsDropped = 0; - - // Used to perform averaging of reported coordinates, to smooth - // the data and filter out transients during a release. - static final int HISTORY_SIZE = 5; - int[] mHistoryDataStart = new int[MAX_POINTERS]; - int[] mHistoryDataEnd = new int[MAX_POINTERS]; - final int[] mHistoryData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS) - * HISTORY_SIZE]; - final int[] mAveragedData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; - - // Temporary data structures for doing the pointer ID mapping. - final int[] mLast2Next = new int[MAX_POINTERS]; - final int[] mNext2Last = new int[MAX_POINTERS]; - final long[] mNext2LastDistance = new long[MAX_POINTERS]; - - // Temporary data structure for generating the final motion data. - final float[] mReportData = new float[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS]; - - // This is not used here, but can be used by callers for state tracking. - int mAddingPointerOffset = 0; - final boolean[] mDown = new boolean[MAX_POINTERS]; - - void dumpIntArray(PrintWriter pw, int[] array) { - pw.print("["); - for (int i=0; i<array.length; i++) { - if (i > 0) pw.print(", "); - pw.print(array[i]); - } - pw.print("]"); - } - - void dumpBooleanArray(PrintWriter pw, boolean[] array) { - pw.print("["); - for (int i=0; i<array.length; i++) { - if (i > 0) pw.print(", "); - pw.print(array[i] ? "true" : "false"); - } - pw.print("]"); - } - - void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("xPrecision="); pw.print(xPrecision); - pw.print(" yPrecision="); pw.println(yPrecision); - pw.print(prefix); pw.print("xMoveScale="); pw.print(xMoveScale); - pw.print(" yMoveScale="); pw.println(yMoveScale); - if (currentMove != null) { - pw.print(prefix); pw.print("currentMove="); pw.println(currentMove); - } - if (changed || mDownTime != 0) { - pw.print(prefix); pw.print("changed="); pw.print(changed); - pw.print(" mDownTime="); pw.println(mDownTime); - } - pw.print(prefix); pw.print("mPointerIds="); dumpIntArray(pw, mPointerIds); - pw.println(""); - if (mSkipLastPointers || mLastNumPointers != 0) { - pw.print(prefix); pw.print("mSkipLastPointers="); pw.print(mSkipLastPointers); - pw.print(" mLastNumPointers="); pw.println(mLastNumPointers); - pw.print(prefix); pw.print("mLastData="); dumpIntArray(pw, mLastData); - pw.println(""); - } - if (mNextNumPointers != 0) { - pw.print(prefix); pw.print("mNextNumPointers="); pw.println(mNextNumPointers); - pw.print(prefix); pw.print("mNextData="); dumpIntArray(pw, mNextData); - pw.println(""); - } - pw.print(prefix); pw.print("mDroppedBadPoint="); - dumpBooleanArray(pw, mDroppedBadPoint); pw.println(""); - pw.print(prefix); pw.print("mAddingPointerOffset="); pw.println(mAddingPointerOffset); - pw.print(prefix); pw.print("mDown="); - dumpBooleanArray(pw, mDown); pw.println(""); - } - - MotionState(int mx, int my) { - xPrecision = mx; - yPrecision = my; - xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f; - yMoveScale = my != 0 ? (1.0f/my) : 1.0f; - for (int i=0; i<MAX_POINTERS; i++) { - mPointerIds[i] = i; - } - } - - /** - * Special hack for devices that have bad screen data: if one of the - * points has moved more than a screen height from the last position, - * then drop it. - */ - void dropBadPoint(InputDevice dev) { - // We should always have absY, but let's be paranoid. - if (dev.absY == null) { - return; - } - // Don't do anything if a finger is going down or up. We run - // here before assigning pointer IDs, so there isn't a good - // way to do per-finger matching. - if (mNextNumPointers != mLastNumPointers) { - return; - } - - // We consider a single movement across more than a 7/16 of - // the long size of the screen to be bad. This was a magic value - // determined by looking at the maximum distance it is feasible - // to actually move in one sample. - final int maxDy = ((dev.absY.maxValue-dev.absY.minValue)*7)/16; - - // Look through all new points and see if any are farther than - // acceptable from all previous points. - for (int i=mNextNumPointers-1; i>=0; i--) { - final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - //final int x = mNextData[ioff + MotionEvent.SAMPLE_X]; - final int y = mNextData[ioff + MotionEvent.SAMPLE_Y]; - if (DEBUG_HACKS) Slog.v("InputDevice", "Looking at next point #" + i + ": y=" + y); - boolean dropped = false; - if (!mDroppedBadPoint[i] && mLastNumPointers > 0) { - dropped = true; - int closestDy = -1; - int closestY = -1; - // We will drop this new point if it is sufficiently - // far away from -all- last points. - for (int j=mLastNumPointers-1; j>=0; j--) { - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - //int dx = x - mLastData[joff + MotionEvent.SAMPLE_X]; - int dy = y - mLastData[joff + MotionEvent.SAMPLE_Y]; - //if (dx < 0) dx = -dx; - if (dy < 0) dy = -dy; - if (DEBUG_HACKS) Slog.v("InputDevice", "Comparing with last point #" + j - + ": y=" + mLastData[joff] + " dy=" + dy); - if (dy < maxDy) { - dropped = false; - break; - } else if (closestDy < 0 || dy < closestDy) { - closestDy = dy; - closestY = mLastData[joff + MotionEvent.SAMPLE_Y]; - } - } - if (dropped) { - dropped = true; - Slog.i("InputDevice", "Dropping bad point #" + i - + ": newY=" + y + " closestDy=" + closestDy - + " maxDy=" + maxDy); - mNextData[ioff + MotionEvent.SAMPLE_Y] = closestY; - break; - } - } - mDroppedBadPoint[i] = dropped; - } - } - - void dropJumpyPoint(InputDevice dev) { - // We should always have absY, but let's be paranoid. - if (dev.absY == null) { - return; - } - final int jumpyEpsilon = dev.absY.range / JUMPY_EPSILON_DIVISOR; - - final int nextNumPointers = mNextNumPointers; - final int lastNumPointers = mLastNumPointers; - final int[] nextData = mNextData; - final int[] lastData = mLastData; - - if (nextNumPointers != mLastNumPointers) { - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Different pointer count " + lastNumPointers + - " -> " + nextNumPointers); - for (int i = 0; i < nextNumPointers; i++) { - int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - Slog.d("InputDevice", "Pointer " + i + " (" + - mNextData[ioff + MotionEvent.SAMPLE_X] + ", " + - mNextData[ioff + MotionEvent.SAMPLE_Y] + ")"); - } - } - - // Just drop the first few events going from 1 to 2 pointers. - // They're bad often enough that they're not worth considering. - if (lastNumPointers == 1 && nextNumPointers == 2 - && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) { - mNextNumPointers = 1; - mJumpyPointsDropped++; - } else if (lastNumPointers == 2 && nextNumPointers == 1 - && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) { - // The event when we go from 2 -> 1 tends to be messed up too - System.arraycopy(lastData, 0, nextData, 0, - lastNumPointers * MotionEvent.NUM_SAMPLE_DATA); - mNextNumPointers = lastNumPointers; - mJumpyPointsDropped++; - - if (DEBUG_HACKS) { - for (int i = 0; i < mNextNumPointers; i++) { - int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - Slog.d("InputDevice", "Pointer " + i + " replaced (" + - mNextData[ioff + MotionEvent.SAMPLE_X] + ", " + - mNextData[ioff + MotionEvent.SAMPLE_Y] + ")"); - } - } - } else { - mJumpyPointsDropped = 0; - - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Transition - drop limit reset"); - } - } - return; - } - - // A 'jumpy' point is one where the coordinate value for one axis - // has jumped to the other pointer's location. No need to do anything - // else if we only have one pointer. - if (nextNumPointers < 2) { - return; - } - - int badPointerIndex = -1; - int badPointerReplaceXWith = 0; - int badPointerReplaceYWith = 0; - int badPointerDistance = Integer.MIN_VALUE; - for (int i = nextNumPointers - 1; i >= 0; i--) { - boolean dropx = false; - boolean dropy = false; - - // Limit how many times a jumpy point can get dropped. - if (mJumpyPointsDropped < JUMPY_DROP_LIMIT) { - final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - final int x = nextData[ioff + MotionEvent.SAMPLE_X]; - final int y = nextData[ioff + MotionEvent.SAMPLE_Y]; - - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Point " + i + " (" + x + ", " + y + ")"); - } - - // Check if a touch point is too close to another's coordinates - for (int j = 0; j < nextNumPointers && !dropx && !dropy; j++) { - if (j == i) { - continue; - } - - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - final int xOther = nextData[joff + MotionEvent.SAMPLE_X]; - final int yOther = nextData[joff + MotionEvent.SAMPLE_Y]; - - dropx = Math.abs(x - xOther) <= jumpyEpsilon; - dropy = Math.abs(y - yOther) <= jumpyEpsilon; - } - - if (dropx) { - int xreplace = lastData[MotionEvent.SAMPLE_X]; - int yreplace = lastData[MotionEvent.SAMPLE_Y]; - int distance = Math.abs(yreplace - y); - for (int j = 1; j < lastNumPointers; j++) { - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - int lasty = lastData[joff + MotionEvent.SAMPLE_Y]; - int currDist = Math.abs(lasty - y); - if (currDist < distance) { - xreplace = lastData[joff + MotionEvent.SAMPLE_X]; - yreplace = lasty; - distance = currDist; - } - } - - int badXDelta = Math.abs(xreplace - x); - if (badXDelta > badPointerDistance) { - badPointerDistance = badXDelta; - badPointerIndex = i; - badPointerReplaceXWith = xreplace; - badPointerReplaceYWith = yreplace; - } - } else if (dropy) { - int xreplace = lastData[MotionEvent.SAMPLE_X]; - int yreplace = lastData[MotionEvent.SAMPLE_Y]; - int distance = Math.abs(xreplace - x); - for (int j = 1; j < lastNumPointers; j++) { - final int joff = j * MotionEvent.NUM_SAMPLE_DATA; - int lastx = lastData[joff + MotionEvent.SAMPLE_X]; - int currDist = Math.abs(lastx - x); - if (currDist < distance) { - xreplace = lastx; - yreplace = lastData[joff + MotionEvent.SAMPLE_Y]; - distance = currDist; - } - } - - int badYDelta = Math.abs(yreplace - y); - if (badYDelta > badPointerDistance) { - badPointerDistance = badYDelta; - badPointerIndex = i; - badPointerReplaceXWith = xreplace; - badPointerReplaceYWith = yreplace; - } - } - } - } - if (badPointerIndex >= 0) { - if (DEBUG_HACKS) { - Slog.d("InputDevice", "Replacing bad pointer " + badPointerIndex + - " with (" + badPointerReplaceXWith + ", " + badPointerReplaceYWith + - ")"); - } - - final int offset = badPointerIndex * MotionEvent.NUM_SAMPLE_DATA; - nextData[offset + MotionEvent.SAMPLE_X] = badPointerReplaceXWith; - nextData[offset + MotionEvent.SAMPLE_Y] = badPointerReplaceYWith; - mJumpyPointsDropped++; - } else { - mJumpyPointsDropped = 0; - } - } - - /** - * Special hack for devices that have bad screen data: aggregate and - * compute averages of the coordinate data, to reduce the amount of - * jitter seen by applications. - */ - int[] generateAveragedData(int upOrDownPointer, int lastNumPointers, - int nextNumPointers) { - final int numPointers = mLastNumPointers; - final int[] rawData = mLastData; - if (DEBUG_HACKS) Slog.v("InputDevice", "lastNumPointers=" + lastNumPointers - + " nextNumPointers=" + nextNumPointers - + " numPointers=" + numPointers); - for (int i=0; i<numPointers; i++) { - final int ioff = i * MotionEvent.NUM_SAMPLE_DATA; - // We keep the average data in offsets based on the pointer - // ID, so we don't need to move it around as fingers are - // pressed and released. - final int p = mPointerIds[i]; - final int poff = p * MotionEvent.NUM_SAMPLE_DATA * HISTORY_SIZE; - if (i == upOrDownPointer && lastNumPointers != nextNumPointers) { - if (lastNumPointers < nextNumPointers) { - // This pointer is going down. Clear its history - // and start fresh. - if (DEBUG_HACKS) Slog.v("InputDevice", "Pointer down @ index " - + upOrDownPointer + " id " + mPointerIds[i]); - mHistoryDataStart[i] = 0; - mHistoryDataEnd[i] = 0; - System.arraycopy(rawData, ioff, mHistoryData, poff, - MotionEvent.NUM_SAMPLE_DATA); - System.arraycopy(rawData, ioff, mAveragedData, ioff, - MotionEvent.NUM_SAMPLE_DATA); - continue; - } else { - // The pointer is going up. Just fall through to - // recompute the last averaged point (and don't add - // it as a new point to include in the average). - if (DEBUG_HACKS) Slog.v("InputDevice", "Pointer up @ index " - + upOrDownPointer + " id " + mPointerIds[i]); - } - } else { - int end = mHistoryDataEnd[i]; - int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); - int oldX = mHistoryData[eoff + MotionEvent.SAMPLE_X]; - int oldY = mHistoryData[eoff + MotionEvent.SAMPLE_Y]; - int newX = rawData[ioff + MotionEvent.SAMPLE_X]; - int newY = rawData[ioff + MotionEvent.SAMPLE_Y]; - int dx = newX-oldX; - int dy = newY-oldY; - int delta = dx*dx + dy*dy; - if (DEBUG_HACKS) Slog.v("InputDevice", "Delta from last: " + delta); - if (delta >= (75*75)) { - // Magic number, if moving farther than this, turn - // off filtering to avoid lag in response. - mHistoryDataStart[i] = 0; - mHistoryDataEnd[i] = 0; - System.arraycopy(rawData, ioff, mHistoryData, poff, - MotionEvent.NUM_SAMPLE_DATA); - System.arraycopy(rawData, ioff, mAveragedData, ioff, - MotionEvent.NUM_SAMPLE_DATA); - continue; - } else { - end++; - if (end >= HISTORY_SIZE) { - end -= HISTORY_SIZE; - } - mHistoryDataEnd[i] = end; - int noff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); - mHistoryData[noff + MotionEvent.SAMPLE_X] = newX; - mHistoryData[noff + MotionEvent.SAMPLE_Y] = newY; - mHistoryData[noff + MotionEvent.SAMPLE_PRESSURE] - = rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; - int start = mHistoryDataStart[i]; - if (end == start) { - start++; - if (start >= HISTORY_SIZE) { - start -= HISTORY_SIZE; - } - mHistoryDataStart[i] = start; - } - } - } - - // Now compute the average. - int start = mHistoryDataStart[i]; - int end = mHistoryDataEnd[i]; - int x=0, y=0; - int totalPressure = 0; - while (start != end) { - int soff = poff + (start*MotionEvent.NUM_SAMPLE_DATA); - int pressure = mHistoryData[soff + MotionEvent.SAMPLE_PRESSURE]; - if (pressure <= 0) pressure = 1; - x += mHistoryData[soff + MotionEvent.SAMPLE_X] * pressure; - y += mHistoryData[soff + MotionEvent.SAMPLE_Y] * pressure; - totalPressure += pressure; - start++; - if (start >= HISTORY_SIZE) start = 0; - } - int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA); - int pressure = mHistoryData[eoff + MotionEvent.SAMPLE_PRESSURE]; - if (pressure <= 0) pressure = 1; - x += mHistoryData[eoff + MotionEvent.SAMPLE_X] * pressure; - y += mHistoryData[eoff + MotionEvent.SAMPLE_Y] * pressure; - totalPressure += pressure; - x /= totalPressure; - y /= totalPressure; - if (DEBUG_HACKS) Slog.v("InputDevice", "Averaging " + totalPressure - + " weight: (" + x + "," + y + ")"); - mAveragedData[ioff + MotionEvent.SAMPLE_X] = x; - mAveragedData[ioff + MotionEvent.SAMPLE_Y] = y; - mAveragedData[ioff + MotionEvent.SAMPLE_PRESSURE] = - rawData[ioff + MotionEvent.SAMPLE_PRESSURE]; - mAveragedData[ioff + MotionEvent.SAMPLE_SIZE] = - rawData[ioff + MotionEvent.SAMPLE_SIZE]; - } - return mAveragedData; - } - - private boolean assignPointer(int nextIndex, boolean allowOverlap) { - final int lastNumPointers = mLastNumPointers; - final int[] next2Last = mNext2Last; - final long[] next2LastDistance = mNext2LastDistance; - final int[] last2Next = mLast2Next; - final int[] lastData = mLastData; - final int[] nextData = mNextData; - final int id = nextIndex * MotionEvent.NUM_SAMPLE_DATA; - - if (DEBUG_POINTERS) Slog.v("InputDevice", "assignPointer: nextIndex=" - + nextIndex + " dataOff=" + id); - final int x1 = nextData[id + MotionEvent.SAMPLE_X]; - final int y1 = nextData[id + MotionEvent.SAMPLE_Y]; - - long bestDistance = -1; - int bestIndex = -1; - for (int j=0; j<lastNumPointers; j++) { - // If we are not allowing multiple new points to be assigned - // to the same old pointer, then skip this one if it is already - // detected as a conflict (-2). - if (!allowOverlap && last2Next[j] < -1) { - continue; - } - final int jd = j * MotionEvent.NUM_SAMPLE_DATA; - final int xd = lastData[jd + MotionEvent.SAMPLE_X] - x1; - final int yd = lastData[jd + MotionEvent.SAMPLE_Y] - y1; - final long distance = xd*(long)xd + yd*(long)yd; - if (bestDistance == -1 || distance < bestDistance) { - bestDistance = distance; - bestIndex = j; - } - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", "New index " + nextIndex - + " best old index=" + bestIndex + " (distance=" - + bestDistance + ")"); - next2Last[nextIndex] = bestIndex; - next2LastDistance[nextIndex] = bestDistance; - - if (bestIndex < 0) { - return true; - } - - if (last2Next[bestIndex] == -1) { - last2Next[bestIndex] = nextIndex; - return false; - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", "Old index " + bestIndex - + " has multiple best new pointers!"); - - last2Next[bestIndex] = -2; - return true; - } - - private int updatePointerIdentifiers() { - final int[] lastData = mLastData; - final int[] nextData = mNextData; - final int nextNumPointers = mNextNumPointers; - final int lastNumPointers = mLastNumPointers; - - if (nextNumPointers == 1 && lastNumPointers == 1) { - System.arraycopy(nextData, 0, lastData, 0, - MotionEvent.NUM_SAMPLE_DATA); - return -1; - } - - // Clear our old state. - final int[] last2Next = mLast2Next; - for (int i=0; i<lastNumPointers; i++) { - last2Next[i] = -1; - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Update pointers: lastNumPointers=" + lastNumPointers - + " nextNumPointers=" + nextNumPointers); - - // Figure out the closes new points to the previous points. - final int[] next2Last = mNext2Last; - final long[] next2LastDistance = mNext2LastDistance; - boolean conflicts = false; - for (int i=0; i<nextNumPointers; i++) { - conflicts |= assignPointer(i, true); - } - - // Resolve ambiguities in pointer mappings, when two or more - // new pointer locations find their best previous location is - // the same. - if (conflicts) { - if (DEBUG_POINTERS) Slog.v("InputDevice", "Resolving conflicts"); - - for (int i=0; i<lastNumPointers; i++) { - if (last2Next[i] != -2) { - continue; - } - - // Note that this algorithm is far from perfect. Ideally - // we should do something like the one described at - // http://portal.acm.org/citation.cfm?id=997856 - - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Resolving last index #" + i); - - int numFound; - do { - numFound = 0; - long worstDistance = 0; - int worstJ = -1; - for (int j=0; j<nextNumPointers; j++) { - if (next2Last[j] != i) { - continue; - } - numFound++; - if (worstDistance < next2LastDistance[j]) { - worstDistance = next2LastDistance[j]; - worstJ = j; - } - } - - if (worstJ >= 0) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Worst new pointer: " + worstJ - + " (distance=" + worstDistance + ")"); - if (assignPointer(worstJ, false)) { - // In this case there is no last pointer - // remaining for this new one! - next2Last[worstJ] = -1; - } - } - } while (numFound > 2); - } - } - - int retIndex = -1; - - if (lastNumPointers < nextNumPointers) { - // We have one or more new pointers that are down. Create a - // new pointer identifier for one of them. - if (DEBUG_POINTERS) Slog.v("InputDevice", "Adding new pointer"); - int nextId = 0; - int i=0; - while (i < lastNumPointers) { - if (mPointerIds[i] > nextId) { - // Found a hole, insert the pointer here. - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Inserting new pointer at hole " + i); - System.arraycopy(mPointerIds, i, mPointerIds, - i+1, lastNumPointers-i); - System.arraycopy(lastData, i*MotionEvent.NUM_SAMPLE_DATA, - lastData, (i+1)*MotionEvent.NUM_SAMPLE_DATA, - (lastNumPointers-i)*MotionEvent.NUM_SAMPLE_DATA); - System.arraycopy(next2Last, i, next2Last, - i+1, lastNumPointers-i); - break; - } - i++; - nextId++; - } - - if (DEBUG_POINTERS) Slog.v("InputDevice", - "New pointer id " + nextId + " at index " + i); - - mLastNumPointers++; - retIndex = i; - mPointerIds[i] = nextId; - - // And assign this identifier to the first new pointer. - for (int j=0; j<nextNumPointers; j++) { - if (next2Last[j] < 0) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Assigning new id to new pointer index " + j); - next2Last[j] = i; - break; - } - } - } - - // Propagate all of the current data into the appropriate - // location in the old data to match the pointer ID that was - // assigned to it. - for (int i=0; i<nextNumPointers; i++) { - int lastIndex = next2Last[i]; - if (lastIndex >= 0) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Copying next pointer index " + i - + " to last index " + lastIndex); - System.arraycopy(nextData, i*MotionEvent.NUM_SAMPLE_DATA, - lastData, lastIndex*MotionEvent.NUM_SAMPLE_DATA, - MotionEvent.NUM_SAMPLE_DATA); - } - } - - if (lastNumPointers > nextNumPointers) { - // One or more pointers has gone up. Find the first one, - // and adjust accordingly. - if (DEBUG_POINTERS) Slog.v("InputDevice", "Removing old pointer"); - for (int i=0; i<lastNumPointers; i++) { - if (last2Next[i] == -1) { - if (DEBUG_POINTERS) Slog.v("InputDevice", - "Removing old pointer at index " + i); - retIndex = i; - break; - } - } - } - - return retIndex; - } - - void removeOldPointer(int index) { - final int lastNumPointers = mLastNumPointers; - if (index >= 0 && index < lastNumPointers) { - System.arraycopy(mPointerIds, index+1, mPointerIds, - index, lastNumPointers-index-1); - System.arraycopy(mLastData, (index+1)*MotionEvent.NUM_SAMPLE_DATA, - mLastData, (index)*MotionEvent.NUM_SAMPLE_DATA, - (lastNumPointers-index-1)*MotionEvent.NUM_SAMPLE_DATA); - mLastNumPointers--; - } - } - - MotionEvent generateAbsMotion(InputDevice device, long curTime, - long curTimeNano, Display display, int orientation, - int metaState) { - - if (mSkipLastPointers) { - mSkipLastPointers = false; - mLastNumPointers = 0; - } - - if (mNextNumPointers <= 0 && mLastNumPointers <= 0) { - return null; - } - - final int lastNumPointers = mLastNumPointers; - final int nextNumPointers = mNextNumPointers; - if (mNextNumPointers > MAX_POINTERS) { - Slog.w("InputDevice", "Number of pointers " + mNextNumPointers - + " exceeded maximum of " + MAX_POINTERS); - mNextNumPointers = MAX_POINTERS; - } - - int upOrDownPointer = updatePointerIdentifiers(); - - final float[] reportData = mReportData; - final int[] rawData; - if (KeyInputQueue.BAD_TOUCH_HACK) { - rawData = generateAveragedData(upOrDownPointer, lastNumPointers, - nextNumPointers); - } else { - rawData = mLastData; - } - - final int numPointers = mLastNumPointers; - - if (DEBUG_POINTERS) Slog.v("InputDevice", "Processing " - + numPointers + " pointers (going from " + lastNumPointers - + " to " + nextNumPointers + ")"); - - for (int i=0; i<numPointers; i++) { - final int pos = i * MotionEvent.NUM_SAMPLE_DATA; - reportData[pos + MotionEvent.SAMPLE_X] = rawData[pos + MotionEvent.SAMPLE_X]; - reportData[pos + MotionEvent.SAMPLE_Y] = rawData[pos + MotionEvent.SAMPLE_Y]; - reportData[pos + MotionEvent.SAMPLE_PRESSURE] = rawData[pos + MotionEvent.SAMPLE_PRESSURE]; - reportData[pos + MotionEvent.SAMPLE_SIZE] = rawData[pos + MotionEvent.SAMPLE_SIZE]; - } - - int action; - int edgeFlags = 0; - if (nextNumPointers != lastNumPointers) { - if (nextNumPointers > lastNumPointers) { - if (lastNumPointers == 0) { - action = MotionEvent.ACTION_DOWN; - mDownTime = curTime; - } else { - action = MotionEvent.ACTION_POINTER_DOWN - | (upOrDownPointer << MotionEvent.ACTION_POINTER_INDEX_SHIFT); - } - } else { - if (numPointers == 1) { - action = MotionEvent.ACTION_UP; - } else { - action = MotionEvent.ACTION_POINTER_UP - | (upOrDownPointer << MotionEvent.ACTION_POINTER_INDEX_SHIFT); - } - } - currentMove = null; - } else { - action = MotionEvent.ACTION_MOVE; - } - - final int dispW = display.getWidth()-1; - final int dispH = display.getHeight()-1; - int w = dispW; - int h = dispH; - if (orientation == Surface.ROTATION_90 - || orientation == Surface.ROTATION_270) { - int tmp = w; - w = h; - h = tmp; - } - - final AbsoluteInfo absX = device.absX; - final AbsoluteInfo absY = device.absY; - final AbsoluteInfo absPressure = device.absPressure; - final AbsoluteInfo absSize = device.absSize; - for (int i=0; i<numPointers; i++) { - final int j = i * MotionEvent.NUM_SAMPLE_DATA; - - if (absX != null) { - reportData[j + MotionEvent.SAMPLE_X] = - ((reportData[j + MotionEvent.SAMPLE_X]-absX.minValue) - / absX.range) * w; - } - if (absY != null) { - reportData[j + MotionEvent.SAMPLE_Y] = - ((reportData[j + MotionEvent.SAMPLE_Y]-absY.minValue) - / absY.range) * h; - } - if (absPressure != null) { - reportData[j + MotionEvent.SAMPLE_PRESSURE] = - ((reportData[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue) - / (float)absPressure.range); - } - if (absSize != null) { - reportData[j + MotionEvent.SAMPLE_SIZE] = - ((reportData[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue) - / (float)absSize.range); - } - - switch (orientation) { - case Surface.ROTATION_90: { - final float temp = reportData[j + MotionEvent.SAMPLE_X]; - reportData[j + MotionEvent.SAMPLE_X] = reportData[j + MotionEvent.SAMPLE_Y]; - reportData[j + MotionEvent.SAMPLE_Y] = w-temp; - break; - } - case Surface.ROTATION_180: { - reportData[j + MotionEvent.SAMPLE_X] = w-reportData[j + MotionEvent.SAMPLE_X]; - reportData[j + MotionEvent.SAMPLE_Y] = h-reportData[j + MotionEvent.SAMPLE_Y]; - break; - } - case Surface.ROTATION_270: { - final float temp = reportData[j + MotionEvent.SAMPLE_X]; - reportData[j + MotionEvent.SAMPLE_X] = h-reportData[j + MotionEvent.SAMPLE_Y]; - reportData[j + MotionEvent.SAMPLE_Y] = temp; - break; - } - } - } - - // We only consider the first pointer when computing the edge - // flags, since they are global to the event. - if (action == MotionEvent.ACTION_DOWN) { - if (reportData[MotionEvent.SAMPLE_X] <= 0) { - edgeFlags |= MotionEvent.EDGE_LEFT; - } else if (reportData[MotionEvent.SAMPLE_X] >= dispW) { - edgeFlags |= MotionEvent.EDGE_RIGHT; - } - if (reportData[MotionEvent.SAMPLE_Y] <= 0) { - edgeFlags |= MotionEvent.EDGE_TOP; - } else if (reportData[MotionEvent.SAMPLE_Y] >= dispH) { - edgeFlags |= MotionEvent.EDGE_BOTTOM; - } - } - - if (currentMove != null) { - if (false) Slog.i("InputDevice", "Adding batch x=" - + reportData[MotionEvent.SAMPLE_X] - + " y=" + reportData[MotionEvent.SAMPLE_Y] - + " to " + currentMove); - currentMove.addBatch(curTime, reportData, metaState); - if (WindowManagerPolicy.WATCH_POINTER) { - Slog.i("KeyInputQueue", "Updating: " + currentMove); - } - return null; - } - - MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, - curTimeNano, action, numPointers, mPointerIds, reportData, - metaState, xPrecision, yPrecision, device.id, edgeFlags); - if (action == MotionEvent.ACTION_MOVE) { - currentMove = me; - } - - if (nextNumPointers < lastNumPointers) { - removeOldPointer(upOrDownPointer); - } - - return me; - } - - boolean hasMore() { - return mLastNumPointers != mNextNumPointers; - } - - void finish() { - mNextNumPointers = mAddingPointerOffset = 0; - mNextData[MotionEvent.SAMPLE_PRESSURE] = 0; - } - - MotionEvent generateRelMotion(InputDevice device, long curTime, - long curTimeNano, int orientation, int metaState) { - - final float[] scaled = mReportData; - - // For now we only support 1 pointer with relative motions. - scaled[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_Y] = mNextData[MotionEvent.SAMPLE_Y]; - scaled[MotionEvent.SAMPLE_PRESSURE] = 1.0f; - scaled[MotionEvent.SAMPLE_SIZE] = 0; - int edgeFlags = 0; - - int action; - if (mNextNumPointers != mLastNumPointers) { - mNextData[MotionEvent.SAMPLE_X] = - mNextData[MotionEvent.SAMPLE_Y] = 0; - if (mNextNumPointers > 0 && mLastNumPointers == 0) { - action = MotionEvent.ACTION_DOWN; - mDownTime = curTime; - } else if (mNextNumPointers == 0) { - action = MotionEvent.ACTION_UP; - } else { - action = MotionEvent.ACTION_MOVE; - } - mLastNumPointers = mNextNumPointers; - currentMove = null; - } else { - action = MotionEvent.ACTION_MOVE; - } - - scaled[MotionEvent.SAMPLE_X] *= xMoveScale; - scaled[MotionEvent.SAMPLE_Y] *= yMoveScale; - switch (orientation) { - case Surface.ROTATION_90: { - final float temp = scaled[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_X] = scaled[MotionEvent.SAMPLE_Y]; - scaled[MotionEvent.SAMPLE_Y] = -temp; - break; - } - case Surface.ROTATION_180: { - scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_Y] = -scaled[MotionEvent.SAMPLE_Y]; - break; - } - case Surface.ROTATION_270: { - final float temp = scaled[MotionEvent.SAMPLE_X]; - scaled[MotionEvent.SAMPLE_X] = -scaled[MotionEvent.SAMPLE_Y]; - scaled[MotionEvent.SAMPLE_Y] = temp; - break; - } - } - - if (currentMove != null) { - if (false) Slog.i("InputDevice", "Adding batch x=" - + scaled[MotionEvent.SAMPLE_X] - + " y=" + scaled[MotionEvent.SAMPLE_Y] - + " to " + currentMove); - currentMove.addBatch(curTime, scaled, metaState); - if (WindowManagerPolicy.WATCH_POINTER) { - Slog.i("KeyInputQueue", "Updating: " + currentMove); - } - return null; - } - - MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime, - curTimeNano, action, 1, mPointerIds, scaled, metaState, - xPrecision, yPrecision, device.id, edgeFlags); - if (action == MotionEvent.ACTION_MOVE) { - currentMove = me; - } - return me; - } - } - - static class AbsoluteInfo { - int minValue; - int maxValue; - int range; - int flat; - int fuzz; - - final void dump(PrintWriter pw) { - pw.print("minValue="); pw.print(minValue); - pw.print(" maxValue="); pw.print(maxValue); - pw.print(" range="); pw.print(range); - pw.print(" flat="); pw.print(flat); - pw.print(" fuzz="); pw.print(fuzz); - } - }; - - InputDevice(int _id, int _classes, String _name, - AbsoluteInfo _absX, AbsoluteInfo _absY, - AbsoluteInfo _absPressure, AbsoluteInfo _absSize) { - id = _id; - classes = _classes; - name = _name; - absX = _absX; - absY = _absY; - absPressure = _absPressure; - absSize = _absSize; - } -}; diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java new file mode 100644 index 0000000..9195123 --- /dev/null +++ b/services/java/com/android/server/InputManager.java @@ -0,0 +1,486 @@ +/* + * 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; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.os.Environment; +import android.os.LocalPowerManager; +import android.os.PowerManager; +import android.util.Slog; +import android.util.Xml; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.WindowManagerPolicy; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; + +/* + * Wraps the C++ InputManager and provides its callbacks. + * + * XXX Tempted to promote this to a first-class service, ie. InputManagerService, to + * improve separation of concerns with respect to the window manager. + */ +public class InputManager { + static final String TAG = "InputManager"; + + private final Callbacks mCallbacks; + private final Context mContext; + private final WindowManagerService mWindowManagerService; + + private int mTouchScreenConfig; + private int mKeyboardConfig; + private int mNavigationConfig; + + private static native void nativeInit(Callbacks callbacks); + private static native void nativeStart(); + private static native void nativeSetDisplaySize(int displayId, int width, int height); + private static native void nativeSetDisplayOrientation(int displayId, int rotation); + + private static native int nativeGetScanCodeState(int deviceId, int sourceMask, + int scanCode); + private static native int nativeGetKeyCodeState(int deviceId, int sourceMask, + int keyCode); + private static native int nativeGetSwitchState(int deviceId, int sourceMask, + int sw); + private static native boolean nativeHasKeys(int deviceId, int sourceMask, + int[] keyCodes, boolean[] keyExists); + private static native void nativeRegisterInputChannel(InputChannel inputChannel); + private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native int nativeInjectInputEvent(InputEvent event, + int injectorPid, int injectorUid, int syncMode, int timeoutMillis); + private static native void nativeSetInputWindows(InputWindow[] windows); + private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); + private static native void nativeSetFocusedApplication(InputApplication application); + private static native void nativePreemptInputDispatch(); + private static native String nativeDump(); + + // Input event injection constants defined in InputDispatcher.h. + static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; + static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1; + static final int INPUT_EVENT_INJECTION_FAILED = 2; + static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3; + + // Input event injection synchronization modes defined in InputDispatcher.h + static final int INPUT_EVENT_INJECTION_SYNC_NONE = 0; + static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1; + static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH = 2; + + // Key states (may be returned by queries about the current state of a + // particular key code, scan code or switch). + + /** The key state is unknown or the requested key itself is not supported. */ + public static final int KEY_STATE_UNKNOWN = -1; + + /** The key is up. /*/ + public static final int KEY_STATE_UP = 0; + + /** The key is down. */ + public static final int KEY_STATE_DOWN = 1; + + /** The key is down but is a virtual key press that is being emulated by the system. */ + public static final int KEY_STATE_VIRTUAL = 2; + + public InputManager(Context context, WindowManagerService windowManagerService) { + this.mContext = context; + this.mWindowManagerService = windowManagerService; + + this.mCallbacks = new Callbacks(); + + mTouchScreenConfig = Configuration.TOUCHSCREEN_NOTOUCH; + mKeyboardConfig = Configuration.KEYBOARD_NOKEYS; + mNavigationConfig = Configuration.NAVIGATION_NONAV; + + init(); + } + + private void init() { + Slog.i(TAG, "Initializing input manager"); + nativeInit(mCallbacks); + } + + public void start() { + Slog.i(TAG, "Starting input manager"); + nativeStart(); + } + + public void setDisplaySize(int displayId, int width, int height) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid display id or dimensions."); + } + + Slog.i(TAG, "Setting display #" + displayId + " size to " + width + "x" + height); + nativeSetDisplaySize(displayId, width, height); + } + + public void setDisplayOrientation(int displayId, int rotation) { + if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) { + throw new IllegalArgumentException("Invalid rotation."); + } + + Slog.i(TAG, "Setting display #" + displayId + " orientation to " + rotation); + nativeSetDisplayOrientation(displayId, rotation); + } + + public void getInputConfiguration(Configuration config) { + if (config == null) { + throw new IllegalArgumentException("config must not be null."); + } + + config.touchscreen = mTouchScreenConfig; + config.keyboard = mKeyboardConfig; + config.navigation = mNavigationConfig; + } + + /** + * Gets the current state of a key or button by key code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCode The key code to check. + * @return The key state. + */ + public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) { + return nativeGetKeyCodeState(deviceId, sourceMask, keyCode); + } + + /** + * Gets the current state of a key or button by scan code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param scanCode The scan code to check. + * @return The key state. + */ + public int getScanCodeState(int deviceId, int sourceMask, int scanCode) { + return nativeGetScanCodeState(deviceId, sourceMask, scanCode); + } + + /** + * Gets the current state of a switch by switch code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param switchCode The switch code to check. + * @return The switch state. + */ + public int getSwitchState(int deviceId, int sourceMask, int switchCode) { + return nativeGetSwitchState(deviceId, sourceMask, switchCode); + } + + /** + * Determines whether the specified key codes are supported by a particular device. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCodes The array of key codes to check. + * @param keyExists An array at least as large as keyCodes whose entries will be set + * to true or false based on the presence or absence of support for the corresponding + * key codes. + * @return True if the lookup was successful, false otherwise. + */ + public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) { + if (keyCodes == null) { + throw new IllegalArgumentException("keyCodes must not be null."); + } + if (keyExists == null || keyExists.length < keyCodes.length) { + throw new IllegalArgumentException("keyExists must not be null and must be at " + + "least as large as keyCodes."); + } + + return nativeHasKeys(deviceId, sourceMask, keyCodes, keyExists); + } + + public void registerInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeRegisterInputChannel(inputChannel); + } + + public void unregisterInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeUnregisterInputChannel(inputChannel); + } + + /** + * Injects an input event into the event system on behalf of an application. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_NONE} never blocks. Injection is asynchronous and + * is assumed always to be successful. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT} waits for previous events to be + * dispatched so that the input dispatcher can determine whether input event injection will + * be permitted based on the current input focus. Does not wait for the input event to + * finish processing. + * + * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH} waits for the input event to + * be completely processed. + * + * @param event The event to inject. + * @param injectorPid The pid of the injecting application. + * @param injectorUid The uid of the injecting application. + * @param syncMode The synchronization mode. + * @param timeoutMillis The injection timeout in milliseconds. + * @return One of the INPUT_EVENT_INJECTION_XXX constants. + */ + public int injectInputEvent(InputEvent event, int injectorPid, int injectorUid, + int syncMode, int timeoutMillis) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (injectorPid < 0 || injectorUid < 0) { + throw new IllegalArgumentException("injectorPid and injectorUid must not be negative."); + } + if (timeoutMillis <= 0) { + throw new IllegalArgumentException("timeoutMillis must be positive"); + } + + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); + } + + public void setInputWindows(InputWindow[] windows) { + nativeSetInputWindows(windows); + } + + public void setFocusedApplication(InputApplication application) { + nativeSetFocusedApplication(application); + } + + public void preemptInputDispatch() { + nativePreemptInputDispatch(); + } + + public void setInputDispatchMode(boolean enabled, boolean frozen) { + nativeSetInputDispatchMode(enabled, frozen); + } + + public void dump(PrintWriter pw) { + String dumpStr = nativeDump(); + if (dumpStr != null) { + pw.println(dumpStr); + } + } + + private static final class VirtualKeyDefinition { + public int scanCode; + + // configured position data, specified in display coords + public int centerX; + public int centerY; + public int width; + public int height; + } + + /* + * Callbacks from native. + */ + private class Callbacks { + static final String TAG = "InputManager-Callbacks"; + + private static final boolean DEBUG_VIRTUAL_KEYS = false; + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; + + @SuppressWarnings("unused") + public void virtualKeyDownFeedback() { + mWindowManagerService.mInputMonitor.virtualKeyDownFeedback(); + } + + @SuppressWarnings("unused") + public void notifyConfigurationChanged(long whenNanos, + int touchScreenConfig, int keyboardConfig, int navigationConfig) { + mTouchScreenConfig = touchScreenConfig; + mKeyboardConfig = keyboardConfig; + mNavigationConfig = navigationConfig; + + mWindowManagerService.sendNewConfiguration(); + } + + @SuppressWarnings("unused") + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + mWindowManagerService.mInputMonitor.notifyLidSwitchChanged(whenNanos, lidOpen); + } + + @SuppressWarnings("unused") + public void notifyInputChannelBroken(InputChannel inputChannel) { + mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel); + } + + @SuppressWarnings("unused") + public long notifyInputChannelANR(InputChannel inputChannel) { + return mWindowManagerService.mInputMonitor.notifyInputChannelANR(inputChannel); + } + + @SuppressWarnings("unused") + public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) { + mWindowManagerService.mInputMonitor.notifyInputChannelRecoveredFromANR(inputChannel); + } + + @SuppressWarnings("unused") + public long notifyANR(Object token) { + return mWindowManagerService.mInputMonitor.notifyANR(token); + } + + @SuppressWarnings("unused") + public int interceptKeyBeforeQueueing(long whenNanos, int keyCode, boolean down, + int policyFlags, boolean isScreenOn) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( + whenNanos, keyCode, down, policyFlags, isScreenOn); + } + + @SuppressWarnings("unused") + public boolean interceptKeyBeforeDispatching(InputChannel focus, int action, + int flags, int keyCode, int metaState, int repeatCount, int policyFlags) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(focus, + action, flags, keyCode, metaState, repeatCount, policyFlags); + } + + @SuppressWarnings("unused") + public boolean checkInjectEventsPermission(int injectorPid, int injectorUid) { + return mContext.checkPermission( + android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid) + == PackageManager.PERMISSION_GRANTED; + } + + @SuppressWarnings("unused") + public void notifyAppSwitchComing() { + mWindowManagerService.mInputMonitor.notifyAppSwitchComing(); + } + + @SuppressWarnings("unused") + public boolean filterTouchEvents() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterTouchEvents); + } + + @SuppressWarnings("unused") + public boolean filterJumpyTouchEvents() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterJumpyTouchEvents); + } + + @SuppressWarnings("unused") + public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) { + ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>(); + + try { + FileInputStream fis = new FileInputStream( + "/sys/board_properties/virtualkeys." + deviceName); + InputStreamReader isr = new InputStreamReader(fis); + BufferedReader br = new BufferedReader(isr, 2048); + String str = br.readLine(); + if (str != null) { + String[] it = str.split(":"); + if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it); + final int N = it.length-6; + for (int i=0; i<=N; i+=6) { + if (!"0x01".equals(it[i])) { + Slog.w(TAG, "Unknown virtual key type at elem #" + i + + ": " + it[i]); + continue; + } + try { + VirtualKeyDefinition key = new VirtualKeyDefinition(); + key.scanCode = Integer.parseInt(it[i+1]); + key.centerX = Integer.parseInt(it[i+2]); + key.centerY = Integer.parseInt(it[i+3]); + key.width = Integer.parseInt(it[i+4]); + key.height = Integer.parseInt(it[i+5]); + if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key " + + key.scanCode + ": center=" + key.centerX + "," + + key.centerY + " size=" + key.width + "x" + + key.height); + keys.add(key); + } catch (NumberFormatException e) { + Slog.w(TAG, "Bad number at region " + i + " in: " + + str, e); + } + } + } + br.close(); + } catch (FileNotFoundException e) { + Slog.i(TAG, "No virtual keys found"); + } catch (IOException e) { + Slog.w(TAG, "Error reading virtual keys", e); + } + + return keys.toArray(new VirtualKeyDefinition[keys.size()]); + } + + @SuppressWarnings("unused") + public String[] getExcludedDeviceNames() { + ArrayList<String> names = new ArrayList<String>(); + + // Read partner-provided list of excluded input devices + XmlPullParser parser = null; + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); + FileReader confreader = null; + try { + confreader = new FileReader(confFile); + parser = Xml.newPullParser(); + parser.setInput(confreader); + XmlUtils.beginDocument(parser, "devices"); + + while (true) { + XmlUtils.nextElement(parser); + if (!"device".equals(parser.getName())) { + break; + } + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + names.add(name); + } + } + } catch (FileNotFoundException e) { + // It's ok if the file does not exist. + } catch (Exception e) { + Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); + } finally { + try { if (confreader != null) confreader.close(); } catch (IOException e) { } + } + + return names.toArray(new String[names.size()]); + } + } +} diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 0e74169..710d817 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -26,8 +26,7 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; -import com.android.server.status.IconData; -import com.android.server.status.StatusBarService; +import com.android.server.StatusBarManagerService; import org.xmlpull.v1.XmlPullParserException; @@ -113,9 +112,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final Context mContext; final Handler mHandler; final SettingsObserver mSettingsObserver; - final StatusBarService mStatusBar; - final IBinder mInputMethodIcon; - final IconData mInputMethodData; + final StatusBarManagerService mStatusBar; final IWindowManager mIWindowManager; final HandlerCaller mCaller; @@ -450,7 +447,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - public InputMethodManagerService(Context context, StatusBarService statusBar) { + public InputMethodManagerService(Context context, StatusBarManagerService statusBar) { mContext = context; mHandler = new Handler(this); mIWindowManager = IWindowManager.Stub.asInterface( @@ -511,9 +508,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mStatusBar = statusBar; - mInputMethodData = IconData.makeIcon("ime", null, 0, 0, 0); - mInputMethodIcon = statusBar.addIcon(mInputMethodData, null); - statusBar.setIconVisibility(mInputMethodIcon, false); + statusBar.setIconVisibility("ime", false); mSettingsObserver = new SettingsObserver(mHandler); updateFromSettingsLocked(); @@ -915,7 +910,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mEnabledSession = null; mCurMethod = null; } - mStatusBar.setIconVisibility(mInputMethodIcon, false); + mStatusBar.setIconVisibility("ime", false); } public void onServiceDisconnected(ComponentName name) { @@ -949,13 +944,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { if (iconId == 0) { if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); - mStatusBar.setIconVisibility(mInputMethodIcon, false); + mStatusBar.setIconVisibility("ime", false); } else if (packageName != null) { if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); - mInputMethodData.iconId = iconId; - mInputMethodData.iconPackage = packageName; - mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null); - mStatusBar.setIconVisibility(mInputMethodIcon, true); + mStatusBar.setIcon("ime", packageName, iconId, 0); + mStatusBar.setIconVisibility("ime", true); } } } finally { @@ -1738,8 +1731,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" sessionRequested=" + ci.sessionRequested); p.println(" curSession=" + ci.curSession); } - p.println(" mInputMethodIcon=" + mInputMethodIcon); - p.println(" mInputMethodData=" + mInputMethodData); p.println(" mCurMethodId=" + mCurMethodId); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java new file mode 100644 index 0000000..8da0cf1 --- /dev/null +++ b/services/java/com/android/server/InputWindow.java @@ -0,0 +1,66 @@ +/* + * 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; + +import android.view.InputChannel; + +/** + * Describes input-related window properties for use by the input dispatcher. + * + * @hide + */ +public final class InputWindow { + // The input channel associated with the window. + public InputChannel inputChannel; + + // Window layout params attributes. (WindowManager.LayoutParams) + public int layoutParamsFlags; + public int layoutParamsType; + + // Dispatching timeout. + public long dispatchingTimeoutNanos; + + // Window frame position. + public int frameLeft; + public int frameTop; + + // Window touchable area. + public int touchableAreaLeft; + public int touchableAreaTop; + public int touchableAreaRight; + public int touchableAreaBottom; + + // Window is visible. + public boolean visible; + + // Window has focus. + public boolean hasFocus; + + // Window has wallpaper. (window is the current wallpaper target) + public boolean hasWallpaper; + + // Input event dispatching is paused. + public boolean paused; + + // Id of process and user that owns the window. + public int ownerPid; + public int ownerUid; + + public void recycle() { + inputChannel = null; + } +} diff --git a/services/java/com/android/server/InputWindowList.java b/services/java/com/android/server/InputWindowList.java new file mode 100644 index 0000000..1cbb2cc --- /dev/null +++ b/services/java/com/android/server/InputWindowList.java @@ -0,0 +1,89 @@ +/* + * 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; + + +/** + * A specialized list of window information objects backed by an array. + * + * This class is part of an InputManager optimization to avoid allocating objects and arrays + * unnecessarily. Internally, it keeps an array full of demand-allocated objects that it + * recycles each time the list is cleared. The used portion of the array is padded with a null. + * + * The contents of the list are intended to be Z-ordered from top to bottom. + * + * @hide + */ +public final class InputWindowList { + private InputWindow[] mArray; + private int mCount; + + /** + * Creates an empty list. + */ + public InputWindowList() { + mArray = new InputWindow[8]; + } + + /** + * Clears the list. + */ + public void clear() { + if (mCount == 0) { + return; + } + + int count = mCount; + mCount = 0; + mArray[count] = mArray[0]; + while (count > 0) { + count -= 1; + mArray[count].recycle(); + } + mArray[0] = null; + } + + /** + * Adds an uninitialized input window object to the list and returns it. + */ + public InputWindow add() { + if (mCount + 1 == mArray.length) { + InputWindow[] oldArray = mArray; + mArray = new InputWindow[oldArray.length * 2]; + System.arraycopy(oldArray, 0, mArray, 0, mCount); + } + + // Grab object from tail (after used section) if available. + InputWindow item = mArray[mCount + 1]; + if (item == null) { + item = new InputWindow(); + } + + mArray[mCount] = item; + mCount += 1; + mArray[mCount] = null; + return item; + } + + /** + * Gets the input window objects as a null-terminated array. + * @return The input window array. + */ + public InputWindow[] toNullTerminatedArray() { + return mArray; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/Installer.java index 2eaa58c..1f34eba 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/Installer.java @@ -166,11 +166,17 @@ class Installer { } } - public int install(String name, int uid, int gid) { + public int install(String name, boolean useEncryptedFilesystem, int uid, int gid) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); builder.append(name); builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } + builder.append(' '); builder.append(uid); builder.append(' '); builder.append(gid); @@ -203,33 +209,57 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } - public int rename(String oldname, String newname) { + public int rename(String oldname, String newname, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("rename"); builder.append(' '); builder.append(oldname); builder.append(' '); builder.append(newname); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } - public int deleteCacheFiles(String name) { + public int deleteCacheFiles(String name, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("rmcache"); builder.append(' '); builder.append(name); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } - public int clearUserData(String name) { + public int clearUserData(String name, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } return execute(builder.toString()); } @@ -263,7 +293,7 @@ class Installer { } public int getSizeInfo(String pkgName, String apkPath, - String fwdLockApkPath, PackageStats pStats) { + String fwdLockApkPath, PackageStats pStats, boolean useEncryptedFilesystem) { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); @@ -271,6 +301,13 @@ class Installer { builder.append(apkPath); builder.append(' '); builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!"); + builder.append(' '); + if (useEncryptedFilesystem) { + builder.append('1'); + } else { + builder.append('0'); + } + String s = transaction(builder.toString()); String res[] = s.split(" "); diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java deleted file mode 100644 index 6d42141..0000000 --- a/services/java/com/android/server/KeyInputQueue.java +++ /dev/null @@ -1,1386 +0,0 @@ -/* - * Copyright (C) 2007 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.content.res.Configuration; -import android.content.res.Resources; -import android.os.Environment; -import android.os.LatencyTimer; -import android.os.PowerManager; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.util.Slog; -import android.util.SparseArray; -import android.util.Xml; -import android.view.Display; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.RawInputEvent; -import android.view.Surface; -import android.view.WindowManagerPolicy; - -import com.android.internal.util.XmlUtils; - -import org.xmlpull.v1.XmlPullParser; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.util.ArrayList; - -public abstract class KeyInputQueue { - static final String TAG = "KeyInputQueue"; - - static final boolean DEBUG = false; - static final boolean DEBUG_VIRTUAL_KEYS = false; - static final boolean DEBUG_POINTERS = false; - - /** - * Turn on some hacks we have to improve the touch interaction with a - * certain device whose screen currently is not all that good. - */ - static boolean BAD_TOUCH_HACK = false; - - /** - * Turn on some hacks to improve touch interaction with another device - * where touch coordinate data can get corrupted. - */ - static boolean JUMPY_TOUCH_HACK = false; - - private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; - - final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>(); - final SparseArray<InputDevice> mIgnoredDevices = new SparseArray<InputDevice>(); - final ArrayList<VirtualKey> mVirtualKeys = new ArrayList<VirtualKey>(); - final HapticFeedbackCallback mHapticFeedbackCallback; - - int mGlobalMetaState = 0; - boolean mHaveGlobalMetaState = false; - - final QueuedEvent mFirst; - final QueuedEvent mLast; - QueuedEvent mCache; - int mCacheCount; - - Display mDisplay = null; - int mDisplayWidth; - int mDisplayHeight; - - int mOrientation = Surface.ROTATION_0; - int[] mKeyRotationMap = null; - - VirtualKey mPressedVirtualKey = null; - - PowerManager.WakeLock mWakeLock; - - static final int[] KEY_90_MAP = new int[] { - KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN, - }; - - static final int[] KEY_180_MAP = new int[] { - KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, - }; - - static final int[] KEY_270_MAP = new int[] { - KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_DOWN, - }; - - public static final int FILTER_REMOVE = 0; - public static final int FILTER_KEEP = 1; - public static final int FILTER_ABORT = -1; - - private static final boolean MEASURE_LATENCY = false; - private LatencyTimer lt; - - public interface FilterCallback { - int filterEvent(QueuedEvent ev); - } - - public interface HapticFeedbackCallback { - void virtualKeyFeedback(KeyEvent event); - } - - static class QueuedEvent { - InputDevice inputDevice; - long whenNano; - int flags; // From the raw event - int classType; // One of the class constants in InputEvent - Object event; - boolean inQueue; - - void copyFrom(QueuedEvent that) { - this.inputDevice = that.inputDevice; - this.whenNano = that.whenNano; - this.flags = that.flags; - this.classType = that.classType; - this.event = that.event; - } - - @Override - public String toString() { - return "QueuedEvent{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + event + "}"; - } - - // not copied - QueuedEvent prev; - QueuedEvent next; - } - - /** - * A key that exists as a part of the touch-screen, outside of the normal - * display area of the screen. - */ - static class VirtualKey { - int scancode; - int centerx; - int centery; - int width; - int height; - - int hitLeft; - int hitTop; - int hitRight; - int hitBottom; - - InputDevice lastDevice; - int lastKeycode; - - boolean checkHit(int x, int y) { - return (x >= hitLeft && x <= hitRight - && y >= hitTop && y <= hitBottom); - } - - void computeHitRect(InputDevice dev, int dw, int dh) { - if (dev == lastDevice) { - return; - } - - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "computeHitRect for " + scancode - + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY); - - lastDevice = dev; - - int minx = dev.absX.minValue; - int maxx = dev.absX.maxValue; - - int halfw = width/2; - int left = centerx - halfw; - int right = centerx + halfw; - hitLeft = minx + ((left*maxx-minx)/dw); - hitRight = minx + ((right*maxx-minx)/dw); - - int miny = dev.absY.minValue; - int maxy = dev.absY.maxValue; - - int halfh = height/2; - int top = centery - halfh; - int bottom = centery + halfh; - hitTop = miny + ((top*maxy-miny)/dh); - hitBottom = miny + ((bottom*maxy-miny)/dh); - } - } - - private void readVirtualKeys(String deviceName) { - try { - FileInputStream fis = new FileInputStream( - "/sys/board_properties/virtualkeys." + deviceName); - InputStreamReader isr = new InputStreamReader(fis); - BufferedReader br = new BufferedReader(isr, 2048); - String str = br.readLine(); - if (str != null) { - String[] it = str.split(":"); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it); - final int N = it.length-6; - for (int i=0; i<=N; i+=6) { - if (!"0x01".equals(it[i])) { - Slog.w(TAG, "Unknown virtual key type at elem #" + i - + ": " + it[i]); - continue; - } - try { - VirtualKey sb = new VirtualKey(); - sb.scancode = Integer.parseInt(it[i+1]); - sb.centerx = Integer.parseInt(it[i+2]); - sb.centery = Integer.parseInt(it[i+3]); - sb.width = Integer.parseInt(it[i+4]); - sb.height = Integer.parseInt(it[i+5]); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key " - + sb.scancode + ": center=" + sb.centerx + "," - + sb.centery + " size=" + sb.width + "x" - + sb.height); - mVirtualKeys.add(sb); - } catch (NumberFormatException e) { - Slog.w(TAG, "Bad number at region " + i + " in: " - + str, e); - } - } - } - br.close(); - } catch (FileNotFoundException e) { - Slog.i(TAG, "No virtual keys found"); - } catch (IOException e) { - Slog.w(TAG, "Error reading virtual keys", e); - } - } - - private void readExcludedDevices() { - // Read partner-provided list of excluded input devices - XmlPullParser parser = null; - // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". - File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); - FileReader confreader = null; - try { - confreader = new FileReader(confFile); - parser = Xml.newPullParser(); - parser.setInput(confreader); - XmlUtils.beginDocument(parser, "devices"); - - while (true) { - XmlUtils.nextElement(parser); - if (!"device".equals(parser.getName())) { - break; - } - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - if (DEBUG) Slog.v(TAG, "addExcludedDevice " + name); - addExcludedDevice(name); - } - } - } catch (FileNotFoundException e) { - // It's ok if the file does not exist. - } catch (Exception e) { - Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); - } finally { - try { if (confreader != null) confreader.close(); } catch (IOException e) { } - } - } - - KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) { - if (MEASURE_LATENCY) { - lt = new LatencyTimer(100, 1000); - } - - Resources r = context.getResources(); - BAD_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterTouchEvents); - - JUMPY_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterJumpyTouchEvents); - - mHapticFeedbackCallback = hapticFeedbackCallback; - - readExcludedDevices(); - - PowerManager pm = (PowerManager)context.getSystemService( - Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "KeyInputQueue"); - mWakeLock.setReferenceCounted(false); - - mFirst = new QueuedEvent(); - mLast = new QueuedEvent(); - mFirst.next = mLast; - mLast.prev = mFirst; - } - - void start() { - mThread.start(); - } - - public void setDisplay(Display display) { - mDisplay = display; - - // We assume at this point that the display dimensions reflect the - // natural, unrotated display. We will perform hit tests for soft - // buttons based on that display. - mDisplayWidth = display.getWidth(); - mDisplayHeight = display.getHeight(); - } - - public void getInputConfiguration(Configuration config) { - synchronized (mFirst) { - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - config.keyboard = Configuration.KEYBOARD_NOKEYS; - config.navigation = Configuration.NAVIGATION_NONAV; - - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice d = mDevices.valueAt(i); - if (d != null) { - if ((d.classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - config.touchscreen - = Configuration.TOUCHSCREEN_FINGER; - //Slog.i("foo", "***** HAVE TOUCHSCREEN!"); - } - if ((d.classes&RawInputEvent.CLASS_ALPHAKEY) != 0) { - config.keyboard - = Configuration.KEYBOARD_QWERTY; - //Slog.i("foo", "***** HAVE QWERTY!"); - } - if ((d.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - config.navigation - = Configuration.NAVIGATION_TRACKBALL; - //Slog.i("foo", "***** HAVE TRACKBALL!"); - } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) { - config.navigation - = Configuration.NAVIGATION_DPAD; - //Slog.i("foo", "***** HAVE DPAD!"); - } - } - } - } - } - - public int getScancodeState(int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.scancode == code) { - return 2; - } - } - return nativeGetScancodeState(code); - } - } - - public int getScancodeState(int deviceId, int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.scancode == code) { - return 2; - } - } - return nativeGetScancodeState(deviceId, code); - } - } - - public int getTrackballScancodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - int res = nativeGetScancodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public int getDPadScancodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_DPAD) != 0) { - int res = nativeGetScancodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public int getKeycodeState(int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.lastKeycode == code) { - return 2; - } - } - return nativeGetKeycodeState(code); - } - } - - public int getKeycodeState(int deviceId, int code) { - synchronized (mFirst) { - VirtualKey vk = mPressedVirtualKey; - if (vk != null) { - if (vk.lastKeycode == code) { - return 2; - } - } - return nativeGetKeycodeState(deviceId, code); - } - } - - public int getTrackballKeycodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - int res = nativeGetKeycodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public int getDPadKeycodeState(int code) { - synchronized (mFirst) { - final int N = mDevices.size(); - for (int i=0; i<N; i++) { - InputDevice dev = mDevices.valueAt(i); - if ((dev.classes&RawInputEvent.CLASS_DPAD) != 0) { - int res = nativeGetKeycodeState(dev.id, code); - if (res > 0) { - return res; - } - } - } - } - - return 0; - } - - public static native String getDeviceName(int deviceId); - public static native int getDeviceClasses(int deviceId); - public static native void addExcludedDevice(String deviceName); - public static native boolean getAbsoluteInfo(int deviceId, int axis, - InputDevice.AbsoluteInfo outInfo); - public static native int getSwitchState(int sw); - public static native int getSwitchState(int deviceId, int sw); - public static native int nativeGetScancodeState(int code); - public static native int nativeGetScancodeState(int deviceId, int code); - public static native int nativeGetKeycodeState(int code); - public static native int nativeGetKeycodeState(int deviceId, int code); - public static native int scancodeToKeycode(int deviceId, int scancode); - public static native boolean hasKeys(int[] keycodes, boolean[] keyExists); - - public static KeyEvent newKeyEvent(InputDevice device, long downTime, - long eventTime, boolean down, int keycode, int repeatCount, - int scancode, int flags) { - return new KeyEvent( - downTime, eventTime, - down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP, - keycode, repeatCount, - device != null ? device.mMetaKeysState : 0, - device != null ? device.id : -1, scancode, - flags | KeyEvent.FLAG_FROM_SYSTEM); - } - - Thread mThread = new Thread("InputDeviceReader") { - public void run() { - if (DEBUG) Slog.v(TAG, "InputDeviceReader.run()"); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); - - RawInputEvent ev = new RawInputEvent(); - while (true) { - try { - InputDevice di; - - // block, doesn't release the monitor - readEvent(ev); - - boolean send = false; - boolean configChanged = false; - - if (false) { - Slog.i(TAG, "Input event: dev=0x" - + Integer.toHexString(ev.deviceId) - + " type=0x" + Integer.toHexString(ev.type) - + " scancode=" + ev.scancode - + " keycode=" + ev.keycode - + " value=" + ev.value); - } - - if (ev.type == RawInputEvent.EV_DEVICE_ADDED) { - synchronized (mFirst) { - di = newInputDevice(ev.deviceId); - if (di.classes != 0) { - // If this device is some kind of input class, - // we care about it. - mDevices.put(ev.deviceId, di); - if ((di.classes & RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - readVirtualKeys(di.name); - } - // The configuration may have changed because - // of this device. - configChanged = true; - } else { - // We won't do anything with this device. - mIgnoredDevices.put(ev.deviceId, di); - Slog.i(TAG, "Ignoring non-input device: id=0x" - + Integer.toHexString(di.id) - + ", name=" + di.name); - } - } - } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) { - synchronized (mFirst) { - if (false) { - Slog.i(TAG, "Device removed: id=0x" - + Integer.toHexString(ev.deviceId)); - } - di = mDevices.get(ev.deviceId); - if (di != null) { - mDevices.delete(ev.deviceId); - // The configuration may have changed because - // of this device. - configChanged = true; - } else if ((di=mIgnoredDevices.get(ev.deviceId)) != null) { - mIgnoredDevices.remove(ev.deviceId); - } else { - Slog.w(TAG, "Removing bad device id: " - + Integer.toHexString(ev.deviceId)); - continue; - } - } - } else { - di = getInputDevice(ev.deviceId); - if (di == null) { - // This may be some junk from an ignored device. - continue; - } - - // first crack at it - send = preprocessEvent(di, ev); - - if (ev.type == RawInputEvent.EV_KEY) { - di.mMetaKeysState = makeMetaState(ev.keycode, - ev.value != 0, di.mMetaKeysState); - mHaveGlobalMetaState = false; - } - } - - if (configChanged) { - synchronized (mFirst) { - addLocked(di, System.nanoTime(), 0, - RawInputEvent.CLASS_CONFIGURATION_CHANGED, - null); - } - } - - if (!send) { - continue; - } - - synchronized (mFirst) { - // NOTE: The event timebase absolutely must be the same - // timebase as SystemClock.uptimeMillis(). - //curTime = gotOne ? ev.when : SystemClock.uptimeMillis(); - final long curTime = SystemClock.uptimeMillis(); - final long curTimeNano = System.nanoTime(); - //Slog.i(TAG, "curTime=" + curTime + ", systemClock=" + SystemClock.uptimeMillis()); - - final int classes = di.classes; - final int type = ev.type; - final int scancode = ev.scancode; - send = false; - - // Is it a key event? - if (type == RawInputEvent.EV_KEY && - (classes&RawInputEvent.CLASS_KEYBOARD) != 0 && - (scancode < RawInputEvent.BTN_FIRST || - scancode > RawInputEvent.BTN_LAST)) { - boolean down; - if (ev.value != 0) { - down = true; - di.mKeyDownTime = curTime; - } else { - down = false; - } - int keycode = rotateKeyCodeLocked(ev.keycode); - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_KEYBOARD, - newKeyEvent(di, di.mKeyDownTime, curTime, down, - keycode, 0, scancode, - ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0) - ? KeyEvent.FLAG_WOKE_HERE : 0)); - - } else if (ev.type == RawInputEvent.EV_KEY) { - // Single touch protocol: touch going down or up. - if (ev.scancode == RawInputEvent.BTN_TOUCH && - (classes&(RawInputEvent.CLASS_TOUCHSCREEN - |RawInputEvent.CLASS_TOUCHSCREEN_MT)) - == RawInputEvent.CLASS_TOUCHSCREEN) { - di.mAbs.changed = true; - di.mAbs.mDown[0] = ev.value != 0; - - // Trackball (mouse) protocol: press down or up. - } else if (ev.scancode == RawInputEvent.BTN_MOUSE && - (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - di.mRel.changed = true; - di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0; - send = true; - } - - // Process position events from multitouch protocol. - } else if (ev.type == RawInputEvent.EV_ABS && - (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { - if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_PRESSURE] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_X] = ev.value; - if (DEBUG_POINTERS) Slog.v(TAG, "MT @" - + di.mAbs.mAddingPointerOffset - + " X:" + ev.value); - } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_Y] = ev.value; - if (DEBUG_POINTERS) Slog.v(TAG, "MT @" - + di.mAbs.mAddingPointerOffset - + " Y:" + ev.value); - } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) { - di.mAbs.changed = true; - di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_SIZE] = ev.value; - } - - // Process position events from single touch protocol. - } else if (ev.type == RawInputEvent.EV_ABS && - (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - if (ev.scancode == RawInputEvent.ABS_X) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_Y) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_Y] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_PRESSURE] = ev.value; - di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA - + MotionEvent.SAMPLE_PRESSURE] = ev.value; - } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) { - di.mAbs.changed = true; - di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value; - di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA - + MotionEvent.SAMPLE_SIZE] = ev.value; - } - - // Process movement events from trackball (mouse) protocol. - } else if (ev.type == RawInputEvent.EV_REL && - (classes&RawInputEvent.CLASS_TRACKBALL) != 0) { - // Add this relative movement into our totals. - if (ev.scancode == RawInputEvent.REL_X) { - di.mRel.changed = true; - di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value; - } else if (ev.scancode == RawInputEvent.REL_Y) { - di.mRel.changed = true; - di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value; - } - } - - // Handle multitouch protocol sync: tells us that the - // driver has returned all data for -one- of the pointers - // that is currently down. - if (ev.type == RawInputEvent.EV_SYN - && ev.scancode == RawInputEvent.SYN_MT_REPORT - && di.mAbs != null) { - di.mAbs.changed = true; - if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) { - // If the value is <= 0, the pointer is not - // down, so keep it in the count. - - if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset - + MotionEvent.SAMPLE_PRESSURE] != 0) { - final int num = di.mAbs.mNextNumPointers+1; - di.mAbs.mNextNumPointers = num; - if (DEBUG_POINTERS) Slog.v(TAG, - "MT_REPORT: now have " + num + " pointers"); - final int newOffset = (num <= InputDevice.MAX_POINTERS) - ? (num * MotionEvent.NUM_SAMPLE_DATA) - : (InputDevice.MAX_POINTERS * - MotionEvent.NUM_SAMPLE_DATA); - di.mAbs.mAddingPointerOffset = newOffset; - di.mAbs.mNextData[newOffset - + MotionEvent.SAMPLE_PRESSURE] = 0; - } else { - if (DEBUG_POINTERS) Slog.v(TAG, "MT_REPORT: no pointer"); - } - } - - // Handle general event sync: all data for the current - // event update has been delivered. - } else if (send || (ev.type == RawInputEvent.EV_SYN - && ev.scancode == RawInputEvent.SYN_REPORT)) { - if (mDisplay != null) { - if (!mHaveGlobalMetaState) { - computeGlobalMetaStateLocked(); - } - - MotionEvent me; - - InputDevice.MotionState ms = di.mAbs; - if (ms.changed) { - ms.everChanged = true; - ms.changed = false; - - if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN - |RawInputEvent.CLASS_TOUCHSCREEN_MT)) - == RawInputEvent.CLASS_TOUCHSCREEN) { - ms.mNextNumPointers = 0; - if (ms.mDown[0]) { - System.arraycopy(di.curTouchVals, 0, - ms.mNextData, 0, - MotionEvent.NUM_SAMPLE_DATA); - ms.mNextNumPointers++; - } - } - - if (BAD_TOUCH_HACK) { - ms.dropBadPoint(di); - } - if (JUMPY_TOUCH_HACK) { - ms.dropJumpyPoint(di); - } - - boolean doMotion = !monitorVirtualKey(di, - ev, curTime, curTimeNano); - - if (doMotion && ms.mNextNumPointers > 0 - && (ms.mLastNumPointers == 0 - || ms.mSkipLastPointers)) { - doMotion = !generateVirtualKeyDown(di, - ev, curTime, curTimeNano); - } - - if (doMotion) { - // XXX Need to be able to generate - // multiple events here, for example - // if two fingers change up/down state - // at the same time. - do { - me = ms.generateAbsMotion(di, curTime, - curTimeNano, mDisplay, - mOrientation, mGlobalMetaState); - if (DEBUG_POINTERS) Slog.v(TAG, "Absolute: x=" - + di.mAbs.mNextData[MotionEvent.SAMPLE_X] - + " y=" - + di.mAbs.mNextData[MotionEvent.SAMPLE_Y] - + " ev=" + me); - if (me != null) { - if (WindowManagerPolicy.WATCH_POINTER) { - Slog.i(TAG, "Enqueueing: " + me); - } - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_TOUCHSCREEN, me); - } - } while (ms.hasMore()); - } else { - // We are consuming movement in the - // virtual key area... but still - // propagate this to the previous - // data for comparisons. - int num = ms.mNextNumPointers; - if (num > InputDevice.MAX_POINTERS) { - num = InputDevice.MAX_POINTERS; - } - System.arraycopy(ms.mNextData, 0, - ms.mLastData, 0, - num * MotionEvent.NUM_SAMPLE_DATA); - ms.mLastNumPointers = num; - ms.mSkipLastPointers = true; - } - - ms.finish(); - } - - ms = di.mRel; - if (ms.changed) { - ms.everChanged = true; - ms.changed = false; - - me = ms.generateRelMotion(di, curTime, - curTimeNano, - mOrientation, mGlobalMetaState); - if (false) Slog.v(TAG, "Relative: x=" - + di.mRel.mNextData[MotionEvent.SAMPLE_X] - + " y=" - + di.mRel.mNextData[MotionEvent.SAMPLE_Y] - + " ev=" + me); - if (me != null) { - addLocked(di, curTimeNano, ev.flags, - RawInputEvent.CLASS_TRACKBALL, me); - } - } - } - } - } - - } catch (RuntimeException exc) { - Slog.e(TAG, "InputReaderThread uncaught exception", exc); - } - } - } - }; - - private boolean isInsideDisplay(InputDevice dev) { - final InputDevice.AbsoluteInfo absx = dev.absX; - final InputDevice.AbsoluteInfo absy = dev.absY; - final InputDevice.MotionState absm = dev.mAbs; - if (absx == null || absy == null || absm == null) { - return true; - } - - if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue - && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue - && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue - && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) { - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Input (" - + absm.mNextData[MotionEvent.SAMPLE_X] - + "," + absm.mNextData[MotionEvent.SAMPLE_Y] - + ") inside of display"); - return true; - } - - return false; - } - - private VirtualKey findVirtualKey(InputDevice dev) { - final int N = mVirtualKeys.size(); - if (N <= 0) { - return null; - } - - final InputDevice.MotionState absm = dev.mAbs; - for (int i=0; i<N; i++) { - VirtualKey sb = mVirtualKeys.get(i); - sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight); - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Hit test (" - + absm.mNextData[MotionEvent.SAMPLE_X] + "," - + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code " - + sb.scancode + " - (" + sb.hitLeft - + "," + sb.hitTop + ")-(" + sb.hitRight + "," - + sb.hitBottom + ")"); - if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X], - absm.mNextData[MotionEvent.SAMPLE_Y])) { - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Hit!"); - return sb; - } - } - - return null; - } - - private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev, - long curTime, long curTimeNano) { - if (isInsideDisplay(di)) { - // Didn't consume event. - return false; - } - - - VirtualKey vk = findVirtualKey(di); - if (vk != null) { - final InputDevice.MotionState ms = di.mAbs; - mPressedVirtualKey = vk; - vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode); - ms.mLastNumPointers = ms.mNextNumPointers; - di.mKeyDownTime = curTime; - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, - "Generate key down for: " + vk.scancode - + " (keycode=" + vk.lastKeycode + ")"); - KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true, - vk.lastKeycode, 0, vk.scancode, - KeyEvent.FLAG_VIRTUAL_HARD_KEY); - mHapticFeedbackCallback.virtualKeyFeedback(event); - addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - event); - } - - // We always consume the event, even if we didn't - // generate a key event. There are two reasons for - // this: to avoid spurious touches when holding - // the edges of the device near the touchscreen, - // and to avoid reporting events if there are virtual - // keys on the touchscreen outside of the display - // area. - // Note that for all of this we are only looking at the - // first pointer, since what we are handling here is the - // first pointer going down, and this is the coordinate - // that will be used to dispatch the event. - if (false) { - final InputDevice.AbsoluteInfo absx = di.absX; - final InputDevice.AbsoluteInfo absy = di.absY; - final InputDevice.MotionState absm = di.mAbs; - Slog.v(TAG, "Rejecting (" - + absm.mNextData[MotionEvent.SAMPLE_X] + "," - + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of (" - + absx.minValue + "," + absy.minValue - + ")-(" + absx.maxValue + "," - + absx.maxValue + ")"); - } - return true; - } - - private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev, - long curTime, long curTimeNano) { - VirtualKey vk = mPressedVirtualKey; - if (vk == null) { - return false; - } - - final InputDevice.MotionState ms = di.mAbs; - if (ms.mNextNumPointers <= 0) { - mPressedVirtualKey = null; - ms.mLastNumPointers = 0; - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Generate key up for: " + vk.scancode); - KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, - vk.lastKeycode, 0, vk.scancode, - KeyEvent.FLAG_VIRTUAL_HARD_KEY); - mHapticFeedbackCallback.virtualKeyFeedback(event); - addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - event); - return true; - - } else if (isInsideDisplay(di)) { - // Whoops the pointer has moved into - // the display area! Cancel the - // virtual key and start a pointer - // motion. - mPressedVirtualKey = null; - if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Cancel key up for: " + vk.scancode); - KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false, - vk.lastKeycode, 0, vk.scancode, - KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY); - mHapticFeedbackCallback.virtualKeyFeedback(event); - addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD, - event); - ms.mLastNumPointers = 0; - return false; - } - - return true; - } - - /** - * Returns a new meta state for the given keys and old state. - */ - private static final int makeMetaState(int keycode, boolean down, int old) { - int mask; - switch (keycode) { - case KeyEvent.KEYCODE_ALT_LEFT: - mask = KeyEvent.META_ALT_LEFT_ON; - break; - case KeyEvent.KEYCODE_ALT_RIGHT: - mask = KeyEvent.META_ALT_RIGHT_ON; - break; - case KeyEvent.KEYCODE_SHIFT_LEFT: - mask = KeyEvent.META_SHIFT_LEFT_ON; - break; - case KeyEvent.KEYCODE_SHIFT_RIGHT: - mask = KeyEvent.META_SHIFT_RIGHT_ON; - break; - case KeyEvent.KEYCODE_SYM: - mask = KeyEvent.META_SYM_ON; - break; - default: - return old; - } - int result = ~(KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON) - & (down ? (old | mask) : (old & ~mask)); - if (0 != (result & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON))) { - result |= KeyEvent.META_ALT_ON; - } - if (0 != (result & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON))) { - result |= KeyEvent.META_SHIFT_ON; - } - return result; - } - - private void computeGlobalMetaStateLocked() { - int i = mDevices.size(); - mGlobalMetaState = 0; - while ((--i) >= 0) { - mGlobalMetaState |= mDevices.valueAt(i).mMetaKeysState; - } - mHaveGlobalMetaState = true; - } - - /* - * Return true if you want the event to get passed on to the - * rest of the system, and false if you've handled it and want - * it dropped. - */ - abstract boolean preprocessEvent(InputDevice device, RawInputEvent event); - - InputDevice getInputDevice(int deviceId) { - synchronized (mFirst) { - return getInputDeviceLocked(deviceId); - } - } - - private InputDevice getInputDeviceLocked(int deviceId) { - return mDevices.get(deviceId); - } - - public void setOrientation(int orientation) { - synchronized(mFirst) { - mOrientation = orientation; - switch (orientation) { - case Surface.ROTATION_90: - mKeyRotationMap = KEY_90_MAP; - break; - case Surface.ROTATION_180: - mKeyRotationMap = KEY_180_MAP; - break; - case Surface.ROTATION_270: - mKeyRotationMap = KEY_270_MAP; - break; - default: - mKeyRotationMap = null; - break; - } - } - } - - public int rotateKeyCode(int keyCode) { - synchronized(mFirst) { - return rotateKeyCodeLocked(keyCode); - } - } - - private int rotateKeyCodeLocked(int keyCode) { - int[] map = mKeyRotationMap; - if (map != null) { - final int N = map.length; - for (int i=0; i<N; i+=2) { - if (map[i] == keyCode) { - return map[i+1]; - } - } - } - return keyCode; - } - - boolean hasEvents() { - synchronized (mFirst) { - return mFirst.next != mLast; - } - } - - /* - * returns true if we returned an event, and false if we timed out - */ - QueuedEvent getEvent(long timeoutMS) { - long begin = SystemClock.uptimeMillis(); - final long end = begin+timeoutMS; - long now = begin; - synchronized (mFirst) { - while (mFirst.next == mLast && end > now) { - try { - mWakeLock.release(); - mFirst.wait(end-now); - } - catch (InterruptedException e) { - } - now = SystemClock.uptimeMillis(); - if (begin > now) { - begin = now; - } - } - if (mFirst.next == mLast) { - return null; - } - QueuedEvent p = mFirst.next; - mFirst.next = p.next; - mFirst.next.prev = mFirst; - p.inQueue = false; - return p; - } - } - - /** - * Return true if the queue has an up event pending that corresponds - * to the same key as the given key event. - */ - boolean hasKeyUpEvent(KeyEvent origEvent) { - synchronized (mFirst) { - final int keyCode = origEvent.getKeyCode(); - QueuedEvent cur = mLast.prev; - while (cur.prev != null) { - if (cur.classType == RawInputEvent.CLASS_KEYBOARD) { - KeyEvent ke = (KeyEvent)cur.event; - if (ke.getAction() == KeyEvent.ACTION_UP - && ke.getKeyCode() == keyCode) { - return true; - } - } - cur = cur.prev; - } - } - - return false; - } - - void recycleEvent(QueuedEvent ev) { - synchronized (mFirst) { - //Slog.i(TAG, "Recycle event: " + ev); - if (ev.event == ev.inputDevice.mAbs.currentMove) { - ev.inputDevice.mAbs.currentMove = null; - } - if (ev.event == ev.inputDevice.mRel.currentMove) { - if (false) Slog.i(TAG, "Detach rel " + ev.event); - ev.inputDevice.mRel.currentMove = null; - ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0; - ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0; - } - recycleLocked(ev); - } - } - - void filterQueue(FilterCallback cb) { - synchronized (mFirst) { - QueuedEvent cur = mLast.prev; - while (cur.prev != null) { - switch (cb.filterEvent(cur)) { - case FILTER_REMOVE: - cur.prev.next = cur.next; - cur.next.prev = cur.prev; - break; - case FILTER_ABORT: - return; - } - cur = cur.prev; - } - } - } - - private QueuedEvent obtainLocked(InputDevice device, long whenNano, - int flags, int classType, Object event) { - QueuedEvent ev; - if (mCacheCount == 0) { - ev = new QueuedEvent(); - } else { - ev = mCache; - ev.inQueue = false; - mCache = ev.next; - mCacheCount--; - } - ev.inputDevice = device; - ev.whenNano = whenNano; - ev.flags = flags; - ev.classType = classType; - ev.event = event; - return ev; - } - - private void recycleLocked(QueuedEvent ev) { - if (ev.inQueue) { - throw new RuntimeException("Event already in queue!"); - } - if (mCacheCount < 10) { - mCacheCount++; - ev.next = mCache; - mCache = ev; - ev.inQueue = true; - } - } - - private void addLocked(InputDevice device, long whenNano, int flags, - int classType, Object event) { - boolean poke = mFirst.next == mLast; - - QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event); - QueuedEvent p = mLast.prev; - while (p != mFirst && ev.whenNano < p.whenNano) { - p = p.prev; - } - - ev.next = p.next; - ev.prev = p; - p.next = ev; - ev.next.prev = ev; - ev.inQueue = true; - - if (poke) { - long time; - if (MEASURE_LATENCY) { - time = System.nanoTime(); - } - mFirst.notify(); - mWakeLock.acquire(); - if (MEASURE_LATENCY) { - lt.sample("1 addLocked-queued event ", System.nanoTime() - time); - } - } - } - - private InputDevice newInputDevice(int deviceId) { - int classes = getDeviceClasses(deviceId); - String name = getDeviceName(deviceId); - InputDevice.AbsoluteInfo absX = null; - InputDevice.AbsoluteInfo absY = null; - InputDevice.AbsoluteInfo absPressure = null; - InputDevice.AbsoluteInfo absSize = null; - if (classes != 0) { - Slog.i(TAG, "Device added: id=0x" + Integer.toHexString(deviceId) - + ", name=" + name - + ", classes=" + Integer.toHexString(classes)); - if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) { - absX = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_POSITION_X, "X"); - absY = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_POSITION_Y, "Y"); - absPressure = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure"); - absSize = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size"); - } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) { - absX = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_X, "X"); - absY = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_Y, "Y"); - absPressure = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_PRESSURE, "Pressure"); - absSize = loadAbsoluteInfo(deviceId, - RawInputEvent.ABS_TOOL_WIDTH, "Size"); - } - } - - return new InputDevice(deviceId, classes, name, absX, absY, absPressure, absSize); - } - - private InputDevice.AbsoluteInfo loadAbsoluteInfo(int id, int channel, - String name) { - InputDevice.AbsoluteInfo info = new InputDevice.AbsoluteInfo(); - if (getAbsoluteInfo(id, channel, info) - && info.minValue != info.maxValue) { - Slog.i(TAG, " " + name + ": min=" + info.minValue - + " max=" + info.maxValue - + " flat=" + info.flat - + " fuzz=" + info.fuzz); - info.range = info.maxValue-info.minValue; - return info; - } - Slog.i(TAG, " " + name + ": unknown values"); - return null; - } - private static native boolean readEvent(RawInputEvent outEvent); - - void dump(PrintWriter pw, String prefix) { - synchronized (mFirst) { - for (int i=0; i<mDevices.size(); i++) { - InputDevice dev = mDevices.valueAt(i); - pw.print(prefix); pw.print("Device #"); - pw.print(mDevices.keyAt(i)); pw.print(" "); - pw.print(dev.name); pw.print(" (classes=0x"); - pw.print(Integer.toHexString(dev.classes)); - pw.println("):"); - pw.print(prefix); pw.print(" mKeyDownTime="); - pw.print(dev.mKeyDownTime); pw.print(" mMetaKeysState="); - pw.println(dev.mMetaKeysState); - if (dev.absX != null) { - pw.print(prefix); pw.print(" absX: "); dev.absX.dump(pw); - pw.println(""); - } - if (dev.absY != null) { - pw.print(prefix); pw.print(" absY: "); dev.absY.dump(pw); - pw.println(""); - } - if (dev.absPressure != null) { - pw.print(prefix); pw.print(" absPressure: "); - dev.absPressure.dump(pw); pw.println(""); - } - if (dev.absSize != null) { - pw.print(prefix); pw.print(" absSize: "); - dev.absSize.dump(pw); pw.println(""); - } - if (dev.mAbs.everChanged) { - pw.print(prefix); pw.println(" mAbs:"); - dev.mAbs.dump(pw, prefix + " "); - } - if (dev.mRel.everChanged) { - pw.print(prefix); pw.println(" mRel:"); - dev.mRel.dump(pw, prefix + " "); - } - } - pw.println(" "); - for (int i=0; i<mIgnoredDevices.size(); i++) { - InputDevice dev = mIgnoredDevices.valueAt(i); - pw.print(prefix); pw.print("Ignored Device #"); - pw.print(mIgnoredDevices.keyAt(i)); pw.print(" "); - pw.print(dev.name); pw.print(" (classes=0x"); - pw.print(Integer.toHexString(dev.classes)); - pw.println(")"); - } - pw.println(" "); - for (int i=0; i<mVirtualKeys.size(); i++) { - VirtualKey vk = mVirtualKeys.get(i); - pw.print(prefix); pw.print("Virtual Key #"); - pw.print(i); pw.println(":"); - pw.print(prefix); pw.print(" scancode="); pw.println(vk.scancode); - pw.print(prefix); pw.print(" centerx="); pw.print(vk.centerx); - pw.print(" centery="); pw.print(vk.centery); - pw.print(" width="); pw.print(vk.width); - pw.print(" height="); pw.println(vk.height); - pw.print(prefix); pw.print(" hitLeft="); pw.print(vk.hitLeft); - pw.print(" hitTop="); pw.print(vk.hitTop); - pw.print(" hitRight="); pw.print(vk.hitRight); - pw.print(" hitBottom="); pw.println(vk.hitBottom); - if (vk.lastDevice != null) { - pw.print(prefix); pw.print(" lastDevice=#"); - pw.println(vk.lastDevice.id); - } - if (vk.lastKeycode != 0) { - pw.print(prefix); pw.print(" lastKeycode="); - pw.println(vk.lastKeycode); - } - } - pw.println(" "); - pw.print(prefix); pw.print(" Default keyboard: "); - pw.println(SystemProperties.get("hw.keyboards.0.devname")); - pw.print(prefix); pw.print(" mGlobalMetaState="); - pw.print(mGlobalMetaState); pw.print(" mHaveGlobalMetaState="); - pw.println(mHaveGlobalMetaState); - pw.print(prefix); pw.print(" mDisplayWidth="); - pw.print(mDisplayWidth); pw.print(" mDisplayHeight="); - pw.println(mDisplayHeight); - pw.print(prefix); pw.print(" mOrientation="); - pw.println(mOrientation); - if (mPressedVirtualKey != null) { - pw.print(prefix); pw.print(" mPressedVirtualKey.scancode="); - pw.println(mPressedVirtualKey.scancode); - } - } - } -} diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 65f4194..3bcf427 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,17 +16,6 @@ package com.android.server; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Observable; -import java.util.Observer; -import java.util.Set; - import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -41,6 +30,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.location.Address; +import android.location.Criteria; import android.location.GeocoderParams; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; @@ -50,7 +40,6 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; -import android.location.LocationProviderInterface; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -68,12 +57,27 @@ import android.util.Log; import android.util.Slog; import android.util.PrintWriterPrinter; -import com.android.internal.location.GeocoderProxy; -import com.android.internal.location.GpsLocationProvider; import com.android.internal.location.GpsNetInitiatedHandler; -import com.android.internal.location.LocationProviderProxy; -import com.android.internal.location.MockProvider; -import com.android.internal.location.PassiveProvider; + +import com.android.server.location.GeocoderProxy; +import com.android.server.location.GpsLocationProvider; +import com.android.server.location.LocationProviderInterface; +import com.android.server.location.LocationProviderProxy; +import com.android.server.location.MockProvider; +import com.android.server.location.PassiveProvider; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; /** * The service class that manages LocationProviders and issues location @@ -609,10 +613,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return out; } - public List<String> getProviders(boolean enabledOnly) { + public List<String> getProviders(Criteria criteria, boolean enabledOnly) { try { synchronized (mLock) { - return _getProvidersLocked(enabledOnly); + return _getProvidersLocked(criteria, enabledOnly); } } catch (SecurityException se) { throw se; @@ -622,7 +626,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private List<String> _getProvidersLocked(boolean enabledOnly) { + private List<String> _getProvidersLocked(Criteria criteria, boolean enabledOnly) { if (LOCAL_LOGV) { Slog.v(TAG, "getProviders"); } @@ -634,12 +638,225 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabledOnly && !isAllowedBySettingsLocked(name)) { continue; } + if (criteria != null && !p.meetsCriteria(criteria)) { + continue; + } out.add(name); } } return out; } + /** + * Returns the next looser power requirement, in the sequence: + * + * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT + */ + private int nextPower(int power) { + switch (power) { + case Criteria.POWER_LOW: + return Criteria.POWER_MEDIUM; + case Criteria.POWER_MEDIUM: + return Criteria.POWER_HIGH; + case Criteria.POWER_HIGH: + return Criteria.NO_REQUIREMENT; + case Criteria.NO_REQUIREMENT: + default: + return Criteria.NO_REQUIREMENT; + } + } + + /** + * Returns the next looser accuracy requirement, in the sequence: + * + * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT + */ + private int nextAccuracy(int accuracy) { + if (accuracy == Criteria.ACCURACY_FINE) { + return Criteria.ACCURACY_COARSE; + } else { + return Criteria.NO_REQUIREMENT; + } + } + + private class LpPowerComparator implements Comparator<LocationProviderInterface> { + public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { + // Smaller is better + return (l1.getPowerRequirement() - l2.getPowerRequirement()); + } + + public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { + return (l1.getPowerRequirement() == l2.getPowerRequirement()); + } + } + + private class LpAccuracyComparator implements Comparator<LocationProviderInterface> { + public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { + // Smaller is better + return (l1.getAccuracy() - l2.getAccuracy()); + } + + public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { + return (l1.getAccuracy() == l2.getAccuracy()); + } + } + + private class LpCapabilityComparator implements Comparator<LocationProviderInterface> { + + private static final int ALTITUDE_SCORE = 4; + private static final int BEARING_SCORE = 4; + private static final int SPEED_SCORE = 4; + + private int score(LocationProviderInterface p) { + return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + + (p.supportsBearing() ? BEARING_SCORE : 0) + + (p.supportsSpeed() ? SPEED_SCORE : 0); + } + + public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { + return (score(l2) - score(l1)); // Bigger is better + } + + public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { + return (score(l1) == score(l2)); + } + } + + private LocationProviderInterface best(List<String> providerNames) { + ArrayList<LocationProviderInterface> providers; + synchronized (mLock) { + providers = new ArrayList<LocationProviderInterface>(mProviders.size()); + for (int i = mProviders.size() - 1; i >= 0; i--) { + providers.add(mProviders.get(i)); + } + } + + if (providers.size() < 2) { + return providers.get(0); + } + + // First, sort by power requirement + Collections.sort(providers, new LpPowerComparator()); + int power = providers.get(0).getPowerRequirement(); + if (power < providers.get(1).getPowerRequirement()) { + return providers.get(0); + } + + int idx, size; + + ArrayList<LocationProviderInterface> tmp = new ArrayList<LocationProviderInterface>(); + idx = 0; + size = providers.size(); + while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) { + tmp.add(providers.get(idx)); + idx++; + } + + // Next, sort by accuracy + Collections.sort(tmp, new LpAccuracyComparator()); + int acc = tmp.get(0).getAccuracy(); + if (acc < tmp.get(1).getAccuracy()) { + return tmp.get(0); + } + + ArrayList<LocationProviderInterface> tmp2 = new ArrayList<LocationProviderInterface>(); + idx = 0; + size = tmp.size(); + while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) { + tmp2.add(tmp.get(idx)); + idx++; + } + + // Finally, sort by capability "score" + Collections.sort(tmp2, new LpCapabilityComparator()); + return tmp2.get(0); + } + + /** + * Returns the name of the provider that best meets the given criteria. Only providers + * that are permitted to be accessed by the calling activity will be + * returned. If several providers meet the criteria, the one with the best + * accuracy is returned. If no provider meets the criteria, + * the criteria are loosened in the following sequence: + * + * <ul> + * <li> power requirement + * <li> accuracy + * <li> bearing + * <li> speed + * <li> altitude + * </ul> + * + * <p> Note that the requirement on monetary cost is not removed + * in this process. + * + * @param criteria the criteria that need to be matched + * @param enabledOnly if true then only a provider that is currently enabled is returned + * @return name of the provider that best matches the requirements + */ + public String getBestProvider(Criteria criteria, boolean enabledOnly) { + List<String> goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Make a copy of the criteria that we can modify + criteria = new Criteria(criteria); + + // Loosen power requirement + int power = criteria.getPowerRequirement(); + while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { + power = nextPower(power); + criteria.setPowerRequirement(power); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Loosen accuracy requirement + int accuracy = criteria.getAccuracy(); + while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { + accuracy = nextAccuracy(accuracy); + criteria.setAccuracy(accuracy); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove bearing requirement + criteria.setBearingRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove speed requirement + criteria.setSpeedRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove altitude requirement + criteria.setAltitudeRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + return null; + } + + public boolean providerMeetsCriteria(String provider, Criteria criteria) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) { + throw new IllegalArgumentException("provider=" + provider); + } + return p.meetsCriteria(criteria); + } + private void updateProvidersLocked() { for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface p = mProviders.get(i); @@ -716,6 +933,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run final Receiver mReceiver; final long mMinTime; final float mMinDistance; + final boolean mSingleShot; final int mUid; Location mLastFixBroadcast; long mLastStatusBroadcast; @@ -723,12 +941,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, long minTime, float minDistance, + UpdateRecord(String provider, long minTime, float minDistance, boolean singleShot, Receiver receiver, int uid) { mProvider = provider; mReceiver = receiver; mMinTime = minTime; mMinDistance = minDistance; + mSingleShot = singleShot; mUid = uid; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); @@ -763,6 +982,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run pw.println(prefix + this); pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver); pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance); + pw.println(prefix + "mSingleShot=" + mSingleShot); pw.println(prefix + "mUid=" + mUid); pw.println(prefix + "mLastFixBroadcast:"); if (mLastFixBroadcast != null) { @@ -818,12 +1038,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return false; } - public void requestLocationUpdates(String provider, - long minTime, float minDistance, ILocationListener listener) { - + public void requestLocationUpdates(String provider, Criteria criteria, + long minTime, float minDistance, boolean singleShot, ILocationListener listener) { + if (criteria != null) { + // FIXME - should we consider using multiple providers simultaneously + // rather than only the best one? + // Should we do anything different for single shot fixes? + provider = getBestProvider(criteria, true); + if (provider == null) { + throw new IllegalArgumentException("no providers found for criteria"); + } + } try { synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, getReceiver(listener)); + requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, + getReceiver(listener)); } } catch (SecurityException se) { throw se; @@ -834,11 +1063,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - public void requestLocationUpdatesPI(String provider, - long minTime, float minDistance, PendingIntent intent) { + public void requestLocationUpdatesPI(String provider, Criteria criteria, + long minTime, float minDistance, boolean singleShot, PendingIntent intent) { + if (criteria != null) { + // FIXME - should we consider using multiple providers simultaneously + // rather than only the best one? + // Should we do anything different for single shot fixes? + provider = getBestProvider(criteria, true); + if (provider == null) { + throw new IllegalArgumentException("no providers found for criteria"); + } + } try { synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, getReceiver(intent)); + requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, + getReceiver(intent)); } } catch (SecurityException se) { throw se; @@ -849,8 +1088,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private void requestLocationUpdatesLocked(String provider, - long minTime, float minDistance, Receiver receiver) { + private void requestLocationUpdatesLocked(String provider, long minTime, float minDistance, + boolean singleShot, Receiver receiver) { if (LOCAL_LOGV) { Slog.v(TAG, "_requestLocationUpdates: listener = " + receiver); } @@ -867,7 +1106,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run boolean newUid = !providerHasListener(provider, callingUid, null); long identity = Binder.clearCallingIdentity(); try { - UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, receiver, callingUid); + UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, singleShot, + receiver, callingUid); UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r); if (oldRecord != null) { oldRecord.disposeLocked(); @@ -881,7 +1121,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (isProviderEnabled) { long minTimeForProvider = getMinTimeLocked(provider); p.setMinTime(minTimeForProvider); - p.enableLocationTracking(true); + // try requesting single shot if singleShot is true, and fall back to + // regular location tracking if requestSingleShotFix() is not supported + if (!singleShot || !p.requestSingleShotFix()) { + p.enableLocationTracking(true); + } } else { // Notify the listener that updates are currently disabled receiver.callProviderEnabledLocked(provider, false); @@ -1287,7 +1531,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface provider = mProviders.get(i); - requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityReceiver); + requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, + false, mProximityReceiver); } } } @@ -1368,8 +1613,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } catch (SecurityException se) { throw se; - } catch (IllegalArgumentException iae) { - throw iae; } catch (Exception e) { Slog.e(TAG, "isProviderEnabled got exception:", e); return false; @@ -1393,7 +1636,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { - throw new IllegalArgumentException("provider=" + provider); + return false; } return isAllowedBySettingsLocked(provider); } @@ -1405,8 +1648,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } catch (SecurityException se) { throw se; - } catch (IllegalArgumentException iae) { - throw iae; } catch (Exception e) { Slog.e(TAG, "getLastKnownLocation got exception:", e); return null; @@ -1418,7 +1659,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { - throw new IllegalArgumentException("provider=" + provider); + return null; } if (!isAllowedBySettingsLocked(provider)) { @@ -1485,6 +1726,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (int i=0; i<N; i++) { UpdateRecord r = records.get(i); Receiver receiver = r.mReceiver; + boolean receiverDead = false; Location lastLoc = r.mLastFixBroadcast; if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) { @@ -1496,10 +1738,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } if (!receiver.callLocationChangedLocked(location)) { Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver); - if (deadReceivers == null) { - deadReceivers = new ArrayList<Receiver>(); - } - deadReceivers.add(receiver); + receiverDead = true; } } @@ -1509,13 +1748,18 @@ public class LocationManagerService extends ILocationManager.Stub implements Run r.mLastStatusBroadcast = newStatusUpdateTime; if (!receiver.callStatusChangedLocked(provider, status, extras)) { + receiverDead = true; Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver); - if (deadReceivers == null) { - deadReceivers = new ArrayList<Receiver>(); - } - if (!deadReceivers.contains(receiver)) { - deadReceivers.add(receiver); - } + } + } + + // remove receiver if it is dead or we just processed a single shot request + if (receiverDead || r.mSingleShot) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<Receiver>(); + } + if (!deadReceivers.contains(receiver)) { + deadReceivers.add(receiver); } } } @@ -1692,6 +1936,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // Geocoder + public boolean geocoderIsImplemented() { + return mGeocodeProvider != null; + } + public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { if (mGeocodeProvider != null) { diff --git a/services/java/com/android/server/MasterClearReceiver.java b/services/java/com/android/server/MasterClearReceiver.java index 27a8a74..4d04cee 100644 --- a/services/java/com/android/server/MasterClearReceiver.java +++ b/services/java/com/android/server/MasterClearReceiver.java @@ -39,7 +39,11 @@ public class MasterClearReceiver extends BroadcastReceiver { try { Slog.w(TAG, "!!! FACTORY RESET !!!"); - RecoverySystem.rebootWipeUserData(context); + if (intent.hasExtra("enableEFS")) { + RecoverySystem.rebootToggleEFS(context, intent.getBooleanExtra("enableEFS", false)); + } else { + RecoverySystem.rebootWipeUserData(context); + } Log.wtf(TAG, "Still running after master clear?!"); } catch (IOException e) { Slog.e(TAG, "Can't perform master clear/factory reset", e); diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 6ceeb95..d7b92ec 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -23,11 +23,14 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.res.ObbInfo; +import android.content.res.ObbScanner; import android.net.Uri; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.IMountShutdownObserver; import android.os.storage.StorageResultCode; +import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -53,7 +56,8 @@ class MountService extends IMountService.Stub private static final boolean LOCAL_LOGD = false; private static final boolean DEBUG_UNMOUNT = false; private static final boolean DEBUG_EVENTS = false; - + private static final boolean DEBUG_OBB = true; + private static final String TAG = "MountService"; /* @@ -130,6 +134,12 @@ class MountService extends IMountService.Stub */ final private HashSet<String> mAsecMountSet = new HashSet<String>(); + /** + * Private hash of currently mounted filesystem images. + */ + final private HashSet<String> mObbMountSet = new HashSet<String>(); + + // Handler messages private static final int H_UNMOUNT_PM_UPDATE = 1; private static final int H_UNMOUNT_PM_DONE = 2; private static final int H_UNMOUNT_MS = 3; @@ -287,7 +297,7 @@ class MountService extends IMountService.Stub Slog.w(TAG, "Waiting too long for mReady!"); } } - + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -344,7 +354,7 @@ class MountService extends IMountService.Stub MountServiceBinderListener(IMountServiceListener listener) { mListener = listener; - + } public void binderDied() { @@ -642,10 +652,21 @@ class MountService extends IMountService.Stub } private boolean doGetShareMethodAvailable(String method) { - ArrayList<String> rsp = mConnector.doCommand("share status " + method); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("share status " + method); + } catch (NativeDaemonConnectorException ex) { + Slog.e(TAG, "Failed to determine whether share method " + method + " is available."); + return false; + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + Slog.e(TAG, "Malformed response to share status " + method); + return false; + } + int code; try { code = Integer.parseInt(tok[0]); @@ -770,10 +791,22 @@ class MountService extends IMountService.Stub private boolean doGetVolumeShared(String path, String method) { String cmd = String.format("volume shared %s %s", path, method); - ArrayList<String> rsp = mConnector.doCommand(cmd); + ArrayList<String> rsp; + + try { + rsp = mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException ex) { + Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); + return false; + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); + return false; + } + int code; try { code = Integer.parseInt(tok[0]); @@ -782,9 +815,7 @@ class MountService extends IMountService.Stub return false; } if (code == VoldResponseCode.ShareEnabledResult) { - if (tok[2].equals("enabled")) - return true; - return false; + return "enabled".equals(tok[2]); } else { Slog.e(TAG, String.format("Unexpected response code %d", code)); return false; @@ -1306,5 +1337,145 @@ class MountService extends IMountService.Stub public void finishMediaUpdate() { mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); } + + private boolean isCallerOwnerOfPackage(String packageName) { + final int callerUid = Binder.getCallingUid(); + return isUidOwnerOfPackage(packageName, callerUid); + } + + private boolean isUidOwnerOfPackage(String packageName, int callerUid) { + if (packageName == null) { + return false; + } + + final int packageUid = mPms.getPackageUid(packageName); + + if (DEBUG_OBB) { + Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + + packageUid + ", callerUid = " + callerUid); + } + + return callerUid == packageUid; + } + + public String getMountedObbPath(String filename) { + waitForReady(); + warnOnNotMounted(); + + // XXX replace with call to IMediaContainerService + ObbInfo obbInfo = ObbScanner.getObbInfo(filename); + if (!isCallerOwnerOfPackage(obbInfo.packageName)) { + throw new IllegalArgumentException("Caller package does not match OBB file"); + } + + try { + ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); + String []tok = rsp.get(0).split(" "); + int code = Integer.parseInt(tok[0]); + if (code != VoldResponseCode.AsecPathResult) { + throw new IllegalStateException(String.format("Unexpected response code %d", code)); + } + return tok[1]; + } catch (NativeDaemonConnectorException e) { + int code = e.getCode(); + if (code == VoldResponseCode.OpFailedStorageNotFound) { + throw new IllegalArgumentException(String.format("OBB '%s' not found", filename)); + } else { + throw new IllegalStateException(String.format("Unexpected response code %d", code)); + } + } + } + + public boolean isObbMounted(String filename) { + waitForReady(); + warnOnNotMounted(); + + // XXX replace with call to IMediaContainerService + ObbInfo obbInfo = ObbScanner.getObbInfo(filename); + if (!isCallerOwnerOfPackage(obbInfo.packageName)) { + throw new IllegalArgumentException("Caller package does not match OBB file"); + } + + synchronized (mObbMountSet) { + return mObbMountSet.contains(filename); + } + } + + public int mountObb(String filename, String key) { + waitForReady(); + warnOnNotMounted(); + + synchronized (mObbMountSet) { + if (mObbMountSet.contains(filename)) { + return StorageResultCode.OperationFailedStorageMounted; + } + } + + final int callerUid = Binder.getCallingUid(); + + // XXX replace with call to IMediaContainerService + ObbInfo obbInfo = ObbScanner.getObbInfo(filename); + if (!isUidOwnerOfPackage(obbInfo.packageName, callerUid)) { + throw new IllegalArgumentException("Caller package does not match OBB file"); + } + + if (key == null) { + key = "none"; + } + + int rc = StorageResultCode.OperationSucceeded; + String cmd = String.format("obb mount %s %s %d", filename, key, callerUid); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + int code = e.getCode(); + if (code != VoldResponseCode.OpFailedStorageBusy) { + rc = StorageResultCode.OperationFailedInternalError; + } + } + + if (rc == StorageResultCode.OperationSucceeded) { + synchronized (mObbMountSet) { + mObbMountSet.add(filename); + } + } + return rc; + } + + public int unmountObb(String filename, boolean force) { + waitForReady(); + warnOnNotMounted(); + + ObbInfo obbInfo = ObbScanner.getObbInfo(filename); + if (!isCallerOwnerOfPackage(obbInfo.packageName)) { + throw new IllegalArgumentException("Caller package does not match OBB file"); + } + + synchronized (mObbMountSet) { + if (!mObbMountSet.contains(filename)) { + return StorageResultCode.OperationFailedStorageNotMounted; + } + } + + int rc = StorageResultCode.OperationSucceeded; + String cmd = String.format("obb unmount %s%s", filename, (force ? " force" : "")); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + int code = e.getCode(); + if (code == VoldResponseCode.OpFailedStorageBusy) { + rc = StorageResultCode.OperationFailedStorageBusy; + } else { + rc = StorageResultCode.OperationFailedInternalError; + } + } + + if (rc == StorageResultCode.OperationSucceeded) { + synchronized (mObbMountSet) { + mObbMountSet.remove(filename); + } + } + return rc; + } } diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index 08d7ce6..c452590 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -128,12 +128,11 @@ final class NativeDaemonConnector implements Runnable { Slog.e(TAG, String.format( "Error handling '%s'", event), ex); } - } else { - try { - mResponseQueue.put(event); - } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to put response onto queue", ex); - } + } + try { + mResponseQueue.put(event); + } catch (InterruptedException ex) { + Slog.e(TAG, "Failed to put response onto queue", ex); } } catch (NumberFormatException nfe) { Slog.w(TAG, String.format("Bad msg (%s)", event)); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index cbbc7be..c156150 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -35,6 +35,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; import java.util.ArrayList; +import java.util.NoSuchElementException; import java.util.StringTokenizer; import android.provider.Settings; import android.content.ContentResolver; @@ -226,44 +227,61 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult); + try { + return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Cannot communicate with native daemon to list interfaces"); + } } public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException { - String rsp = mConnector.doCommand("interface getcfg " + iface).get(0); + String rsp; + try { + rsp = mConnector.doCommand("interface getcfg " + iface).get(0); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Cannot communicate with native daemon to get interface config"); + } Slog.d(TAG, String.format("rsp <%s>", rsp)); // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz.zzz.zzz.zzz [flag1 flag2 flag3] StringTokenizer st = new StringTokenizer(rsp); + InterfaceConfiguration cfg; try { - int code = Integer.parseInt(st.nextToken(" ")); - if (code != NetdResponseCode.InterfaceGetCfgResult) { + try { + int code = Integer.parseInt(st.nextToken(" ")); + if (code != NetdResponseCode.InterfaceGetCfgResult) { + throw new IllegalStateException( + String.format("Expected code %d, but got %d", + NetdResponseCode.InterfaceGetCfgResult, code)); + } + } catch (NumberFormatException nfe) { throw new IllegalStateException( - String.format("Expected code %d, but got %d", - NetdResponseCode.InterfaceGetCfgResult, code)); + String.format("Invalid response from daemon (%s)", rsp)); } - } catch (NumberFormatException nfe) { - throw new IllegalStateException( - String.format("Invalid response from daemon (%s)", rsp)); - } - InterfaceConfiguration cfg = new InterfaceConfiguration(); - cfg.hwAddr = st.nextToken(" "); - try { - cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); - } catch (UnknownHostException uhe) { - Slog.e(TAG, "Failed to parse ipaddr", uhe); - cfg.ipAddr = 0; - } + cfg = new InterfaceConfiguration(); + cfg.hwAddr = st.nextToken(" "); + try { + cfg.ipAddr = stringToIpAddr(st.nextToken(" ")); + } catch (UnknownHostException uhe) { + Slog.e(TAG, "Failed to parse ipaddr", uhe); + cfg.ipAddr = 0; + } - try { - cfg.netmask = stringToIpAddr(st.nextToken(" ")); - } catch (UnknownHostException uhe) { - Slog.e(TAG, "Failed to parse netmask", uhe); - cfg.netmask = 0; + try { + cfg.netmask = stringToIpAddr(st.nextToken(" ")); + } catch (UnknownHostException uhe) { + Slog.e(TAG, "Failed to parse netmask", uhe); + cfg.netmask = 0; + } + cfg.interfaceFlags = st.nextToken("]").trim() +"]"; + } catch (NoSuchElementException nsee) { + throw new IllegalStateException( + String.format("Invalid response from daemon (%s)", rsp)); } - cfg.interfaceFlags = st.nextToken("]").trim() +"]"; Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags)); return cfg; } @@ -272,7 +290,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { String iface, InterfaceConfiguration cfg) throws IllegalStateException { String cmd = String.format("interface setcfg %s %s %s %s", iface, intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags); - mConnector.doCommand(cmd); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate with native daemon to interface setcfg"); + } } public void shutdown() { @@ -289,20 +312,25 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - ArrayList<String> rsp = mConnector.doCommand("ipfwd status"); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("ipfwd status"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate with native daemon to ipfwd status"); + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + Slog.e(TAG, "Malformed response from native daemon: " + line); + return false; + } + int code = Integer.parseInt(tok[0]); if (code == NetdResponseCode.IpFwdStatusResult) { // 211 Forwarding <enabled/disabled> - if (tok.length !=2) { - throw new IllegalStateException( - String.format("Malformatted list entry '%s'", line)); - } - if (tok[2].equals("enabled")) - return true; - return false; + return "enabled".equals(tok[2]); } else { throw new IllegalStateException(String.format("Unexpected response code %d", code)); } @@ -326,29 +354,45 @@ class NetworkManagementService extends INetworkManagementService.Stub { for (String d : dhcpRange) { cmd += " " + d; } - mConnector.doCommand(cmd); + + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Unable to communicate to native daemon"); + } } public void stopTethering() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("tether stop"); + try { + mConnector.doCommand("tether stop"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Unable to communicate to native daemon to stop tether"); + } } public boolean isTetheringStarted() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - ArrayList<String> rsp = mConnector.doCommand("tether status"); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("tether status"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon to get tether status"); + } for (String line : rsp) { - String []tok = line.split(" "); + String[] tok = line.split(" "); + if (tok.length < 3) { + throw new IllegalStateException("Malformed response for tether status: " + line); + } int code = Integer.parseInt(tok[0]); if (code == NetdResponseCode.TetherStatusResult) { // XXX: Tethering services <started/stopped> <TBD>... - if (tok[2].equals("started")) - return true; - return false; + return "started".equals(tok[2]); } else { throw new IllegalStateException(String.format("Unexpected response code %d", code)); } @@ -359,20 +403,35 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void tetherInterface(String iface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("tether interface add " + iface); + try { + mConnector.doCommand("tether interface add " + iface); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for adding tether interface"); + } } public void untetherInterface(String iface) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("tether interface remove " + iface); + try { + mConnector.doCommand("tether interface remove " + iface); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for removing tether interface"); + } } public String[] listTetheredInterfaces() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand( - "tether interface list", NetdResponseCode.TetherInterfaceListResult); + try { + return mConnector.doListCommand( + "tether interface list", NetdResponseCode.TetherInterfaceListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for listing tether interfaces"); + } } public void setDnsForwarders(String[] dns) throws IllegalStateException { @@ -383,7 +442,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { for (String s : dns) { cmd += " " + InetAddress.getByName(s).getHostAddress(); } - mConnector.doCommand(cmd); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for setting tether dns"); + } } catch (UnknownHostException e) { throw new IllegalStateException("Error resolving dns name", e); } @@ -392,30 +456,50 @@ class NetworkManagementService extends INetworkManagementService.Stub { public String[] getDnsForwarders() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand( - "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult); + try { + return mConnector.doListCommand( + "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for listing tether dns"); + } } public void enableNat(String internalInterface, String externalInterface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand( - String.format("nat enable %s %s", internalInterface, externalInterface)); + try { + mConnector.doCommand( + String.format("nat enable %s %s", internalInterface, externalInterface)); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for enabling NAT interface"); + } } public void disableNat(String internalInterface, String externalInterface) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand( - String.format("nat disable %s %s", internalInterface, externalInterface)); + try { + mConnector.doCommand( + String.format("nat disable %s %s", internalInterface, externalInterface)); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for disabling NAT interface"); + } } public String[] listTtys() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); + try { + return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Unable to communicate to native daemon for listing TTYs"); + } } public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, @@ -430,31 +514,52 @@ class NetworkManagementService extends INetworkManagementService.Stub { InetAddress.getByName(dns2Addr).getHostAddress())); } catch (UnknownHostException e) { throw new IllegalStateException("Error resolving addr", e); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to attach pppd", e); } } public void detachPppd(String tty) throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("pppd detach %s", tty)); + try { + mConnector.doCommand(String.format("pppd detach %s", tty)); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to detach pppd", e); + } } public void startUsbRNDIS() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("usb startrndis"); + try { + mConnector.doCommand("usb startrndis"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Error communicating to native daemon for starting RNDIS", e); + } } public void stopUsbRNDIS() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand("usb stoprndis"); + try { + mConnector.doCommand("usb stoprndis"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon", e); + } } public boolean isUsbRNDISStarted() throws IllegalStateException { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - ArrayList<String> rsp = mConnector.doCommand("usb rndisstatus"); + ArrayList<String> rsp; + try { + rsp = mConnector.doCommand("usb rndisstatus"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException( + "Error communicating to native daemon to check RNDIS status", e); + } for (String line : rsp) { String []tok = line.split(" "); @@ -476,31 +581,35 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("softap stop " + wlanIface)); - mConnector.doCommand(String.format("softap fwreload " + wlanIface + " AP")); - mConnector.doCommand(String.format("softap start " + wlanIface)); - if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); - } else { - /** - * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8] - * argv1 - wlan interface - * argv2 - softap interface - * argv3 - SSID - * argv4 - Security - * argv5 - Key - * argv6 - Channel - * argv7 - Preamble - * argv8 - Max SCB - */ - String str = String.format("softap set " + wlanIface + " " + softapIface + - " %s %s %s", convertQuotedString(wifiConfig.SSID), - wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? - "wpa2-psk" : "open", - convertQuotedString(wifiConfig.preSharedKey)); - mConnector.doCommand(str); - } - mConnector.doCommand(String.format("softap startap")); + try { + mConnector.doCommand(String.format("softap stop " + wlanIface)); + mConnector.doCommand(String.format("softap fwreload " + wlanIface + " AP")); + mConnector.doCommand(String.format("softap start " + wlanIface)); + if (wifiConfig == null) { + mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + } else { + /** + * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8] + * argv1 - wlan interface + * argv2 - softap interface + * argv3 - SSID + * argv4 - Security + * argv5 - Key + * argv6 - Channel + * argv7 - Preamble + * argv8 - Max SCB + */ + String str = String.format("softap set " + wlanIface + " " + softapIface + + " %s %s %s", convertQuotedString(wifiConfig.SSID), + wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? + "wpa2-psk" : "open", + convertQuotedString(wifiConfig.preSharedKey)); + mConnector.doCommand(str); + } + mConnector.doCommand(String.format("softap startap")); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to start softap", e); + } } private String convertQuotedString(String s) { @@ -516,7 +625,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - mConnector.doCommand("softap stopap"); + try { + mConnector.doCommand("softap stopap"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to stop soft AP", + e); + } } public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) @@ -525,15 +639,19 @@ class NetworkManagementService extends INetworkManagementService.Stub { android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); - } else { - String str = String.format("softap set " + wlanIface + " " + softapIface + - " %s %s %s", convertQuotedString(wifiConfig.SSID), - wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? - "wpa2-psk" : "open", - convertQuotedString(wifiConfig.preSharedKey)); - mConnector.doCommand(str); + try { + if (wifiConfig == null) { + mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + } else { + String str = String.format("softap set " + wlanIface + " " + softapIface + + " %s %s %s", convertQuotedString(wifiConfig.SSID), + wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ? "wpa2-psk" : "open", + convertQuotedString(wifiConfig.preSharedKey)); + mConnector.doCommand(str); + } + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Error communicating to native daemon to set soft AP", + e); } } @@ -541,9 +659,22 @@ class NetworkManagementService extends INetworkManagementService.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); try { - String rsp = mConnector.doCommand( - String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0); - String []tok = rsp.split(" "); + String rsp; + try { + rsp = mConnector.doCommand( + String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0); + } catch (NativeDaemonConnectorException e1) { + Slog.e(TAG, "Error communicating with native daemon", e1); + return -1; + } + + String[] tok = rsp.split(" "); + if (tok.length < 2) { + Slog.e(TAG, String.format("Malformed response for reading %s interface", + (rx ? "rx" : "tx"))); + return -1; + } + int code; try { code = Integer.parseInt(tok[0]); @@ -575,17 +706,34 @@ class NetworkManagementService extends INetworkManagementService.Stub { public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format( - "interface setthrottle %s %d %d", iface, rxKbps, txKbps)); + try { + mConnector.doCommand(String.format( + "interface setthrottle %s %d %d", iface, rxKbps, txKbps)); + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "Error communicating with native daemon to set throttle", e); + } } private int getInterfaceThrottle(String iface, boolean rx) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); try { - String rsp = mConnector.doCommand( - String.format("interface getthrottle %s %s", iface,(rx ? "rx" : "tx"))).get(0); - String []tok = rsp.split(" "); + String rsp; + try { + rsp = mConnector.doCommand( + String.format("interface getthrottle %s %s", iface, + (rx ? "rx" : "tx"))).get(0); + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "Error communicating with native daemon to getthrottle", e); + return -1; + } + + String[] tok = rsp.split(" "); + if (tok.length < 2) { + Slog.e(TAG, "Malformed response to getthrottle command"); + return -1; + } + int code; try { code = Integer.parseInt(tok[0]); diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 73d17ea..63325dd 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -16,9 +16,8 @@ package com.android.server; -import com.android.server.status.IconData; -import com.android.server.status.NotificationData; -import com.android.server.status.StatusBarService; +import com.android.internal.statusbar.StatusBarNotification; +import com.android.server.StatusBarManagerService; import android.app.ActivityManagerNative; import android.app.IActivityManager; @@ -39,9 +38,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; +import android.hardware.Usb; import android.media.AudioManager; import android.net.Uri; import android.os.BatteryManager; +import android.os.Bundle; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -66,11 +67,14 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -class NotificationManagerService extends INotificationManager.Stub +/** {@hide} */ +public class NotificationManagerService extends INotificationManager.Stub { private static final String TAG = "NotificationService"; private static final boolean DBG = false; + private static final int MAX_PACKAGE_NOTIFICATIONS = 50; + // message codes private static final int MESSAGE_TIMEOUT = 2; @@ -86,7 +90,7 @@ class NotificationManagerService extends INotificationManager.Stub final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; - private StatusBarService mStatusBarService; + private StatusBarManagerService mStatusBar; private LightsService mLightsService; private LightsService.Light mBatteryLight; private LightsService.Light mNotificationLight; @@ -110,8 +114,6 @@ class NotificationManagerService extends INotificationManager.Stub private boolean mNotificationPulseEnabled; // for adb connected notifications - private boolean mUsbConnected; - private boolean mAdbEnabled = false; private boolean mAdbNotificationShown = false; private Notification mAdbNotification; @@ -163,16 +165,21 @@ class NotificationManagerService extends INotificationManager.Stub final String pkg; final String tag; final int id; + final int uid; + final int initialPid; ITransientNotification callback; int duration; final Notification notification; IBinder statusBarKey; - NotificationRecord(String pkg, String tag, int id, Notification notification) + NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, + Notification notification) { this.pkg = pkg; this.tag = tag; this.id = id; + this.uid = uid; + this.initialPid = initialPid; this.notification = notification; } @@ -238,8 +245,8 @@ class NotificationManagerService extends INotificationManager.Stub } } - private StatusBarService.NotificationCallbacks mNotificationCallbacks - = new StatusBarService.NotificationCallbacks() { + private StatusBarManagerService.NotificationCallbacks mNotificationCallbacks + = new StatusBarManagerService.NotificationCallbacks() { public void onSetDisabled(int status) { synchronized (mNotificationList) { @@ -302,6 +309,21 @@ class NotificationManagerService extends INotificationManager.Stub updateLightsLocked(); } } + + 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 + + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); + cancelNotification(pkg, tag, id, 0, 0); + long ident = Binder.clearCallingIdentity(); + try { + ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, + "Bad notification posted from package " + pkg + + ": " + message); + } catch (RemoteException e) { + } + Binder.restoreCallingIdentity(ident); + } }; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -326,12 +348,14 @@ class NotificationManagerService extends INotificationManager.Stub mBatteryFull = batteryFull; updateLights(); } - } else if (action.equals(Intent.ACTION_UMS_CONNECTED)) { - mUsbConnected = true; - updateAdbNotification(); - } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) { - mUsbConnected = false; - updateAdbNotification(); + } else if (action.equals(Usb.ACTION_USB_STATE)) { + Bundle extras = intent.getExtras(); + boolean usbConnected = extras.getBoolean(Usb.USB_CONNECTED); + boolean adbEnabled = (Usb.USB_FUNCTION_ENABLED.equals( + extras.getString(Usb.USB_FUNCTION_ADB))); + updateAdbNotification(usbConnected && adbEnabled); + } else if (action.equals(Usb.ACTION_USB_DISCONNECTED)) { + updateAdbNotification(false); } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || action.equals(Intent.ACTION_PACKAGE_RESTARTED) || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) @@ -377,8 +401,6 @@ class NotificationManagerService extends INotificationManager.Stub void observe() { ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.ADB_ENABLED), false, this); resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.NOTIFICATION_LIGHT_PULSE), false, this); update(); @@ -390,12 +412,6 @@ class NotificationManagerService extends INotificationManager.Stub public void update() { ContentResolver resolver = mContext.getContentResolver(); - boolean adbEnabled = Settings.Secure.getInt(resolver, - Settings.Secure.ADB_ENABLED, 0) != 0; - if (mAdbEnabled != adbEnabled) { - mAdbEnabled = adbEnabled; - updateAdbNotification(); - } boolean pulseEnabled = Settings.System.getInt(resolver, Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0; if (mNotificationPulseEnabled != pulseEnabled) { @@ -405,7 +421,7 @@ class NotificationManagerService extends INotificationManager.Stub } } - NotificationManagerService(Context context, StatusBarService statusBar, + NotificationManagerService(Context context, StatusBarManagerService statusBar, LightsService lights) { super(); @@ -417,7 +433,7 @@ class NotificationManagerService extends INotificationManager.Stub mToastQueue = new ArrayList<ToastRecord>(); mHandler = new WorkerHandler(); - mStatusBarService = statusBar; + mStatusBar = statusBar; statusBar.setNotificationCallbacks(mNotificationCallbacks); mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY); @@ -444,8 +460,7 @@ class NotificationManagerService extends INotificationManager.Stub // register for battery changed notifications IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_UMS_CONNECTED); - filter.addAction(Intent.ACTION_UMS_DISCONNECTED); + filter.addAction(Usb.ACTION_USB_STATE); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); @@ -653,11 +668,43 @@ class NotificationManagerService extends INotificationManager.Stub enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut); } - public void enqueueNotificationWithTag(String pkg, String tag, int id, - Notification notification, int[] idOut) + public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification, + int[] idOut) + { + enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), + tag, id, notification, idOut); + } + + // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the + // uid/pid of another application) + public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, + String tag, int id, Notification notification, int[] idOut) { + Slog.d(TAG, "enqueueNotificationWithTag: calling uid=" + callingUid + + ", pid=" + callingPid); + checkIncomingCall(pkg); + // Limit the number of notifications that any given package except the android + // package can enqueue. Prevents DOS attacks and deals with leaks. + if (!"android".equals(pkg)) { + synchronized (mNotificationList) { + int count = 0; + final int N = mNotificationList.size(); + for (int i=0; i<N; i++) { + final NotificationRecord r = mNotificationList.get(i); + if (r.pkg.equals(pkg)) { + count++; + if (count >= MAX_PACKAGE_NOTIFICATIONS) { + Slog.e(TAG, "Package has already posted " + count + + " notifications. Not showing more. package=" + pkg); + return; + } + } + } + } + } + // This conditional is a dirty hack to limit the logging done on // behalf of the download manager without affecting other apps. if (!pkg.equals("com.android.providers.downloads") @@ -681,7 +728,8 @@ class NotificationManagerService extends INotificationManager.Stub } synchronized (mNotificationList) { - NotificationRecord r = new NotificationRecord(pkg, tag, id, notification); + NotificationRecord r = new NotificationRecord(pkg, tag, id, + callingUid, callingPid, notification); NotificationRecord old = null; int index = indexOfNotificationLocked(pkg, tag, id); @@ -705,36 +753,13 @@ class NotificationManagerService extends INotificationManager.Stub } if (notification.icon != 0) { - IconData icon = IconData.makeIcon(null, pkg, notification.icon, - notification.iconLevel, - notification.number); - CharSequence truncatedTicker = notification.tickerText; - - // TODO: make this restriction do something smarter like never fill - // more than two screens. "Why would anyone need more than 80 characters." :-/ - final int maxTickerLen = 80; - if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) { - truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen); - } - - NotificationData n = new NotificationData(); - n.pkg = pkg; - n.tag = tag; - n.id = id; - n.when = notification.when; - n.tickerText = truncatedTicker; - n.ongoingEvent = (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; - if (!n.ongoingEvent && (notification.flags & Notification.FLAG_NO_CLEAR) == 0) { - n.clearable = true; - } - n.contentView = notification.contentView; - n.contentIntent = notification.contentIntent; - n.deleteIntent = notification.deleteIntent; + StatusBarNotification n = new StatusBarNotification(pkg, id, tag, + r.uid, r.initialPid, notification); if (old != null && old.statusBarKey != null) { r.statusBarKey = old.statusBarKey; long identity = Binder.clearCallingIdentity(); try { - mStatusBarService.updateIcon(r.statusBarKey, icon, n); + mStatusBar.updateNotification(r.statusBarKey, n); } finally { Binder.restoreCallingIdentity(identity); @@ -742,21 +767,19 @@ class NotificationManagerService extends INotificationManager.Stub } else { long identity = Binder.clearCallingIdentity(); try { - r.statusBarKey = mStatusBarService.addIcon(icon, n); + r.statusBarKey = mStatusBar.addNotification(n); mAttentionLight.pulse(); } finally { Binder.restoreCallingIdentity(identity); } } - sendAccessibilityEvent(notification, pkg); - } else { if (old != null && old.statusBarKey != null) { long identity = Binder.clearCallingIdentity(); try { - mStatusBarService.removeIcon(old.statusBarKey); + mStatusBar.removeNotification(old.statusBarKey); } finally { Binder.restoreCallingIdentity(identity); @@ -864,7 +887,7 @@ class NotificationManagerService extends INotificationManager.Stub if (r.notification.icon != 0) { long identity = Binder.clearCallingIdentity(); try { - mStatusBarService.removeIcon(r.statusBarKey); + mStatusBar.removeNotification(r.statusBarKey); } finally { Binder.restoreCallingIdentity(identity); @@ -1118,8 +1141,8 @@ class NotificationManagerService extends INotificationManager.Stub // This is here instead of StatusBarPolicy because it is an important // security feature that we don't want people customizing the platform // to accidentally lose. - private void updateAdbNotification() { - if (mAdbEnabled && mUsbConnected) { + private void updateAdbNotification(boolean adbEnabled) { + if (adbEnabled) { if ("0".equals(SystemProperties.get("persist.adb.notify"))) { return; } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index d23c16a..0ff33d1 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -150,6 +150,8 @@ class PackageManagerService extends IPackageManager.Stub { private static final boolean GET_CERTIFICATES = true; + private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; + private static final int REMOVE_EVENTS = FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_FROM; private static final int ADD_EVENTS = @@ -185,7 +187,9 @@ class PackageManagerService extends IPackageManager.Stub { static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName( "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService"); - + + static final String mTempContainerPrefix = "smdl2tmp"; + final HandlerThread mHandlerThread = new HandlerThread("PackageManager", Process.THREAD_PRIORITY_BACKGROUND); final PackageHandler mHandler; @@ -204,6 +208,10 @@ class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; + // If Encrypted File System feature is enabled, all application persistent data + // should go here instead. + final File mSecureAppDataDir; + // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -768,6 +776,7 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); + mSecureAppDataDir = new File(dataDir, "secure/data"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); if (mInstaller == null) { @@ -777,6 +786,7 @@ class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mSecureAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -937,7 +947,9 @@ class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + // XXX how to set useEncryptedFSDir for packages that + // are not encrypted? + mInstaller.remove(ps.name, true); } } } @@ -1020,7 +1032,8 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg); + int retCode = mInstaller.remove(ps.name, useSecureFS); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); @@ -1484,6 +1497,7 @@ class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.mSetEnabled = ps.enabled; } return generatePackageInfo(ps.pkg, flags); } @@ -1718,6 +1732,7 @@ class PackageManagerService extends IPackageManager.Stub { static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) { if (pi1.icon != pi2.icon) return false; + if (pi1.logo != pi2.logo) return false; if (pi1.protectionLevel != pi2.protectionLevel) return false; if (!compareStrings(pi1.name, pi2.name)) return false; if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false; @@ -2743,6 +2758,11 @@ class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } + + private static boolean useEncryptedFilesystemForPackage(PackageParser.Package pkg) { + return Environment.isEncryptedFilesystemEnabled() && + ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0); + } private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { @@ -2760,7 +2780,14 @@ class PackageManagerService extends IPackageManager.Stub { } private File getDataPathForPackage(PackageParser.Package pkg) { - return new File(mAppDataDir, pkg.packageName); + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); + File dataPath; + if (useEncryptedFSDir) { + dataPath = new File(mSecureAppDataDir, pkg.packageName); + } else { + dataPath = new File(mAppDataDir, pkg.packageName); + } + return dataPath; } private PackageParser.Package scanPackageLI(PackageParser.Package pkg, @@ -3111,6 +3138,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg); dataPath = getDataPathForPackage(pkg); boolean uidError = false; @@ -3127,7 +3155,7 @@ class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName); + int ret = mInstaller.remove(pkgName, useEncryptedFSDir); if (ret >= 0) { // Old data gone! String msg = "System package " + pkg.packageName @@ -3138,7 +3166,7 @@ class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, + ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if (ret == -1) { // Ack should not happen! @@ -3178,7 +3206,7 @@ class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, "Want this data dir: " + dataPath); //invoke installer to do the actual installation if (mInstaller != null) { - int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, + int ret = mInstaller.install(pkgName, useEncryptedFSDir, pkg.applicationInfo.uid, pkg.applicationInfo.uid); if(ret < 0) { // Error from installer @@ -3623,21 +3651,19 @@ class PackageManagerService extends IPackageManager.Stub { installedNativeLibraries = true; + // Always extract the shared library String sharedLibraryFilePath = sharedLibraryDir.getPath() + File.separator + libFileName; File sharedLibraryFile = new File(sharedLibraryFilePath); - if (! sharedLibraryFile.exists() || - sharedLibraryFile.length() != entry.getSize() || - sharedLibraryFile.lastModified() != entry.getTime()) { - if (Config.LOGD) { - Log.d(TAG, "Caching shared lib " + entry.getName()); - } - if (mInstaller == null) { - sharedLibraryDir.mkdir(); - } - cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir, - sharedLibraryFile); + + if (Config.LOGD) { + Log.d(TAG, "Caching shared lib " + entry.getName()); } + if (mInstaller == null) { + sharedLibraryDir.mkdir(); + } + cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir, + sharedLibraryFile); } if (!hasNativeLibraries) return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES; @@ -3679,18 +3705,16 @@ class PackageManagerService extends IPackageManager.Stub { String installGdbServerPath = installGdbServerDir.getPath() + "/" + GDBSERVER; File installGdbServerFile = new File(installGdbServerPath); - if (! installGdbServerFile.exists() || - installGdbServerFile.length() != entry.getSize() || - installGdbServerFile.lastModified() != entry.getTime()) { - if (Config.LOGD) { - Log.d(TAG, "Caching gdbserver " + entry.getName()); - } - if (mInstaller == null) { - installGdbServerDir.mkdir(); - } - cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir, - installGdbServerFile); + + if (Config.LOGD) { + Log.d(TAG, "Caching gdbserver " + entry.getName()); + } + if (mInstaller == null) { + installGdbServerDir.mkdir(); } + cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir, + installGdbServerFile); + return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES; } return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES; @@ -3704,6 +3728,16 @@ class PackageManagerService extends IPackageManager.Stub { // one if ro.product.cpu.abi2 is defined. // private int cachePackageSharedLibsLI(PackageParser.Package pkg, File scanFile) { + // Remove all native binaries from a directory. This is used when upgrading + // a package: in case the new .apk doesn't contain a native binary that was + // in the old one (and thus installed), we need to remove it from + // /data/data/<appname>/lib + // + // The simplest way to do that is to remove all files in this directory, + // since it is owned by "system", applications are not supposed to write + // anything there. + removeNativeBinariesLI(pkg); + String cpuAbi = Build.CPU_ABI; try { int result = cachePackageSharedLibsForAbiLI(pkg, scanFile, cpuAbi); @@ -4552,6 +4586,8 @@ class PackageManagerService extends IPackageManager.Stub { } }; + private static final boolean DEBUG_OBB = false; + private static final void sendPackageBroadcast(String action, String pkg, Bundle extras, IIntentReceiver finishedReceiver) { IActivityManager am = ActivityManagerNative.getDefault(); @@ -4726,6 +4762,29 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void setPackageObbPath(String packageName, String path) { + if (DEBUG_OBB) + Log.v(TAG, "Setting .obb path for " + packageName + " to: " + path); + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingPermission( + android.Manifest.permission.INSTALL_PACKAGES); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + if (!allowedByPermission && (uid != pkgSetting.userId)) { + throw new SecurityException("Permission denial: attempt to set .obb file from pid=" + + Binder.getCallingPid() + ", uid=" + uid + ", package uid=" + + pkgSetting.userId); + } + pkgSetting.obbPathString = path; + mSettings.writeLP(); + } + } + private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { @@ -4960,7 +5019,12 @@ class PackageManagerService extends IPackageManager.Stub { @Override void handleReturnCode() { - processPendingInstall(mArgs, mRet); + // If mArgs is null, then MCS couldn't be reached. When it + // reconnects, it will try again to install. At that point, this + // will succeed. + if (mArgs != null) { + processPendingInstall(mArgs, mRet); + } } @Override @@ -5803,8 +5867,17 @@ class PackageManagerService extends IPackageManager.Stub { if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath); if (retCode != 0) { - Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath); - return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + if (mNoDexOpt) { + /* + * If we're in an engineering build, programs are lazily run + * through dexopt. If the .dex file doesn't exist yet, it + * will be created when the program is run next. + */ + Slog.i(TAG, "dex file doesn't exist, skipping move: " + newPackage.mPath); + } else { + Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath); + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } } } return PackageManager.INSTALL_SUCCEEDED; @@ -6209,8 +6282,9 @@ class PackageManagerService extends IPackageManager.Stub { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int retCode = mInstaller.remove(packageName); + int retCode = mInstaller.remove(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); @@ -6222,11 +6296,10 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = new File(pkg.applicationInfo.dataDir); dataDir.delete(); } + schedulePackageCleaning(packageName); } synchronized (mPackages) { if (deletedPs != null) { - schedulePackageCleaning(packageName); - if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (outInfo != null) { outInfo.removedUid = mSettings.removePackageLP(packageName); @@ -6451,6 +6524,7 @@ class PackageManagerService extends IPackageManager.Stub { p = ps.pkg; } } + boolean useEncryptedFSDir = false; if(!dataOnly) { //need to check this only for fully installed applications @@ -6463,9 +6537,10 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } + useEncryptedFSDir = useEncryptedFilesystemForPackage(p); } if (mInstaller != null) { - int retCode = mInstaller.clearUserData(packageName); + int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6516,8 +6591,9 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { - int retCode = mInstaller.deleteCacheFiles(packageName); + int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6579,9 +6655,10 @@ class PackageManagerService extends IPackageManager.Stub { } publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null; } + boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p); if (mInstaller != null) { int res = mInstaller.getSizeInfo(packageName, p.mPath, - publicSrcDir, pStats); + publicSrcDir, pStats, useEncryptedFSDir); if (res < 0) { return false; } else { @@ -6814,6 +6891,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } pkgSetting.enabled = newState; + pkgSetting.pkg.mSetEnabled = newState; } else { // We're dealing with a component level state change switch (newState) { @@ -7082,12 +7160,19 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(" pkg="); pw.println(ps.pkg); pw.print(" codePath="); pw.println(ps.codePathString); pw.print(" resourcePath="); pw.println(ps.resourcePathString); + pw.print(" obbPath="); pw.println(ps.obbPathString); if (ps.pkg != null) { pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); pw.print(" supportsScreens=["); boolean first = true; if ((ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + if (!first) pw.print(", "); + first = false; + pw.print("small"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { if (!first) pw.print(", "); first = false; @@ -7100,10 +7185,10 @@ class PackageManagerService extends IPackageManager.Stub { pw.print("large"); } if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { if (!first) pw.print(", "); first = false; - pw.print("small"); + pw.print("xlarge"); } if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { @@ -7126,6 +7211,9 @@ class PackageManagerService extends IPackageManager.Stub { pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); pw.print(" installStatus="); pw.print(ps.installStatus); pw.print(" enabled="); pw.println(ps.enabled); + if (ps.pkg.mOperationPending) { + pw.println(" mOperationPending=true"); + } if (ps.disabledComponents.size() > 0) { pw.println(" disabledComponents:"); for (String s : ps.disabledComponents) { @@ -7627,7 +7715,8 @@ class PackageManagerService extends IPackageManager.Stub { this.pkgFlags = pkgFlags & ( ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE); + ApplicationInfo.FLAG_EXTERNAL_STORAGE | + ApplicationInfo.FLAG_NEVER_ENCRYPT); } } @@ -7641,6 +7730,7 @@ class PackageManagerService extends IPackageManager.Stub { String codePathString; File resourcePath; String resourcePathString; + String obbPathString; private long timeStamp; private String timeStampString = "0"; int versionCode; @@ -7894,11 +7984,17 @@ class PackageManagerService extends IPackageManager.Stub { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); // TODO(oam): This secure dir creation needs to be moved somewhere else (later) + File systemSecureDir = new File(dataDir, "secure/system"); systemDir.mkdirs(); + systemSecureDir.mkdirs(); FileUtils.setPermissions(systemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); + FileUtils.setPermissions(systemSecureDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); mSettingsFilename = new File(systemDir, "packages.xml"); mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); mPackageListFilename = new File(systemDir, "packages.list"); @@ -8185,6 +8281,7 @@ class PackageManagerService extends IPackageManager.Stub { private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) { p.pkg = pkg; + pkg.mSetEnabled = p.enabled; String codePath = pkg.applicationInfo.sourceDir; String resourcePath = pkg.applicationInfo.publicSourceDir; // Update code path if needed @@ -8635,6 +8732,9 @@ class PackageManagerService extends IPackageManager.Stub { if (pkg.installerPackageName != null) { serializer.attribute(null, "installer", pkg.installerPackageName); } + if (pkg.obbPathString != null) { + serializer.attribute(null, "obbPath", pkg.obbPathString); + } pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); if ((pkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { serializer.startTag(null, "perms"); @@ -9011,6 +9111,7 @@ class PackageManagerService extends IPackageManager.Stub { String sharedIdStr = null; String codePathStr = null; String resourcePathStr = null; + String obbPathStr = null; String systemStr = null; String installerPackageName = null; String uidError = null; @@ -9028,6 +9129,7 @@ class PackageManagerService extends IPackageManager.Stub { sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); codePathStr = parser.getAttributeValue(null, "codePath"); resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + obbPathStr = parser.getAttributeValue(null, "obbPath"); version = parser.getAttributeValue(null, "version"); if (version != null) { try { @@ -9125,6 +9227,7 @@ class PackageManagerService extends IPackageManager.Stub { if (packageSetting != null) { packageSetting.uidError = "true".equals(uidError); packageSetting.installerPackageName = installerPackageName; + packageSetting.obbPathString = obbPathStr; final String enabledStr = parser.getAttributeValue(null, "enabled"); if (enabledStr != null) { if (enabledStr.equalsIgnoreCase("true")) { @@ -9404,6 +9507,9 @@ class PackageManagerService extends IPackageManager.Stub { } boolean isEnabledLP(ComponentInfo componentInfo, int flags) { + if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { + return true; + } final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); if (Config.LOGV) { Log.v(TAG, "isEnabledLock - packageName = " + componentInfo.packageName @@ -9419,14 +9525,20 @@ class PackageManagerService extends IPackageManager.Stub { Debug.waitForDebugger(); Log.i(TAG, "We will crash!"); } + return false; } - return ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) - || ((componentInfo.enabled - && ((packageSettings.enabled == COMPONENT_ENABLED_STATE_ENABLED) - || (componentInfo.applicationInfo.enabled - && packageSettings.enabled != COMPONENT_ENABLED_STATE_DISABLED)) - && !packageSettings.disabledComponents.contains(componentInfo.name)) - || packageSettings.enabledComponents.contains(componentInfo.name)); + if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED + || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled + && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + return false; + } + if (packageSettings.enabledComponents.contains(componentInfo.name)) { + return true; + } + if (packageSettings.disabledComponents.contains(componentInfo.name)) { + return false; + } + return componentInfo.enabled; } } @@ -9455,48 +9567,28 @@ class PackageManagerService extends IPackageManager.Stub { } } - static String getTempContainerId() { - String prefix = "smdl2tmp"; - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - int idx = 0; - int idList[] = new int[MAX_CONTAINERS]; - boolean neverFound = true; - for (String name : list) { - // Ignore null entries - if (name == null) { - continue; - } - int sidx = name.indexOf(prefix); - if (sidx == -1) { - // Not a temp file. just ignore - continue; - } - String subStr = name.substring(sidx + prefix.length()); - idList[idx] = -1; - if (subStr != null) { - try { - int cid = Integer.parseInt(subStr); - idList[idx++] = cid; - neverFound = false; - } catch (NumberFormatException e) { - } - } - } - if (!neverFound) { - // Sort idList - Arrays.sort(idList); - for (int j = 1; j <= idList.length; j++) { - if (idList[j-1] != j) { - tmpIdx = j; - break; - } - } - } - } - return prefix + tmpIdx; - } + /* package */ static String getTempContainerId() { + int tmpIdx = 1; + String list[] = PackageHelper.getSecureContainerList(); + if (list != null) { + for (final String name : list) { + // Ignore null and non-temporary container entries + if (name == null || !name.startsWith(mTempContainerPrefix)) { + continue; + } + + String subStr = name.substring(mTempContainerPrefix.length()); + try { + int cid = Integer.parseInt(subStr); + if (cid >= tmpIdx) { + tmpIdx = cid + 1; + } + } catch (NumberFormatException e) { + } + } + } + return mTempContainerPrefix + tmpIdx; + } /* * Update media status on PackageManager. @@ -9711,10 +9803,15 @@ class PackageManagerService extends IPackageManager.Stub { if (doGc) { Runtime.getRuntime().gc(); } - // List stale containers. + // List stale containers and destroy stale temporary containers. if (removeCids != null) { for (String cid : removeCids) { - Log.w(TAG, "Container " + cid + " is stale"); + if (cid.startsWith(mTempContainerPrefix)) { + Log.i(TAG, "Destroying stale temporary container " + cid); + PackageHelper.destroySdDir(cid); + } else { + Log.w(TAG, "Container " + cid + " is stale"); + } } } } @@ -9804,6 +9901,9 @@ class PackageManagerService extends IPackageManager.Stub { (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0) { Slog.w(TAG, "Cannot move forward locked app."); returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; + } else if (pkg.mOperationPending) { + Slog.w(TAG, "Attempt to move package which has pending operations"); + returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; } else { // Find install location first if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 && @@ -9820,6 +9920,9 @@ class PackageManagerService extends IPackageManager.Stub { returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; } } + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + pkg.mOperationPending = true; + } } } if (returnCode != PackageManager.MOVE_SUCCEEDED) { @@ -9932,6 +10035,18 @@ class PackageManagerService extends IPackageManager.Stub { mp.srcArgs.doPostDeleteLI(true); } } + + // Allow more operations on this file if we didn't fail because + // an operation was already pending for this package. + if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) { + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + if (pkg != null) { + pkg.mOperationPending = false; + } + } + } + IPackageMoveObserver observer = mp.observer; if (observer != null) { try { diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 493a348..2fb481c 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -247,6 +247,9 @@ class PowerManagerService extends IPowerManager.Stub private static final boolean mSpew = false; private static final boolean mDebugProximitySensor = (true || mSpew); private static final boolean mDebugLightSensor = (false || mSpew); + + private native void nativeInit(); + private native void nativeSetPowerState(boolean screenOn, boolean screenBright); /* static PrintStream mLog; @@ -481,6 +484,11 @@ class PowerManagerService extends IPowerManager.Stub } } } + + nativeInit(); + synchronized (mLocks) { + updateNativePowerStateLocked(); + } } void initInThread() { @@ -1022,36 +1030,71 @@ class PowerManagerService extends IPowerManager.Stub } } - private void setTimeoutLocked(long now, int nextState) - { + private void setTimeoutLocked(long now, int nextState) { + setTimeoutLocked(now, -1, nextState); + } + + // If they gave a timeoutOverride it is the number of seconds + // to screen-off. Figure out where in the countdown cycle we + // should jump to. + private void setTimeoutLocked(long now, long timeoutOverride, int nextState) { if (mBootCompleted) { - mHandler.removeCallbacks(mTimeoutTask); - mTimeoutTask.nextState = nextState; - long when = now; - switch (nextState) - { - case SCREEN_BRIGHT: - when += mKeylightDelay; - break; - case SCREEN_DIM: - if (mDimDelay >= 0) { - when += mDimDelay; - break; - } else { - Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); + synchronized (mLocks) { + mHandler.removeCallbacks(mTimeoutTask); + mTimeoutTask.nextState = nextState; + long when = 0; + if (timeoutOverride <= 0) { + switch (nextState) + { + case SCREEN_BRIGHT: + when = now + mKeylightDelay; + break; + case SCREEN_DIM: + if (mDimDelay >= 0) { + when = now + mDimDelay; + break; + } else { + Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); + } + case SCREEN_OFF: + synchronized (mLocks) { + when = now + mScreenOffDelay; + } + break; + default: + when = now; + break; } - case SCREEN_OFF: - synchronized (mLocks) { - when += mScreenOffDelay; + } else { + override: { + if (timeoutOverride <= mScreenOffDelay) { + when = now + timeoutOverride; + nextState = SCREEN_OFF; + break override; + } + timeoutOverride -= mScreenOffDelay; + + if (mDimDelay >= 0) { + if (timeoutOverride <= mDimDelay) { + when = now + timeoutOverride; + nextState = SCREEN_DIM; + break override; + } + timeoutOverride -= mDimDelay; + } + + when = now + timeoutOverride; + nextState = SCREEN_BRIGHT; } - break; - } - if (mSpew) { - Slog.d(TAG, "setTimeoutLocked now=" + now + " nextState=" + nextState - + " when=" + when); + } + if (mSpew) { + Slog.d(TAG, "setTimeoutLocked now=" + now + + " timeoutOverride=" + timeoutOverride + + " nextState=" + nextState + " when=" + when); + } + mHandler.postAtTime(mTimeoutTask, when); + mNextTimeout = when; // for debugging } - mHandler.postAtTime(mTimeoutTask, when); - mNextTimeout = when; // for debugging } } @@ -1557,8 +1600,16 @@ class PowerManagerService extends IPowerManager.Stub } } } + + updateNativePowerStateLocked(); } } + + private void updateNativePowerStateLocked() { + nativeSetPowerState( + (mPowerState & SCREEN_ON_BIT) != 0, + (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT); + } private int screenOffFinishedAnimatingLocked(int reason) { // I don't think we need to check the current state here because all of these @@ -1958,18 +2009,33 @@ class PowerManagerService extends IPowerManager.Stub public void userActivityWithForce(long time, boolean noChangeLights, boolean force) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - userActivity(time, noChangeLights, OTHER_EVENT, force); + userActivity(time, -1, noChangeLights, OTHER_EVENT, force); } public void userActivity(long time, boolean noChangeLights) { - userActivity(time, noChangeLights, OTHER_EVENT, false); + userActivity(time, -1, noChangeLights, OTHER_EVENT, false); } public void userActivity(long time, boolean noChangeLights, int eventType) { - userActivity(time, noChangeLights, eventType, false); + userActivity(time, -1, noChangeLights, eventType, false); } public void userActivity(long time, boolean noChangeLights, int eventType, boolean force) { + userActivity(time, -1, noChangeLights, eventType, force); + } + + /* + * Reset the user activity timeout to now + timeout. This overrides whatever else is going + * on with user activity. Don't use this function. + */ + public void clearUserActivityTimeout(long now, long timeout) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + Slog.i(TAG, "clearUserActivity for " + timeout + "ms from now"); + userActivity(now, timeout, false, OTHER_EVENT, false); + } + + private void userActivity(long time, long timeoutOverride, boolean noChangeLights, + int eventType, boolean force) { //mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0) @@ -2041,7 +2107,7 @@ class PowerManagerService extends IPowerManager.Stub mWakeLockState = mLocks.reactivateScreenLocksLocked(); setPowerState(mUserState | mWakeLockState, noChangeLights, WindowManagerPolicy.OFF_BECAUSE_OF_USER); - setTimeoutLocked(time, SCREEN_BRIGHT); + setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT); } } } diff --git a/services/java/com/android/server/SensorService.java b/services/java/com/android/server/SensorService.java deleted file mode 100644 index 9f5718f..0000000 --- a/services/java/com/android/server/SensorService.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.hardware.ISensorService; -import android.os.Binder; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.IBinder; -import android.util.Config; -import android.util.Slog; -import android.util.PrintWriterPrinter; -import android.util.Printer; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; - -import com.android.internal.app.IBatteryStats; -import com.android.server.am.BatteryStatsService; - - -/** - * Class that manages the device's sensors. It register clients and activate - * the needed sensors. The sensor events themselves are not broadcasted from - * this service, instead, a file descriptor is provided to each client they - * can read events from. - */ - -class SensorService extends ISensorService.Stub { - static final String TAG = SensorService.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; - private static final int SENSOR_DISABLE = -1; - private int mCurrentDelay = 0; - - /** - * Battery statistics to be updated when sensors are enabled and disabled. - */ - final IBatteryStats mBatteryStats = BatteryStatsService.getService(); - - private final class Listener implements IBinder.DeathRecipient { - final IBinder mToken; - final int mUid; - - int mSensors = 0; - int mDelay = 0x7FFFFFFF; - - Listener(IBinder token, int uid) { - mToken = token; - mUid = uid; - } - - void addSensor(int sensor, int delay) { - mSensors |= (1<<sensor); - if (delay < mDelay) - mDelay = delay; - } - - void removeSensor(int sensor) { - mSensors &= ~(1<<sensor); - } - - boolean hasSensor(int sensor) { - return ((mSensors & (1<<sensor)) != 0); - } - - public void binderDied() { - if (localLOGV) Slog.d(TAG, "sensor listener died"); - synchronized(mListeners) { - mListeners.remove(this); - mToken.unlinkToDeath(this, 0); - // go through the lists of sensors used by the listener that - // died and deactivate them. - for (int sensor=0 ; sensor<32 && mSensors!=0 ; sensor++) { - if (hasSensor(sensor)) { - removeSensor(sensor); - deactivateIfUnusedLocked(sensor); - try { - mBatteryStats.noteStopSensor(mUid, sensor); - } catch (RemoteException e) { - // oops. not a big deal. - } - } - } - if (mListeners.size() == 0) { - _sensors_control_wake(); - _sensors_control_close(); - } else { - // TODO: we should recalculate the delay, since removing - // a listener may increase the overall rate. - } - mListeners.notify(); - } - } - } - - @SuppressWarnings("unused") - public SensorService(Context context) { - if (localLOGV) Slog.d(TAG, "SensorService startup"); - _sensors_control_init(); - } - - public Bundle getDataChannel() throws RemoteException { - // synchronize so we do not require sensor HAL to be thread-safe. - synchronized(mListeners) { - return _sensors_control_open(); - } - } - - public boolean enableSensor(IBinder binder, String name, int sensor, int enable) - throws RemoteException { - - if (localLOGV) Slog.d(TAG, "enableSensor " + name + "(#" + sensor + ") " + enable); - - if (binder == null) { - Slog.e(TAG, "listener is null (sensor=" + name + ", id=" + sensor + ")"); - return false; - } - - if (enable < 0 && (enable != SENSOR_DISABLE)) { - Slog.e(TAG, "invalid enable parameter (enable=" + enable + - ", sensor=" + name + ", id=" + sensor + ")"); - return false; - } - - boolean res; - int uid = Binder.getCallingUid(); - synchronized(mListeners) { - res = enableSensorInternalLocked(binder, uid, name, sensor, enable); - if (res == true) { - // Inform battery statistics service of status change - long identity = Binder.clearCallingIdentity(); - if (enable == SENSOR_DISABLE) { - mBatteryStats.noteStopSensor(uid, sensor); - } else { - mBatteryStats.noteStartSensor(uid, sensor); - } - Binder.restoreCallingIdentity(identity); - } - } - return res; - } - - private boolean enableSensorInternalLocked(IBinder binder, int uid, - String name, int sensor, int enable) throws RemoteException { - - // check if we have this listener - Listener l = null; - for (Listener listener : mListeners) { - if (binder == listener.mToken) { - l = listener; - break; - } - } - - if (enable != SENSOR_DISABLE) { - // Activate the requested sensor - if (_sensors_control_activate(sensor, true) == false) { - Slog.w(TAG, "could not enable sensor " + sensor); - return false; - } - - if (l == null) { - /* - * we don't have a listener for this binder yet, so - * create a new one and add it to the list. - */ - l = new Listener(binder, uid); - binder.linkToDeath(l, 0); - mListeners.add(l); - mListeners.notify(); - } - - // take note that this sensor is now used by this client - l.addSensor(sensor, enable); - - } else { - - if (l == null) { - /* - * This client isn't in the list, this usually happens - * when enabling the sensor failed, but the client - * didn't handle the error and later tries to shut that - * sensor off. - */ - Slog.w(TAG, "listener with binder " + binder + - ", doesn't exist (sensor=" + name + - ", id=" + sensor + ")"); - return false; - } - - // remove this sensor from this client - l.removeSensor(sensor); - - // see if we need to deactivate this sensors= - deactivateIfUnusedLocked(sensor); - - // if the listener doesn't have any more sensors active - // we can get rid of it - if (l.mSensors == 0) { - // we won't need this death notification anymore - binder.unlinkToDeath(l, 0); - // remove the listener from the list - mListeners.remove(l); - // and if the list is empty, turn off the whole sensor h/w - if (mListeners.size() == 0) { - _sensors_control_wake(); - _sensors_control_close(); - } - mListeners.notify(); - } - } - - // calculate and set the new delay - int minDelay = 0x7FFFFFFF; - for (Listener listener : mListeners) { - if (listener.mDelay < minDelay) - minDelay = listener.mDelay; - } - if (minDelay != 0x7FFFFFFF) { - mCurrentDelay = minDelay; - _sensors_control_set_delay(minDelay); - } - - return true; - } - - private void deactivateIfUnusedLocked(int sensor) { - int size = mListeners.size(); - for (int i=0 ; i<size ; i++) { - if (mListeners.get(i).hasSensor(sensor)) { - // this sensor is still in use, don't turn it off - return; - } - } - if (_sensors_control_activate(sensor, false) == false) { - Slog.w(TAG, "could not disable sensor " + sensor); - } - } - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - synchronized (mListeners) { - Printer pr = new PrintWriterPrinter(pw); - int c = 0; - pr.println(mListeners.size() + " listener(s), delay=" + mCurrentDelay + " ms"); - for (Listener l : mListeners) { - pr.println("listener[" + c + "] " + - "sensors=0x" + Integer.toString(l.mSensors, 16) + - ", uid=" + l.mUid + - ", delay=" + - l.mDelay + " ms"); - c++; - } - } - } - - private ArrayList<Listener> mListeners = new ArrayList<Listener>(); - - private static native int _sensors_control_init(); - private static native Bundle _sensors_control_open(); - private static native int _sensors_control_close(); - private static native boolean _sensors_control_activate(int sensor, boolean activate); - private static native int _sensors_control_set_delay(int ms); - private static native int _sensors_control_wake(); -} diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java new file mode 100644 index 0000000..4177432 --- /dev/null +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2007 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.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.Binder; +import android.os.Handler; +import android.os.SystemClock; +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.internal.statusbar.StatusBarNotification; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * A note on locking: We rely on the fact that calls onto mBar are oneway or + * if they are local, that they just enqueue messages to not deadlock. + */ +public class StatusBarManagerService extends IStatusBarService.Stub +{ + static final String TAG = "StatusBarManagerService"; + static final boolean SPEW = true; + + final Context mContext; + Handler mHandler = new Handler(); + NotificationCallbacks mNotificationCallbacks; + volatile IStatusBar mBar; + StatusBarIconList mIcons = new StatusBarIconList(); + HashMap<IBinder,StatusBarNotification> mNotifications + = new HashMap<IBinder,StatusBarNotification>(); + + // for disabling the status bar + ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); + int mDisabled = 0; + + private class DisableRecord implements IBinder.DeathRecipient { + String pkg; + int what; + IBinder token; + + public void binderDied() { + Slog.i(TAG, "binder died for pkg=" + pkg); + disable(0, token, pkg); + token.unlinkToDeath(this, 0); + } + } + + public interface NotificationCallbacks { + void onSetDisabled(int status); + void onClearAll(); + void onNotificationClick(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 + */ + public StatusBarManagerService(Context context) { + mContext = context; + + final Resources res = context.getResources(); + mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons)); + } + + public void setNotificationCallbacks(NotificationCallbacks listener) { + mNotificationCallbacks = listener; + } + + // ================================================================================ + // Constructing the view + // ================================================================================ + + public void systemReady() { + } + + public void systemReady2() { + ComponentName cn = ComponentName.unflattenFromString( + mContext.getString(com.android.internal.R.string.config_statusBarComponent)); + Intent intent = new Intent(); + intent.setComponent(cn); + Slog.i(TAG, "Starting service: " + cn); + mContext.startService(intent); + } + + // ================================================================================ + // From IStatusBarService + // ================================================================================ + public void expand() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateExpand(); + } catch (RemoteException ex) { + } + } + } + + public void collapse() { + enforceExpandStatusBar(); + + if (mBar != null) { + try { + mBar.animateCollapse(); + } catch (RemoteException ex) { + } + } + } + + public void disable(int what, IBinder token, String pkg) { + enforceStatusBar(); + + // It's important that the the callback and the call to mBar get done + // in the same order when multiple threads are calling this function + // so they are paired correctly. The messages on the handler will be + // handled in the order they were enqueued, but will be outside the lock. + synchronized (mDisableRecords) { + manageDisableListLocked(what, token, pkg); + final int net = gatherDisableActionsLocked(); + Slog.d(TAG, "disable... net=0x" + Integer.toHexString(net)); + if (net != mDisabled) { + mDisabled = net; + mHandler.post(new Runnable() { + public void run() { + mNotificationCallbacks.onSetDisabled(net); + } + }); + if (mBar != null) { + try { + mBar.disable(net); + } catch (RemoteException ex) { + } + } + } + } + } + + public void setIcon(String slot, String iconPackage, int iconId, int iconLevel) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + StatusBarIcon icon = new StatusBarIcon(iconPackage, iconId, iconLevel); + //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon); + mIcons.setIcon(index, icon); + + if (mBar != null) { + try { + mBar.setIcon(index, icon); + } catch (RemoteException ex) { + } + } + } + } + + public void setIconVisibility(String slot, boolean visible) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + StatusBarIcon icon = mIcons.getIcon(index); + if (icon == null) { + return; + } + + if (icon.visible != visible) { + icon.visible = visible; + + if (mBar != null) { + try { + mBar.setIcon(index, icon); + } catch (RemoteException ex) { + } + } + } + } + } + + public void removeIcon(String slot) { + enforceStatusBar(); + + synchronized (mIcons) { + int index = mIcons.getSlotIndex(slot); + if (index < 0) { + throw new SecurityException("invalid status bar icon slot: " + slot); + } + + mIcons.removeIcon(index); + + if (mBar != null) { + try { + mBar.removeIcon(index); + } catch (RemoteException ex) { + } + } + } + } + + private void enforceStatusBar() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, + "StatusBarManagerService"); + } + + private void enforceExpandStatusBar() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR, + "StatusBarManagerService"); + } + + private void enforceStatusBarService() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + "StatusBarManagerService"); + } + + + // ================================================================================ + // Callbacks from the status bar service. + // ================================================================================ + public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, + List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + enforceStatusBarService(); + + Slog.i(TAG, "registerStatusBar bar=" + bar); + mBar = bar; + synchronized (mIcons) { + iconList.copyFrom(mIcons); + } + synchronized (mNotifications) { + for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) { + notificationKeys.add(e.getKey()); + notifications.add(e.getValue()); + } + } + } + + /** + * 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. + */ + public void onPanelRevealed() { + enforceStatusBarService(); + + // tell the notification manager to turn off the lights. + mNotificationCallbacks.onPanelRevealed(); + } + + public void onNotificationClick(String pkg, String tag, int id) { + enforceStatusBarService(); + + mNotificationCallbacks.onNotificationClick(pkg, tag, id); + } + + 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); + } + + 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; + } + } + + 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) { + throw new IllegalArgumentException("removeNotification key not found: " + key); + } + if (mBar != null) { + try { + mBar.removeNotification(key); + } catch (RemoteException ex) { + } + } + } + } + + // ================================================================================ + // Can be called from any thread + // ================================================================================ + + // lock on mDisableRecords + void manageDisableListLocked(int what, IBinder token, String pkg) { + if (SPEW) { + Slog.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) + " pkg=" + pkg); + } + // update the list + synchronized (mDisableRecords) { + final int N = mDisableRecords.size(); + DisableRecord tok = null; + int i; + for (i=0; i<N; i++) { + DisableRecord t = mDisableRecords.get(i); + if (t.token == token) { + tok = t; + break; + } + } + if (what == 0 || !token.isBinderAlive()) { + if (tok != null) { + mDisableRecords.remove(i); + tok.token.unlinkToDeath(tok, 0); + } + } else { + if (tok == null) { + tok = new DisableRecord(); + try { + token.linkToDeath(tok, 0); + } + catch (RemoteException ex) { + return; // give up + } + mDisableRecords.add(tok); + } + tok.what = what; + tok.token = token; + tok.pkg = pkg; + } + } + } + + // lock on mDisableRecords + int gatherDisableActionsLocked() { + final int N = mDisableRecords.size(); + // gather the new net flags + int net = 0; + for (int i=0; i<N; i++) { + net |= mDisableRecords.get(i).what; + } + return net; + } + + // ================================================================================ + // Always called from UI thread + // ================================================================================ + + 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 StatusBar from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mIcons) { + mIcons.dump(pw); + } + + synchronized (mNotifications) { + int i=0; + pw.println("Notification list:"); + for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) { + pw.printf(" %2d: %s\n", i, e.getValue().toString()); + i++; + } + } + + synchronized (mDisableRecords) { + final int N = mDisableRecords.size(); + pw.println(" mDisableRecords.size=" + N + + " mDisabled=0x" + Integer.toHexString(mDisabled)); + for (int i=0; i<N; i++) { + DisableRecord tok = mDisableRecords.get(i); + pw.println(" [" + i + "] what=0x" + Integer.toHexString(tok.what) + + " pkg=" + tok.pkg + " token=" + tok.token); + } + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + collapse(); + } + /* + else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { + updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), + intent.getStringExtra(Telephony.Intents.EXTRA_SPN), + intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), + intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); + } + else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { + updateResources(); + } + */ + } + }; + +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9d5d035..1a209e2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -17,7 +17,7 @@ package com.android.server; import com.android.server.am.ActivityManagerService; -import com.android.server.status.StatusBarService; +import com.android.server.sip.SipService; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; @@ -81,7 +81,8 @@ class ServerThread extends Thread { android.os.Process.THREAD_PRIORITY_FOREGROUND); BinderInternal.disableBackgroundScheduling(true); - + android.os.Process.setCanSelfBackground(false); + String factoryTestStr = SystemProperties.get("ro.factorytest"); int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF : Integer.parseInt(factoryTestStr); @@ -97,6 +98,7 @@ class ServerThread extends Thread { BluetoothA2dpService bluetoothA2dp = null; HeadsetObserver headset = null; DockObserver dock = null; + UsbObserver usb = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; @@ -164,10 +166,6 @@ class ServerThread extends Thread { Watchdog.getInstance().init(context, battery, power, alarm, ActivityManagerService.self()); - // Sensor Service is needed by Window Manager, so this goes first - Slog.i(TAG, "Sensor Service"); - ServiceManager.addService(Context.SENSOR_SERVICE, new SensorService(context)); - Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, power, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL); @@ -206,7 +204,7 @@ class ServerThread extends Thread { } DevicePolicyManagerService devicePolicy = null; - StatusBarService statusBar = null; + StatusBarManagerService statusBar = null; InputMethodManagerService imm = null; AppWidgetService appWidget = null; NotificationManagerService notification = null; @@ -224,10 +222,10 @@ class ServerThread extends Thread { try { Slog.i(TAG, "Status Bar"); - statusBar = new StatusBarService(context); + statusBar = new StatusBarManagerService(context); ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); } catch (Throwable e) { - Slog.e(TAG, "Failure starting StatusBarService", e); + Slog.e(TAG, "Failure starting StatusBarManagerService", e); } try { @@ -374,8 +372,16 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "USB Observer"); + // Listen for USB changes + usb = new UsbObserver(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting UsbObserver", e); + } + + try { Slog.i(TAG, "UI Mode Manager Service"); - // Listen for dock station changes + // Listen for UI mode changes uiMode = new UiModeManagerService(context); } catch (Throwable e) { Slog.e(TAG, "Failure starting UiModeManagerService", e); @@ -405,14 +411,15 @@ class ServerThread extends Thread { } try { - com.android.server.status.StatusBarPolicy.installIcons(context, statusBar); + Slog.i(TAG, "DiskStats Service"); + ServiceManager.addService("diskstats", new DiskStatsService(context)); } catch (Throwable e) { - Slog.e(TAG, "Failure installing status bar icons", e); + Slog.e(TAG, "Failure starting DiskStats Service", e); } try { - Slog.i(TAG, "DiskStats Service"); - ServiceManager.addService("diskstats", new DiskStatsService(context)); + Slog.i(TAG, "Sip Service"); + ServiceManager.addService("sip", new SipService(context)); } catch (Throwable e) { Slog.e(TAG, "Failure starting DiskStats Service", e); } @@ -464,9 +471,11 @@ class ServerThread extends Thread { } // These are needed to propagate to the runnable below. + final StatusBarManagerService statusBarF = statusBar; final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; final DockObserver dockF = dock; + final UsbObserver usbF = usb; final ThrottleService throttleF = throttle; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; @@ -485,9 +494,11 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); + if (statusBarF != null) statusBarF.systemReady2(); if (batteryF != null) batteryF.systemReady(); if (connectivityF != null) connectivityF.systemReady(); if (dockF != null) dockF.systemReady(); + if (usbF != null) usbF.systemReady(); if (uiModeF != null) uiModeF.systemReady(); if (recognitionF != null) recognitionF.systemReady(); Watchdog.getInstance().start(); diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index 019245f..431cc39 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -566,7 +566,7 @@ class UiModeManagerService extends IUiModeManager.Stub { mStatusBarManager = (StatusBarManager) mContext.getSystemService(Context.STATUS_BAR_SERVICE); } - // Fear not: StatusBarService manages a list of requests to disable + // Fear not: StatusBarManagerService manages a list of requests to disable // features of the status bar; these are ORed together to form the // active disabled list. So if (for example) the device is locked and // the status bar should be totally disabled, the calls below will diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java new file mode 100644 index 0000000..d08fe9b --- /dev/null +++ b/services/java/com/android/server/UsbObserver.java @@ -0,0 +1,207 @@ +/* + * 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; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.Usb; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.UEventObserver; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; + +/** + * <p>UsbObserver monitors for changes to USB state. + */ +class UsbObserver extends UEventObserver { + private static final String TAG = UsbObserver.class.getSimpleName(); + private static final boolean LOG = false; + + private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration"; + private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/"; + private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state"; + private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite"; + + private static final int MSG_UPDATE = 0; + + private int mUsbConfig = 0; + private int mPreviousUsbConfig = 0; + + // lists of enabled and disabled USB functions + private final ArrayList<String> mEnabledFunctions = new ArrayList<String>(); + private final ArrayList<String> mDisabledFunctions = new ArrayList<String>(); + + private boolean mSystemReady; + + private final Context mContext; + + private PowerManagerService mPowerManager; + + public UsbObserver(Context context) { + mContext = context; + init(); // set initial status + + startObserving(USB_CONFIGURATION_MATCH); + startObserving(USB_FUNCTIONS_MATCH); + } + + @Override + public void onUEvent(UEventObserver.UEvent event) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Slog.v(TAG, "USB UEVENT: " + event.toString()); + } + + synchronized (this) { + String switchState = event.get("SWITCH_STATE"); + if (switchState != null) { + try { + int newConfig = Integer.parseInt(switchState); + if (newConfig != mUsbConfig) { + mPreviousUsbConfig = mUsbConfig; + mUsbConfig = newConfig; + // trigger an Intent broadcast + if (mSystemReady) { + update(); + } + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse switch state from event " + event); + } + } else { + String function = event.get("FUNCTION"); + String enabledStr = event.get("ENABLED"); + if (function != null && enabledStr != null) { + // Note: we do not broadcast a change when a function is enabled or disabled. + // We just record the state change for the next broadcast. + boolean enabled = "1".equals(enabledStr); + if (enabled) { + if (!mEnabledFunctions.contains(function)) { + mEnabledFunctions.add(function); + } + mDisabledFunctions.remove(function); + } else { + if (!mDisabledFunctions.contains(function)) { + mDisabledFunctions.add(function); + } + mEnabledFunctions.remove(function); + } + } + } + } + } + private final void init() { + char[] buffer = new char[1024]; + + try { + FileReader file = new FileReader(USB_CONFIGURATION_PATH); + int len = file.read(buffer, 0, 1024); + mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim()); + + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB configuration switch support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + + try { + File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles(); + for (int i = 0; i < files.length; i++) { + File file = new File(files[i], "enable"); + FileReader reader = new FileReader(file); + int len = reader.read(buffer, 0, 1024); + int value = Integer.valueOf((new String(buffer, 0, len)).trim()); + String functionName = files[i].getName(); + if (value == 1) { + mEnabledFunctions.add(functionName); + } else { + mDisabledFunctions.add(functionName); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have USB composite class support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } + } + + void systemReady() { + synchronized (this) { + update(); + mSystemReady = true; + } + } + + private final void update() { + mHandler.sendEmptyMessage(MSG_UPDATE); + } + + private final Handler mHandler = new Handler() { + private void addEnabledFunctions(Intent intent) { + // include state of all USB functions in our extras + for (int i = 0; i < mEnabledFunctions.size(); i++) { + intent.putExtra(mEnabledFunctions.get(i), Usb.USB_FUNCTION_ENABLED); + } + for (int i = 0; i < mDisabledFunctions.size(); i++) { + intent.putExtra(mDisabledFunctions.get(i), Usb.USB_FUNCTION_DISABLED); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE: + synchronized (this) { + final ContentResolver cr = mContext.getContentResolver(); + + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping USB broadcast"); + return; + } + // Send an Intent containing connected/disconnected state + // and the enabled/disabled state of all USB functions + Intent intent; + boolean usbConnected = (mUsbConfig != 0); + if (usbConnected) { + intent = new Intent(Usb.ACTION_USB_CONNECTED); + addEnabledFunctions(intent); + } else { + intent = new Intent(Usb.ACTION_USB_DISCONNECTED); + } + mContext.sendBroadcast(intent); + + // send a sticky broadcast for clients interested in both connect and disconnect + intent = new Intent(Usb.ACTION_USB_STATE); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Usb.USB_CONNECTED, usbConnected); + addEnabledFunctions(intent); + mContext.sendStickyBroadcast(intent); + } + break; + } + } + }; +} diff --git a/services/java/com/android/server/ViewServer.java b/services/java/com/android/server/ViewServer.java index ae00438..7b5d18a 100644 --- a/services/java/com/android/server/ViewServer.java +++ b/services/java/com/android/server/ViewServer.java @@ -21,6 +21,8 @@ import android.util.Slog; import java.net.ServerSocket; import java.net.Socket; import java.net.InetAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -41,11 +43,13 @@ class ViewServer implements Runnable { */ public static final int VIEW_SERVER_DEFAULT_PORT = 4939; + private static final int VIEW_SERVER_MAX_CONNECTIONS = 10; + // Debug facility private static final String LOG_TAG = "ViewServer"; - private static final String VALUE_PROTOCOL_VERSION = "2"; - private static final String VALUE_SERVER_VERSION = "3"; + private static final String VALUE_PROTOCOL_VERSION = "3"; + private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands // Returns the protocol version @@ -54,6 +58,10 @@ class ViewServer implements Runnable { private static final String COMMAND_SERVER_VERSION = "SERVER"; // Lists all of the available windows in the system private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST"; + // Keeps a connection open and notifies when the list of windows changes + private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST"; + // Returns the focused window + private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; private ServerSocket mServer; private Thread mThread; @@ -61,6 +69,8 @@ class ViewServer implements Runnable { private final WindowManagerService mWindowManager; private final int mPort; + private ExecutorService mThreadPool; + /** * Creates a new ViewServer associated with the specified window manager. * The server uses the default port {@link #VIEW_SERVER_DEFAULT_PORT}. The server @@ -103,8 +113,9 @@ class ViewServer implements Runnable { return false; } - mServer = new ServerSocket(mPort, 1, InetAddress.getLocalHost()); + mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost()); mThread = new Thread(this, "Remote View Server [port=" + mPort + "]"); + mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS); mThread.start(); return true; @@ -122,7 +133,16 @@ class ViewServer implements Runnable { */ boolean stop() { if (mThread != null) { + mThread.interrupt(); + if (mThreadPool != null) { + try { + mThreadPool.shutdownNow(); + } catch (SecurityException e) { + Slog.w(LOG_TAG, "Could not stop all view server threads"); + } + } + mThreadPool = null; mThread = null; try { mServer.close(); @@ -152,62 +172,21 @@ class ViewServer implements Runnable { * Main server loop. */ public void run() { - final ServerSocket server = mServer; - while (Thread.currentThread() == mThread) { - Socket client = null; // Any uncaught exception will crash the system process try { - client = server.accept(); - - BufferedReader in = null; - try { - in = new BufferedReader(new InputStreamReader(client.getInputStream()), 1024); - - final String request = in.readLine(); - - String command; - String parameters; - - int index = request.indexOf(' '); - if (index == -1) { - command = request; - parameters = ""; - } else { - command = request.substring(0, index); - parameters = request.substring(index + 1); - } - - boolean result; - if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) { - result = writeValue(client, VALUE_PROTOCOL_VERSION); - } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) { - result = writeValue(client, VALUE_SERVER_VERSION); - } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { - result = mWindowManager.viewServerListWindows(client); - } else { - result = mWindowManager.viewServerWindowCommand(client, - command, parameters); - } - - if (!result) { - Slog.w(LOG_TAG, "An error occured with the command: " + command); - } - } finally { - if (in != null) { - in.close(); - } - } - } catch (Exception e) { - Slog.w(LOG_TAG, "Connection error: ", e); - } finally { - if (client != null) { + Socket client = mServer.accept(); + if(mThreadPool != null) { + mThreadPool.submit(new ViewServerWorker(client)); + } else { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } + } catch (Exception e) { + Slog.w(LOG_TAG, "Connection error: ", e); } } } @@ -235,4 +214,133 @@ class ViewServer implements Runnable { } return result; } + + class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener { + private Socket mClient; + private boolean mNeedWindowListUpdate; + private boolean mNeedFocusedWindowUpdate; + public ViewServerWorker(Socket client) { + mClient = client; + mNeedWindowListUpdate = false; + mNeedFocusedWindowUpdate = false; + } + + public void run() { + + BufferedReader in = null; + try { + in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024); + + final String request = in.readLine(); + + String command; + String parameters; + + int index = request.indexOf(' '); + if (index == -1) { + command = request; + parameters = ""; + } else { + command = request.substring(0, index); + parameters = request.substring(index + 1); + } + + boolean result; + if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) { + result = writeValue(mClient, VALUE_PROTOCOL_VERSION); + } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) { + result = writeValue(mClient, VALUE_SERVER_VERSION); + } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { + result = mWindowManager.viewServerListWindows(mClient); + } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) { + result = mWindowManager.viewServerGetFocusedWindow(mClient); + } else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { + result = windowManagerAutolistLoop(); + } else { + result = mWindowManager.viewServerWindowCommand(mClient, + command, parameters); + } + + if (!result) { + Slog.w(LOG_TAG, "An error occured with the command: " + command); + } + } catch(IOException e) { + Slog.w(LOG_TAG, "Connection error: ", e); + } finally { + if (in != null) { + try { + in.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + if (mClient != null) { + try { + mClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public void windowsChanged() { + synchronized(this) { + mNeedWindowListUpdate = true; + notifyAll(); + } + } + + public void focusChanged() { + synchronized(this) { + mNeedFocusedWindowUpdate = true; + notifyAll(); + } + } + + private boolean windowManagerAutolistLoop() { + mWindowManager.addWindowChangeListener(this); + BufferedWriter out = null; + try { + out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream())); + while (!Thread.interrupted()) { + boolean needWindowListUpdate = false; + boolean needFocusedWindowUpdate = false; + synchronized (this) { + while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) { + wait(); + } + if (mNeedWindowListUpdate) { + mNeedWindowListUpdate = false; + needWindowListUpdate = true; + } + if (mNeedFocusedWindowUpdate) { + mNeedFocusedWindowUpdate = false; + needFocusedWindowUpdate = true; + } + } + if(needWindowListUpdate) { + out.write("LIST UPDATE\n"); + out.flush(); + } + if(needFocusedWindowUpdate) { + out.write("FOCUS UPDATE\n"); + out.flush(); + } + } + } catch (Exception e) { + Slog.w(LOG_TAG, "Connection error: ", e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + } + } + mWindowManager.removeWindowChangeListener(this); + } + return true; + } + } } diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index d4133f3..a742093 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -57,20 +57,10 @@ public class Watchdog extends Thread { static final boolean RECORD_KERNEL_THREADS = true; static final int MONITOR = 2718; - static final int GLOBAL_PSS = 2719; static final int TIME_TO_RESTART = DB ? 15*1000 : 60*1000; static final int TIME_TO_WAIT = TIME_TO_RESTART / 2; - static final int MEMCHECK_DEFAULT_INTERVAL = DB ? 30 : 30*60; // 30 minutes - static final int MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL = DB ? 60 : 2*60*60; // 2 hours - static final int MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD = (DB ? 10:16)*1024*1024; // 16MB - static final int MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD = (DB ? 14:20)*1024*1024; // 20MB - static final int MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD = (DB ? 4:8)*1024*1024; // 8MB - static final int MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD = (DB ? 8:12)*1024*1024; // 12MB - - static final int MEMCHECK_DEFAULT_EXEC_START_TIME = 1*60*60; // 1:00am - static final int MEMCHECK_DEFAULT_EXEC_END_TIME = 5*60*60; // 5:00am static final int MEMCHECK_DEFAULT_MIN_SCREEN_OFF = DB ? 1*60 : 5*60; // 5 minutes static final int MEMCHECK_DEFAULT_MIN_ALARM = DB ? 1*60 : 3*60; // 3 minutes static final int MEMCHECK_DEFAULT_RECHECK_INTERVAL = DB ? 1*60 : 5*60; // 5 minutes @@ -79,14 +69,12 @@ public class Watchdog extends Thread { static final int REBOOT_DEFAULT_START_TIME = 3*60*60; // 3:00am static final int REBOOT_DEFAULT_WINDOW = 60*60; // within 1 hour - static final String CHECKUP_ACTION = "com.android.service.Watchdog.CHECKUP"; static final String REBOOT_ACTION = "com.android.service.Watchdog.REBOOT"; static Watchdog sWatchdog; /* This handler will be used to post message back onto the main thread */ final Handler mHandler; - final Runnable mGlobalPssCollected; final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); ContentResolver mResolver; BatteryService mBattery; @@ -97,31 +85,9 @@ public class Watchdog extends Thread { boolean mForceKillSystem; Monitor mCurrentMonitor; - PssRequestor mPhoneReq; int mPhonePid; - int mPhonePss; - - long mLastMemCheckTime = -(MEMCHECK_DEFAULT_INTERVAL*1000); - boolean mHavePss; - long mLastMemCheckRealtime = -(MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL*1000); - boolean mHaveGlobalPss; - final MemMonitor mSystemMemMonitor = new MemMonitor("system", - Settings.Secure.MEMCHECK_SYSTEM_ENABLED, - Settings.Secure.MEMCHECK_SYSTEM_SOFT_THRESHOLD, - MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD, - Settings.Secure.MEMCHECK_SYSTEM_HARD_THRESHOLD, - MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD); - final MemMonitor mPhoneMemMonitor = new MemMonitor("com.android.phone", - Settings.Secure.MEMCHECK_PHONE_ENABLED, - Settings.Secure.MEMCHECK_PHONE_SOFT_THRESHOLD, - MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD, - Settings.Secure.MEMCHECK_PHONE_HARD_THRESHOLD, - MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD); final Calendar mCalendar = Calendar.getInstance(); - long mMemcheckLastTime; - long mMemcheckExecStartTime; - long mMemcheckExecEndTime; int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF; int mMinAlarm = MEMCHECK_DEFAULT_MIN_ALARM; boolean mNeedScheduledCheck; @@ -140,126 +106,13 @@ public class Watchdog extends Thread { int mReqRecheckInterval= -1; // >= 0 if a specific recheck interval has been requested /** - * This class monitors the memory in a particular process. - */ - final class MemMonitor { - final String mProcessName; - final String mEnabledSetting; - final String mSoftSetting; - final String mHardSetting; - - int mSoftThreshold; - int mHardThreshold; - boolean mEnabled; - long mLastPss; - - static final int STATE_OK = 0; - static final int STATE_SOFT = 1; - static final int STATE_HARD = 2; - int mState; - - MemMonitor(String processName, String enabledSetting, - String softSetting, int defSoftThreshold, - String hardSetting, int defHardThreshold) { - mProcessName = processName; - mEnabledSetting = enabledSetting; - mSoftSetting = softSetting; - mHardSetting = hardSetting; - mSoftThreshold = defSoftThreshold; - mHardThreshold = defHardThreshold; - } - - void retrieveSettings(ContentResolver resolver) { - mSoftThreshold = Settings.Secure.getInt( - resolver, mSoftSetting, mSoftThreshold); - mHardThreshold = Settings.Secure.getInt( - resolver, mHardSetting, mHardThreshold); - mEnabled = Settings.Secure.getInt( - resolver, mEnabledSetting, 0) != 0; - } - - boolean checkLocked(long curTime, int pid, int pss) { - mLastPss = pss; - if (mLastPss < mSoftThreshold) { - mState = STATE_OK; - } else if (mLastPss < mHardThreshold) { - mState = STATE_SOFT; - } else { - mState = STATE_HARD; - } - EventLog.writeEvent(EventLogTags.WATCHDOG_PROC_PSS, mProcessName, pid, mLastPss); - - if (mState == STATE_OK) { - // Memory is good, don't recover. - return false; - } - - if (mState == STATE_HARD) { - // Memory is really bad, kill right now. - EventLog.writeEvent(EventLogTags.WATCHDOG_HARD_RESET, mProcessName, pid, - mHardThreshold, mLastPss); - return mEnabled; - } - - // It is time to schedule a reset... - // Check if we are currently within the time to kill processes due - // to memory use. - computeMemcheckTimesLocked(curTime); - String skipReason = null; - if (curTime < mMemcheckExecStartTime || curTime > mMemcheckExecEndTime) { - skipReason = "time"; - } else { - skipReason = shouldWeBeBrutalLocked(curTime); - } - EventLog.writeEvent(EventLogTags.WATCHDOG_SOFT_RESET, mProcessName, pid, - mSoftThreshold, mLastPss, skipReason != null ? skipReason : ""); - if (skipReason != null) { - mNeedScheduledCheck = true; - return false; - } - return mEnabled; - } - - void clear() { - mLastPss = 0; - mState = STATE_OK; - } - } - - /** * Used for scheduling monitor callbacks and checking memory usage. */ final class HeartbeatHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { - case GLOBAL_PSS: { - if (mHaveGlobalPss) { - // During the last pass we collected pss information, so - // now it is time to report it. - mHaveGlobalPss = false; - if (localLOGV) Slog.v(TAG, "Received global pss, logging."); - logGlobalMemory(); - } - } break; - case MONITOR: { - if (mHavePss) { - // During the last pass we collected pss information, so - // now it is time to report it. - mHavePss = false; - if (localLOGV) Slog.v(TAG, "Have pss, checking memory."); - checkMemory(); - } - - if (mHaveGlobalPss) { - // During the last pass we collected pss information, so - // now it is time to report it. - mHaveGlobalPss = false; - if (localLOGV) Slog.v(TAG, "Have global pss, logging."); - logGlobalMemory(); - } - long now = SystemClock.uptimeMillis(); // See if we should force a reboot. @@ -274,32 +127,6 @@ public class Watchdog extends Thread { checkReboot(false); } - // See if we should check memory conditions. - long memCheckInterval = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_INTERVAL, - MEMCHECK_DEFAULT_INTERVAL) * 1000; - if ((mLastMemCheckTime+memCheckInterval) < now) { - // It is now time to collect pss information. This - // is async so we won't report it now. And to keep - // things simple, we will assume that everyone has - // reported back by the next MONITOR message. - mLastMemCheckTime = now; - if (localLOGV) Slog.v(TAG, "Collecting memory usage."); - collectMemory(); - mHavePss = true; - - long memCheckRealtimeInterval = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_LOG_REALTIME_INTERVAL, - MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL) * 1000; - long realtimeNow = SystemClock.elapsedRealtime(); - if ((mLastMemCheckRealtime+memCheckRealtimeInterval) < realtimeNow) { - mLastMemCheckRealtime = realtimeNow; - if (localLOGV) Slog.v(TAG, "Collecting global memory usage."); - collectGlobalMemory(); - mHaveGlobalPss = true; - } - } - final int size = mMonitors.size(); for (int i = 0 ; i < size ; i++) { mCurrentMonitor = mMonitors.get(i); @@ -315,20 +142,6 @@ public class Watchdog extends Thread { } } - final class GlobalPssCollected implements Runnable { - public void run() { - mHandler.sendEmptyMessage(GLOBAL_PSS); - } - } - - final class CheckupReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context c, Intent intent) { - if (localLOGV) Slog.v(TAG, "Alarm went off, checking memory."); - checkMemory(); - } - } - final class RebootReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { @@ -359,27 +172,6 @@ public class Watchdog extends Thread { void monitor(); } - public interface PssRequestor { - void requestPss(); - } - - public class PssStats { - public int mEmptyPss; - public int mEmptyCount; - public int mBackgroundPss; - public int mBackgroundCount; - public int mServicePss; - public int mServiceCount; - public int mVisiblePss; - public int mVisibleCount; - public int mForegroundPss; - public int mForegroundCount; - - public int mNoPssCount; - - public int mProcDeaths[] = new int[10]; - } - public static Watchdog getInstance() { if (sWatchdog == null) { sWatchdog = new Watchdog(); @@ -391,7 +183,6 @@ public class Watchdog extends Thread { private Watchdog() { super("watchdog"); mHandler = new HeartbeatHandler(); - mGlobalPssCollected = new GlobalPssCollected(); } public void init(Context context, BatteryService battery, @@ -403,11 +194,6 @@ public class Watchdog extends Thread { mAlarm = alarm; mActivity = activity; - context.registerReceiver(new CheckupReceiver(), - new IntentFilter(CHECKUP_ACTION)); - mCheckupIntent = PendingIntent.getBroadcast(context, - 0, new Intent(CHECKUP_ACTION), 0); - context.registerReceiver(new RebootReceiver(), new IntentFilter(REBOOT_ACTION)); mRebootIntent = PendingIntent.getBroadcast(context, @@ -420,20 +206,10 @@ public class Watchdog extends Thread { mBootTime = System.currentTimeMillis(); } - public void processStarted(PssRequestor req, String name, int pid) { + public void processStarted(String name, int pid) { synchronized (this) { if ("com.android.phone".equals(name)) { - mPhoneReq = req; mPhonePid = pid; - mPhonePss = 0; - } - } - } - - public void reportPss(PssRequestor req, String name, int pss) { - synchronized (this) { - if (mPhoneReq == req) { - mPhonePss = pss; } } } @@ -447,152 +223,6 @@ public class Watchdog extends Thread { } } - /** - * Retrieve memory usage information from specific processes being - * monitored. This is an async operation, so must be done before doing - * memory checks. - */ - void collectMemory() { - synchronized (this) { - if (mPhoneReq != null) { - mPhoneReq.requestPss(); - } - } - } - - /** - * Retrieve memory usage over all application processes. This is an - * async operation, so must be done before doing memory checks. - */ - void collectGlobalMemory() { - mActivity.requestPss(mGlobalPssCollected); - } - - /** - * Check memory usage in the system, scheduling kills/reboots as needed. - * This always runs on the mHandler thread. - */ - void checkMemory() { - boolean needScheduledCheck; - long curTime; - long nextTime = 0; - - long recheckInterval = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_RECHECK_INTERVAL, - MEMCHECK_DEFAULT_RECHECK_INTERVAL) * 1000; - - mSystemMemMonitor.retrieveSettings(mResolver); - mPhoneMemMonitor.retrieveSettings(mResolver); - retrieveBrutalityAmount(); - - synchronized (this) { - curTime = System.currentTimeMillis(); - mNeedScheduledCheck = false; - - // How is the system doing? - if (mSystemMemMonitor.checkLocked(curTime, Process.myPid(), - (int)Process.getPss(Process.myPid()))) { - // Not good! Time to suicide. - mForceKillSystem = true; - notifyAll(); - return; - } - - // How is the phone process doing? - if (mPhoneReq != null) { - if (mPhoneMemMonitor.checkLocked(curTime, mPhonePid, - mPhonePss)) { - // Just kill the phone process and let it restart. - Slog.i(TAG, "Watchdog is killing the phone process"); - Process.killProcess(mPhonePid); - } - } else { - mPhoneMemMonitor.clear(); - } - - needScheduledCheck = mNeedScheduledCheck; - if (needScheduledCheck) { - // Something is going bad, but now is not a good time to - // tear things down... schedule an alarm to check again soon. - nextTime = curTime + recheckInterval; - if (nextTime < mMemcheckExecStartTime) { - nextTime = mMemcheckExecStartTime; - } else if (nextTime >= mMemcheckExecEndTime){ - // Need to check during next exec time... so that needs - // to be computed. - if (localLOGV) Slog.v(TAG, "Computing next time range"); - computeMemcheckTimesLocked(nextTime); - nextTime = mMemcheckExecStartTime; - } - - if (localLOGV) { - mCalendar.setTimeInMillis(nextTime); - Slog.v(TAG, "Next Alarm Time: " + mCalendar); - } - } - } - - if (needScheduledCheck) { - if (localLOGV) Slog.v(TAG, "Scheduling next memcheck alarm for " - + ((nextTime-curTime)/1000/60) + "m from now"); - mAlarm.remove(mCheckupIntent); - mAlarm.set(AlarmManager.RTC_WAKEUP, nextTime, mCheckupIntent); - } else { - if (localLOGV) Slog.v(TAG, "No need to schedule a memcheck alarm!"); - mAlarm.remove(mCheckupIntent); - } - } - - final PssStats mPssStats = new PssStats(); - final String[] mMemInfoFields = new String[] { - "MemFree:", "Buffers:", "Cached:", - "Active:", "Inactive:", - "AnonPages:", "Mapped:", "Slab:", - "SReclaimable:", "SUnreclaim:", "PageTables:" }; - final long[] mMemInfoSizes = new long[mMemInfoFields.length]; - final String[] mVMStatFields = new String[] { - "pgfree ", "pgactivate ", "pgdeactivate ", - "pgfault ", "pgmajfault " }; - final long[] mVMStatSizes = new long[mVMStatFields.length]; - final long[] mPrevVMStatSizes = new long[mVMStatFields.length]; - long mLastLogGlobalMemoryTime; - - void logGlobalMemory() { - PssStats stats = mPssStats; - mActivity.collectPss(stats); - EventLog.writeEvent(EventLogTags.WATCHDOG_PSS_STATS, - stats.mEmptyPss, stats.mEmptyCount, - stats.mBackgroundPss, stats.mBackgroundCount, - stats.mServicePss, stats.mServiceCount, - stats.mVisiblePss, stats.mVisibleCount, - stats.mForegroundPss, stats.mForegroundCount, - stats.mNoPssCount); - EventLog.writeEvent(EventLogTags.WATCHDOG_PROC_STATS, - stats.mProcDeaths[0], stats.mProcDeaths[1], stats.mProcDeaths[2], - stats.mProcDeaths[3], stats.mProcDeaths[4]); - Process.readProcLines("/proc/meminfo", mMemInfoFields, mMemInfoSizes); - for (int i=0; i<mMemInfoSizes.length; i++) { - mMemInfoSizes[i] *= 1024; - } - EventLog.writeEvent(EventLogTags.WATCHDOG_MEMINFO, - (int)mMemInfoSizes[0], (int)mMemInfoSizes[1], (int)mMemInfoSizes[2], - (int)mMemInfoSizes[3], (int)mMemInfoSizes[4], - (int)mMemInfoSizes[5], (int)mMemInfoSizes[6], (int)mMemInfoSizes[7], - (int)mMemInfoSizes[8], (int)mMemInfoSizes[9], (int)mMemInfoSizes[10]); - long now = SystemClock.uptimeMillis(); - long dur = now - mLastLogGlobalMemoryTime; - mLastLogGlobalMemoryTime = now; - Process.readProcLines("/proc/vmstat", mVMStatFields, mVMStatSizes); - for (int i=0; i<mVMStatSizes.length; i++) { - long v = mVMStatSizes[i]; - mVMStatSizes[i] -= mPrevVMStatSizes[i]; - mPrevVMStatSizes[i] = v; - } - EventLog.writeEvent(EventLogTags.WATCHDOG_VMSTAT, dur, - (int)mVMStatSizes[0], (int)mVMStatSizes[1], (int)mVMStatSizes[2], - (int)mVMStatSizes[3], (int)mVMStatSizes[4]); - } - void checkReboot(boolean fromAlarm) { int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval : Settings.Secure.getInt( @@ -730,47 +360,6 @@ public class Watchdog extends Thread { return null; } - /** - * Compute the times during which we next would like to perform process - * restarts. - * - * @param curTime The current system time. - */ - void computeMemcheckTimesLocked(long curTime) { - if (mMemcheckLastTime == curTime) { - return; - } - - mMemcheckLastTime = curTime; - - long memcheckExecStartTime = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_EXEC_START_TIME, - MEMCHECK_DEFAULT_EXEC_START_TIME); - long memcheckExecEndTime = Settings.Secure.getLong( - mResolver, Settings.Secure.MEMCHECK_EXEC_END_TIME, - MEMCHECK_DEFAULT_EXEC_END_TIME); - - mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime, - memcheckExecEndTime); - if (mMemcheckExecEndTime < curTime) { - memcheckExecStartTime += 24*60*60; - memcheckExecEndTime += 24*60*60; - mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime, - memcheckExecEndTime); - } - mMemcheckExecStartTime = computeCalendarTime(mCalendar, curTime, - memcheckExecStartTime); - - if (localLOGV) { - mCalendar.setTimeInMillis(curTime); - Slog.v(TAG, "Current Time: " + mCalendar); - mCalendar.setTimeInMillis(mMemcheckExecStartTime); - Slog.v(TAG, "Start Check Time: " + mCalendar); - mCalendar.setTimeInMillis(mMemcheckExecEndTime); - Slog.v(TAG, "End Check Time: " + mCalendar); - } - } - static long computeCalendarTime(Calendar c, long curTime, long secondsSinceMidnight) { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 8d6ad93..509c789 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -631,6 +631,7 @@ public class WifiService extends IWifiManager.Stub { } public WifiConfiguration getWifiApConfiguration() { + enforceAccessPermission(); final ContentResolver cr = mContext.getContentResolver(); WifiConfiguration wifiConfig = new WifiConfiguration(); int authType; @@ -648,7 +649,8 @@ public class WifiService extends IWifiManager.Stub { } } - private void persistApConfiguration(WifiConfiguration wifiConfig) { + public void setWifiApConfiguration(WifiConfiguration wifiConfig) { + enforceChangePermission(); final ContentResolver cr = mContext.getContentResolver(); boolean isWpa; if (wifiConfig == null) @@ -679,9 +681,9 @@ public class WifiService extends IWifiManager.Stub { /* Configuration changed on a running access point */ if(enable && (wifiConfig != null)) { try { - persistApConfiguration(wifiConfig); nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), SOFTAP_IFACE); + setWifiApConfiguration(wifiConfig); return true; } catch(Exception e) { Slog.e(TAG, "Exception in nwService during AP restart"); @@ -717,7 +719,6 @@ public class WifiService extends IWifiManager.Stub { wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); } - persistApConfiguration(wifiConfig); if (!mWifiStateTracker.loadDriver()) { Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode"); @@ -734,6 +735,8 @@ public class WifiService extends IWifiManager.Stub { return false; } + setWifiApConfiguration(wifiConfig); + } else { try { diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 68787cd..0def5f2 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -48,7 +48,6 @@ import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.WindowManagerPolicyThread; -import com.android.server.KeyInputQueue.QueuedEvent; import com.android.server.am.BatteryStatsService; import android.Manifest; @@ -95,15 +94,19 @@ import android.util.Slog; import android.util.SparseIntArray; import android.view.Display; import android.view.Gravity; +import android.view.HapticFeedbackConstants; import android.view.IApplicationToken; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindow; import android.view.IWindowManager; import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputQueue; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.RawInputEvent; import android.view.Surface; import android.view.SurfaceSession; import android.view.View; @@ -135,7 +138,7 @@ import java.util.List; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, KeyInputQueue.HapticFeedbackCallback { + implements Watchdog.Monitor { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_FOCUS = false; @@ -163,9 +166,6 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean BLUR = true; static final boolean localLOGV = DEBUG; - /** How long to wait for subsequent key repeats, in milliseconds */ - static final int KEY_REPEAT_DELAY = 50; - /** How much to multiply the policy's type layer, to reserve room * for multiple windows of the same type and Z-ordering adjustment * with TYPE_LAYER_OFFSET. */ @@ -198,35 +198,19 @@ public class WindowManagerService extends IWindowManager.Stub /** Adjustment to time to perform a dim, to make it more dramatic. */ static final int DIM_DURATION_MULTIPLIER = 6; - - static final int INJECT_FAILED = 0; - static final int INJECT_SUCCEEDED = 1; - static final int INJECT_NO_PERMISSION = -1; + + // Maximum number of milliseconds to wait for input event injection. + // FIXME is this value reasonable? + private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; + + // Default input dispatching timeout in nanoseconds. + private static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; static final int UPDATE_FOCUS_NORMAL = 0; static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1; static final int UPDATE_FOCUS_PLACING_SURFACES = 2; static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3; - /** The minimum time between dispatching touch events. */ - int mMinWaitTimeBetweenTouchEvents = 1000 / 35; - - // Last touch event time - long mLastTouchEventTime = 0; - - // Last touch event type - int mLastTouchEventType = OTHER_EVENT; - - // Time to wait before calling useractivity again. This saves CPU usage - // when we get a flood of touch events. - static final int MIN_TIME_BETWEEN_USERACTIVITIES = 1000; - - // Last time we call user activity - long mLastUserActivityCallTime = 0; - - // Last time we updated battery stats - long mLastBatteryStatsCallTime = 0; - private static final String SYSTEM_SECURE = "ro.secure"; private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; @@ -349,7 +333,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Z-ordered (bottom-most first) list of all Window objects. */ - final ArrayList mWindows = new ArrayList(); + final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); /** * Windows that are being resized. Used so we can tell the client about @@ -442,8 +426,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppWindowToken> mToTopApps = new ArrayList<AppWindowToken>(); final ArrayList<AppWindowToken> mToBottomApps = new ArrayList<AppWindowToken>(); - //flag to detect fat touch events - boolean mFatTouch = false; Display mDisplay; H mH = new H(); @@ -477,7 +459,6 @@ public class WindowManagerService extends IWindowManager.Stub float mLastWallpaperY = -1; float mLastWallpaperXStep = -1; float mLastWallpaperYStep = -1; - boolean mSendingPointersToWallpaper = false; // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. WindowState mWaitingOnWallpaper; @@ -495,12 +476,11 @@ public class WindowManagerService extends IWindowManager.Stub float mWindowAnimationScale = 1.0f; float mTransitionAnimationScale = 1.0f; - final KeyWaiter mKeyWaiter = new KeyWaiter(); - final KeyQ mQueue; - final InputDispatcherThread mInputThread; + final InputManager mInputManager; // Who is holding the screen on. Session mHoldingScreenOn; + PowerManager.WakeLock mHoldingScreenWakeLock; boolean mTurnOnScreen; @@ -511,8 +491,14 @@ public class WindowManagerService extends IWindowManager.Stub boolean mInTouchMode = false; private ViewServer mViewServer; + private ArrayList<WindowChangeListener> mWindowChangeListeners = + new ArrayList<WindowChangeListener>(); + private boolean mWindowsChanged = false; - final Rect mTempRect = new Rect(); + public interface WindowChangeListener { + public void windowsChanged(); + public void focusChanged(); + } final Configuration mTempConfiguration = new Configuration(); int mScreenLayout = Configuration.SCREENLAYOUT_SIZE_UNDEFINED; @@ -561,6 +547,7 @@ public class WindowManagerService extends IWindowManager.Stub mHaveInputMethods); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DISPLAY); + android.os.Process.setCanSelfBackground(false); synchronized (this) { mService = s; @@ -596,6 +583,7 @@ public class WindowManagerService extends IWindowManager.Stub // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); mPolicy.init(mContext, mService, mPM); synchronized (this) { @@ -639,20 +627,11 @@ public class WindowManagerService extends IWindowManager.Stub filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); mContext.registerReceiver(mBroadcastReceiver, filter); - int max_events_per_sec = 35; - try { - max_events_per_sec = Integer.parseInt(SystemProperties - .get("windowsmgr.max_events_per_sec")); - if (max_events_per_sec < 1) { - max_events_per_sec = 35; - } - } catch (NumberFormatException e) { - } - mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec; - - mQueue = new KeyQ(); + mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + "KEEP_SCREEN_ON_FLAG"); + mHoldingScreenWakeLock.setReferenceCounted(false); - mInputThread = new InputDispatcherThread(); + mInputManager = new InputManager(context, this); PolicyThread thr = new PolicyThread(mPolicy, this, context, pm); thr.start(); @@ -666,8 +645,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputThread.start(); - mQueue.start(); + mInputManager.start(); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -688,33 +666,35 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void placeWindowAfter(Object pos, WindowState window) { + private void placeWindowAfter(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( TAG, "Adding window " + window + " at " + (i+1) + " of " + mWindows.size() + " (after " + pos + ")"); mWindows.add(i+1, window); + mWindowsChanged = true; } - private void placeWindowBefore(Object pos, WindowState window) { + private void placeWindowBefore(WindowState pos, WindowState window) { final int i = mWindows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v( TAG, "Adding window " + window + " at " + i + " of " + mWindows.size() + " (before " + pos + ")"); mWindows.add(i, window); + mWindowsChanged = true; } //This method finds out the index of a window that has the same app token as //win. used for z ordering the windows in mWindows private int findIdxBasedOnAppTokens(WindowState win) { //use a local variable to cache mWindows - ArrayList localmWindows = mWindows; + ArrayList<WindowState> localmWindows = mWindows; int jmax = localmWindows.size(); if(jmax == 0) { return -1; } for(int j = (jmax-1); j >= 0; j--) { - WindowState wentry = (WindowState)localmWindows.get(j); + WindowState wentry = localmWindows.get(j); if(wentry.mAppToken == win.mAppToken) { return j; } @@ -725,7 +705,7 @@ public class WindowManagerService extends IWindowManager.Stub private void addWindowToListInOrderLocked(WindowState win, boolean addToToken) { final IWindow client = win.mClient; final WindowToken token = win.mToken; - final ArrayList localmWindows = mWindows; + final ArrayList<WindowState> localmWindows = mWindows; final int N = localmWindows.size(); final WindowState attached = win.mAttachedWindow; @@ -759,6 +739,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Adding window " + win + " at " + (newIdx+1) + " of " + N); localmWindows.add(newIdx+1, win); + mWindowsChanged = true; } } } @@ -769,7 +750,7 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out where the window should go, based on the // order of applications. final int NA = mAppTokens.size(); - Object pos = null; + WindowState pos = null; for (i=NA-1; i>=0; i--) { AppWindowToken t = mAppTokens.get(i); if (t == token) { @@ -789,8 +770,7 @@ public class WindowManagerService extends IWindowManager.Stub // we need to look some more. if (pos != null) { // Move behind any windows attached to this one. - WindowToken atoken = - mTokenMap.get(((WindowState)pos).mClient.asBinder()); + WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { final int NC = atoken.windows.size(); if (NC > 0) { @@ -816,8 +796,7 @@ public class WindowManagerService extends IWindowManager.Stub if (pos != null) { // Move in front of any windows attached to this // one. - WindowToken atoken = - mTokenMap.get(((WindowState)pos).mClient.asBinder()); + WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { final int NC = atoken.windows.size(); if (NC > 0) { @@ -832,7 +811,7 @@ public class WindowManagerService extends IWindowManager.Stub // Just search for the start of this layer. final int myLayer = win.mBaseLayer; for (i=0; i<N; i++) { - WindowState w = (WindowState)localmWindows.get(i); + WindowState w = localmWindows.get(i); if (w.mBaseLayer > myLayer) { break; } @@ -841,6 +820,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Adding window " + win + " at " + i + " of " + N); localmWindows.add(i, win); + mWindowsChanged = true; } } } @@ -848,7 +828,7 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out where window should go, based on layer. final int myLayer = win.mBaseLayer; for (i=N-1; i>=0; i--) { - if (((WindowState)localmWindows.get(i)).mBaseLayer <= myLayer) { + if (localmWindows.get(i).mBaseLayer <= myLayer) { i++; break; } @@ -858,6 +838,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Adding window " + win + " at " + i + " of " + N); localmWindows.add(i, win); + mWindowsChanged = true; } if (addToToken) { token.windows.add(tokenWindowsPos, win); @@ -930,13 +911,13 @@ public class WindowManagerService extends IWindowManager.Stub } int findDesiredInputMethodWindowIndexLocked(boolean willMove) { - final ArrayList localmWindows = mWindows; + final ArrayList<WindowState> localmWindows = mWindows; final int N = localmWindows.size(); WindowState w = null; int i = N; while (i > 0) { i--; - w = (WindowState)localmWindows.get(i); + w = localmWindows.get(i); //Slog.i(TAG, "Checking window @" + i + " " + w + " fl=0x" // + Integer.toHexString(w.mAttrs.flags)); @@ -951,7 +932,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!willMove && w.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING && i > 0) { - WindowState wb = (WindowState)localmWindows.get(i-1); + WindowState wb = localmWindows.get(i-1); if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) { i--; w = wb; @@ -981,7 +962,7 @@ public class WindowManagerService extends IWindowManager.Stub int pos = 0; pos = localmWindows.indexOf(curTarget); while (pos >= 0) { - WindowState win = (WindowState)localmWindows.get(pos); + WindowState win = localmWindows.get(pos); if (win.mAppToken != token) { break; } @@ -1066,6 +1047,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v( TAG, "Adding input method window " + win + " at " + pos); mWindows.add(pos, win); + mWindowsChanged = true; moveInputMethodDialogsLocked(pos+1); return; } @@ -1085,7 +1067,7 @@ public class WindowManagerService extends IWindowManager.Stub int wi = imw.mChildWindows.size(); while (wi > 0) { wi--; - WindowState cw = (WindowState)imw.mChildWindows.get(wi); + WindowState cw = imw.mChildWindows.get(wi); cw.mAnimLayer = cw.mLayer + adj; if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + cw + " anim layer: " + cw.mAnimLayer); @@ -1107,10 +1089,11 @@ public class WindowManagerService extends IWindowManager.Stub if (wpos < interestingPos) interestingPos--; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing at " + wpos + ": " + win); mWindows.remove(wpos); + mWindowsChanged = true; int NC = win.mChildWindows.size(); while (NC > 0) { NC--; - WindowState cw = (WindowState)win.mChildWindows.get(NC); + WindowState cw = win.mChildWindows.get(NC); int cpos = mWindows.indexOf(cw); if (cpos >= 0) { if (cpos < interestingPos) interestingPos--; @@ -1133,6 +1116,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "ReAdd removing from " + wpos + ": " + win); mWindows.remove(wpos); + mWindowsChanged = true; reAddWindowLocked(wpos, win); } } @@ -1161,7 +1145,7 @@ public class WindowManagerService extends IWindowManager.Stub if (pos >= 0) { final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken; if (pos < mWindows.size()) { - WindowState wp = (WindowState)mWindows.get(pos); + WindowState wp = mWindows.get(pos); if (wp == mInputMethodWindow) { pos++; } @@ -1205,14 +1189,14 @@ public class WindowManagerService extends IWindowManager.Stub // located here, and contiguous. final int N = mWindows.size(); WindowState firstImWin = imPos < N - ? (WindowState)mWindows.get(imPos) : null; + ? mWindows.get(imPos) : null; // Figure out the actual input method window that should be // at the bottom of their stack. WindowState baseImWin = imWin != null ? imWin : mInputMethodDialogs.get(0); if (baseImWin.mChildWindows.size() > 0) { - WindowState cw = (WindowState)baseImWin.mChildWindows.get(0); + WindowState cw = baseImWin.mChildWindows.get(0); if (cw.mSubLayer < 0) baseImWin = cw; } @@ -1221,7 +1205,7 @@ public class WindowManagerService extends IWindowManager.Stub // First find the top IM window. int pos = imPos+1; while (pos < N) { - if (!((WindowState)mWindows.get(pos)).mIsImWindow) { + if (!(mWindows.get(pos)).mIsImWindow) { break; } pos++; @@ -1229,7 +1213,7 @@ public class WindowManagerService extends IWindowManager.Stub pos++; // Now there should be no more input method windows above. while (pos < N) { - if (((WindowState)mWindows.get(pos)).mIsImWindow) { + if ((mWindows.get(pos)).mIsImWindow) { break; } pos++; @@ -1317,7 +1301,7 @@ public class WindowManagerService extends IWindowManager.Stub // First find top-most window that has asked to be on top of the // wallpaper; all wallpapers go behind it. - final ArrayList localmWindows = mWindows; + final ArrayList<WindowState> localmWindows = mWindows; int N = localmWindows.size(); WindowState w = null; WindowState foundW = null; @@ -1327,7 +1311,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = N; while (i > 0) { i--; - w = (WindowState)localmWindows.get(i); + w = localmWindows.get(i); if ((w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER)) { if (topCurW == null) { topCurW = w; @@ -1498,7 +1482,7 @@ public class WindowManagerService extends IWindowManager.Stub // AND any starting window associated with it, AND below the // maximum layer the policy allows for wallpapers. while (foundI > 0) { - WindowState wb = (WindowState)localmWindows.get(foundI-1); + WindowState wb = localmWindows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && (wb.mAttrs.type != TYPE_APPLICATION_STARTING || @@ -1522,7 +1506,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Okay i is the position immediately above the wallpaper. Look at // what is below it for later. - foundW = foundI > 0 ? (WindowState)localmWindows.get(foundI-1) : null; + foundW = foundI > 0 ? localmWindows.get(foundI-1) : null; } if (visible) { @@ -1581,7 +1565,7 @@ public class WindowManagerService extends IWindowManager.Stub if (wallpaper == foundW) { foundI--; foundW = foundI > 0 - ? (WindowState)localmWindows.get(foundI-1) : null; + ? localmWindows.get(foundI-1) : null; continue; } @@ -1593,6 +1577,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Wallpaper removing at " + oldIndex + ": " + wallpaper); localmWindows.remove(oldIndex); + mWindowsChanged = true; if (oldIndex < foundI) { foundI--; } @@ -1604,6 +1589,7 @@ public class WindowManagerService extends IWindowManager.Stub + " from " + oldIndex + " to " + foundI); localmWindows.add(foundI, wallpaper); + mWindowsChanged = true; changed |= ADJUST_WALLPAPER_LAYERS_CHANGED; } } @@ -1793,74 +1779,10 @@ public class WindowManagerService extends IWindowManager.Stub } } } - - void sendPointerToWallpaperLocked(WindowState srcWin, - MotionEvent pointer, long eventTime) { - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); - if ((wallpaper.mAttrs.flags & - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { - continue; - } - try { - MotionEvent ev = MotionEvent.obtainNoHistory(pointer); - if (srcWin != null) { - ev.offsetLocation(srcWin.mFrame.left-wallpaper.mFrame.left, - srcWin.mFrame.top-wallpaper.mFrame.top); - } else { - ev.offsetLocation(-wallpaper.mFrame.left, -wallpaper.mFrame.top); - } - switch (pointer.getAction()) { - case MotionEvent.ACTION_DOWN: - mSendingPointersToWallpaper = true; - break; - case MotionEvent.ACTION_UP: - mSendingPointersToWallpaper = false; - break; - } - wallpaper.mClient.dispatchPointer(ev, eventTime, false); - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending pointer to wallpaper", e); - } - } - } - } - - void dispatchPointerElsewhereLocked(WindowState srcWin, WindowState relWin, - MotionEvent pointer, long eventTime, boolean skipped) { - if (relWin != null) { - mPolicy.dispatchedPointerEventLw(pointer, relWin.mFrame.left, relWin.mFrame.top); - } else { - mPolicy.dispatchedPointerEventLw(pointer, 0, 0); - } - - // If we sent an initial down to the wallpaper, then continue - // sending events until the final up. - if (mSendingPointersToWallpaper) { - if (skipped) { - Slog.i(TAG, "Sending skipped pointer to wallpaper!"); - } - sendPointerToWallpaperLocked(relWin, pointer, eventTime); - - // If we are on top of the wallpaper, then the wallpaper also - // gets to see this movement. - } else if (srcWin != null - && pointer.getAction() == MotionEvent.ACTION_DOWN - && mWallpaperTarget == srcWin - && srcWin.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD) { - sendPointerToWallpaperLocked(relWin, pointer, eventTime); - } - } public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility, - Rect outContentInsets) { + Rect outContentInsets, InputChannel outInputChannel) { int res = mPolicy.checkAddPermission(attrs); if (res != WindowManagerImpl.ADD_OKAY) { return res; @@ -1879,7 +1801,7 @@ public class WindowManagerService extends IWindowManager.Stub mDisplay = wm.getDefaultDisplay(); mInitialDisplayWidth = mDisplay.getWidth(); mInitialDisplayHeight = mDisplay.getHeight(); - mQueue.setDisplay(mDisplay); + mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight); reportNewConfig = true; } @@ -1972,6 +1894,15 @@ public class WindowManagerService extends IWindowManager.Stub if (res != WindowManagerImpl.ADD_OKAY) { return res; } + + if (outInputChannel != null) { + String name = win.makeInputChannelName(); + InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); + win.mInputChannel = inputChannels[0]; + inputChannels[1].transferToBinderOutParameter(outInputChannel); + + mInputManager.registerInputChannel(win.mInputChannel); + } // From now on, no exceptions or errors allowed! @@ -2025,8 +1956,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean focusChanged = false; if (win.canReceiveKeys()) { - if ((focusChanged=updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS)) - == true) { + focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS); + if (focusChanged) { imMayMove = false; } } @@ -2042,10 +1973,9 @@ public class WindowManagerService extends IWindowManager.Stub //dump(); if (focusChanged) { - if (mCurrentFocus != null) { - mKeyWaiter.handleNewWindowLocked(mCurrentFocus); - } + finishUpdateFocusedWindowAfterAssignLayersLocked(); } + if (localLOGV) Slog.v( TAG, "New client " + client.asBinder() + ": window=" + win); @@ -2087,6 +2017,8 @@ public class WindowManagerService extends IWindowManager.Stub + ", surface=" + win.mSurface); final long origId = Binder.clearCallingIdentity(); + + win.disposeInputChannel(); if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Remove " + win + ": mSurface=" + win.mSurface @@ -2147,12 +2079,6 @@ public class WindowManagerService extends IWindowManager.Stub } private void removeWindowInnerLocked(Session session, WindowState win) { - mKeyWaiter.finishedKey(session, win.mClient, true, - KeyWaiter.RETURN_NOTHING); - mKeyWaiter.releaseMotionTarget(win); - mKeyWaiter.releasePendingPointerLocked(win.mSession); - mKeyWaiter.releasePendingTrackballLocked(win.mSession); - win.mRemoved = true; if (mInputMethodTarget == win) { @@ -2170,6 +2096,7 @@ public class WindowManagerService extends IWindowManager.Stub mWindowMap.remove(win.mClient.asBinder()); mWindows.remove(win); + mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win); if (mInputMethodWindow == win) { @@ -2230,6 +2157,8 @@ public class WindowManagerService extends IWindowManager.Stub win.mAppToken.updateReportedVisibilityLocked(); } } + + mInputMonitor.updateInputWindowsLw(); } private static void logSurface(WindowState w, String msg, RuntimeException where) { @@ -2473,6 +2402,8 @@ public class WindowManagerService extends IWindowManager.Stub outSurface.release(); } } catch (Exception e) { + mInputMonitor.updateInputWindowsLw(); + Slog.w(TAG, "Exception thrown when creating surface for client " + client + " (" + win.mAttrs.getTitle() + ")", e); @@ -2519,8 +2450,6 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, transit, false)) { focusMayChange = true; win.mExiting = true; - mKeyWaiter.finishedKey(session, client, true, - KeyWaiter.RETURN_NOTHING); } else if (win.isAnimating()) { // Currently in a hide animation... turn this into // an exit. @@ -2615,6 +2544,8 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange); inTouchMode = mInTouchMode; + + mInputMonitor.updateInputWindowsLw(); } if (configChanged) { @@ -2981,8 +2912,6 @@ public class WindowManagerService extends IWindowManager.Stub if (win.isVisibleNow()) { applyAnimationLocked(win, WindowManagerPolicy.TRANSIT_EXIT, false); - mKeyWaiter.finishedKey(win.mSession, win.mClient, true, - KeyWaiter.RETURN_NOTHING); changed = true; } } @@ -3000,6 +2929,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + mInputMonitor.updateInputWindowsLw(); } else { Slog.w(TAG, "Attempted to remove non-existing token: " + token); } @@ -3013,6 +2943,20 @@ public class WindowManagerService extends IWindowManager.Stub "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } + + // Get the dispatching timeout here while we are not holding any locks so that it + // can be cached by the AppWindowToken. The timeout value is used later by the + // input dispatcher in code that does hold locks. If we did not cache the value + // here we would run the chance of introducing a deadlock between the window manager + // (which holds locks while updating the input dispatcher state) and the activity manager + // (which holds locks while querying the application token). + long inputDispatchingTimeoutNanos; + try { + inputDispatchingTimeoutNanos = token.getKeyDispatchingTimeout() * 1000000L; + } catch (RemoteException ex) { + Slog.w(TAG, "Could not get dispatching timeout.", ex); + inputDispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + } synchronized(mWindowMap) { AppWindowToken wtoken = findAppWindowToken(token.asBinder()); @@ -3021,6 +2965,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } wtoken = new AppWindowToken(token); + wtoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; wtoken.groupId = groupId; wtoken.appFullscreen = fullscreen; wtoken.requestedOrientation = requestedOrientation; @@ -3056,7 +3001,7 @@ public class WindowManagerService extends IWindowManager.Stub public int getOrientationFromWindowsLocked() { int pos = mWindows.size() - 1; while (pos >= 0) { - WindowState wtoken = (WindowState) mWindows.get(pos); + WindowState wtoken = mWindows.get(pos); pos--; if (wtoken.mAppToken != null) { // We hit an application window. so the orientation will be determined by the @@ -3284,7 +3229,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FOCUS) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp); changed = mFocusedApp != null; mFocusedApp = null; - mKeyWaiter.tickle(); + if (changed) { + mInputMonitor.setFocusedAppLw(null); + } } else { AppWindowToken newFocus = findAppWindowToken(token); if (newFocus == null) { @@ -3294,7 +3241,9 @@ public class WindowManagerService extends IWindowManager.Stub changed = mFocusedApp != newFocus; mFocusedApp = newFocus; if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp); - mKeyWaiter.tickle(); + if (changed) { + mInputMonitor.setFocusedAppLw(newFocus); + } } if (moveFocusNow && changed) { @@ -3435,6 +3384,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Removing starting window: " + startingWindow); mWindows.remove(startingWindow); + mWindowsChanged = true; ttoken.windows.remove(startingWindow); ttoken.allAppWindows.remove(startingWindow); addWindowToListInOrderLocked(startingWindow, true); @@ -3605,8 +3555,6 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, WindowManagerPolicy.TRANSIT_EXIT, false); } - mKeyWaiter.finishedKey(win.mSession, win.mClient, true, - KeyWaiter.RETURN_NOTHING); changed = true; } } @@ -3634,6 +3582,8 @@ public class WindowManagerService extends IWindowManager.Stub if (performLayout) { updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES); performLayoutAndPlaceSurfacesLocked(); + } else { + mInputMonitor.updateInputWindowsLw(); } } } @@ -3891,7 +3841,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken); mFocusedApp = null; updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); - mKeyWaiter.tickle(); + mInputMonitor.setFocusedAppLw(null); } } else { Slog.w(TAG, "Attempted to remove non-existing app token: " + token); @@ -3917,10 +3867,11 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = token.windows.get(i); if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win); mWindows.remove(win); + mWindowsChanged = true; int j = win.mChildWindows.size(); while (j > 0) { j--; - WindowState cwin = (WindowState)win.mChildWindows.get(j); + WindowState cwin = win.mChildWindows.get(j); if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing child window " + cwin); mWindows.remove(cwin); @@ -3948,7 +3899,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = NW; while (i > 0) { i--; - WindowState win = (WindowState)mWindows.get(i); + WindowState win = mWindows.get(i); if (win.getAppToken() != null) { return i+1; } @@ -3974,7 +3925,7 @@ public class WindowManagerService extends IWindowManager.Stub int j = win.mChildWindows.size(); while (j > 0) { j--; - WindowState cwin = (WindowState)win.mChildWindows.get(j); + WindowState cwin = win.mChildWindows.get(j); if (cwin.mSubLayer >= 0) { for (int pos=NW-1; pos>=0; pos--) { if (mWindows.get(pos) == cwin) { @@ -4002,7 +3953,7 @@ public class WindowManagerService extends IWindowManager.Stub final int NCW = win.mChildWindows.size(); boolean added = false; for (int j=0; j<NCW; j++) { - WindowState cwin = (WindowState)win.mChildWindows.get(j); + WindowState cwin = win.mChildWindows.get(j); if (!added && cwin.mSubLayer >= 0) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at " + index + ": " + cwin); @@ -4021,6 +3972,7 @@ public class WindowManagerService extends IWindowManager.Stub mWindows.add(index, win); index++; } + mWindowsChanged = true; return index; } @@ -4285,7 +4237,7 @@ public class WindowManagerService extends IWindowManager.Stub public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mSurface != null) { try { w.mClient.closeSystemDialogs(reason); @@ -4356,7 +4308,7 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getSwitchState(sw); + return mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, sw); } public int getSwitchStateForDevice(int devid, int sw) { @@ -4364,7 +4316,7 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getSwitchState(devid, sw); + return mInputManager.getSwitchState(devid, InputDevice.SOURCE_ANY, sw); } public int getScancodeState(int sw) { @@ -4372,7 +4324,7 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_ANY, sw); } public int getScancodeStateForDevice(int devid, int sw) { @@ -4380,7 +4332,7 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getScancodeState(devid, sw); + return mInputManager.getScanCodeState(devid, InputDevice.SOURCE_ANY, sw); } public int getTrackballScancodeState(int sw) { @@ -4388,7 +4340,7 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getTrackballScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); } public int getDPadScancodeState(int sw) { @@ -4396,7 +4348,7 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getDPadScancodeState(sw); + return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_DPAD, sw); } public int getKeycodeState(int sw) { @@ -4404,7 +4356,7 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, sw); } public int getKeycodeStateForDevice(int devid, int sw) { @@ -4412,7 +4364,7 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getKeycodeState(devid, sw); + return mInputManager.getKeyCodeState(devid, InputDevice.SOURCE_ANY, sw); } public int getTrackballKeycodeState(int sw) { @@ -4420,7 +4372,7 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getTrackballKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); } public int getDPadKeycodeState(int sw) { @@ -4428,11 +4380,11 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getDPadKeycodeState(sw); + return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, sw); } public boolean hasKeys(int[] keycodes, boolean[] keyExists) { - return KeyInputQueue.hasKeys(keycodes, keyExists); + return mInputManager.hasKeys(-1, InputDevice.SOURCE_ANY, keycodes, keyExists); } public void enableScreenAfterBoot() { @@ -4469,7 +4421,7 @@ public class WindowManagerService extends IWindowManager.Stub // have been drawn. final int N = mWindows.size(); for (int i=0; i<N; i++) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.isVisibleLw() && !w.mObscured && (w.mOrientationChanging || !w.isDrawnLw())) { return; @@ -4577,12 +4529,12 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = true; startFreezingDisplayLocked(); Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags); - mQueue.setOrientation(rotation); + mInputManager.setDisplayOrientation(0, rotation); if (mDisplayEnabled) { Surface.setOrientation(0, rotation, animFlags); } for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mSurface != null) { w.mOrientationChanging = true; } @@ -4738,11 +4690,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean result = true; - Object[] windows; + WindowState[] windows; synchronized (mWindowMap) { - windows = new Object[mWindows.size()]; //noinspection unchecked - windows = mWindows.toArray(windows); + windows = mWindows.toArray(new WindowState[mWindows.size()]); } BufferedWriter out = null; @@ -4754,7 +4705,7 @@ public class WindowManagerService extends IWindowManager.Stub final int count = windows.length; for (int i = 0; i < count; i++) { - final WindowState w = (WindowState) windows[i]; + final WindowState w = windows[i]; out.write(Integer.toHexString(System.identityHashCode(w))); out.write(' '); out.append(w.mAttrs.getTitle()); @@ -4779,6 +4730,51 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Returns the focused window in the following format: + * windowHashCodeInHexadecimal windowName + * + * @param client The remote client to send the listing to. + * @return False if an error occurred, true otherwise. + */ + boolean viewServerGetFocusedWindow(Socket client) { + if (isSystemSecure()) { + return false; + } + + boolean result = true; + + WindowState focusedWindow = getFocusedWindow(); + + BufferedWriter out = null; + + // Any uncaught exception will crash the system process + try { + OutputStream clientStream = client.getOutputStream(); + out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); + + if(focusedWindow != null) { + out.write(Integer.toHexString(System.identityHashCode(focusedWindow))); + out.write(' '); + out.append(focusedWindow.mAttrs.getTitle()); + } + out.write('\n'); + out.flush(); + } catch (Exception e) { + result = false; + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + result = false; + } + } + } + + return result; + } + + /** * Sends a command to a target window. The result of the command, if any, will be * written in the output stream of the specified socket. * @@ -4859,17 +4855,59 @@ public class WindowManagerService extends IWindowManager.Stub return success; } + public void addWindowChangeListener(WindowChangeListener listener) { + synchronized(mWindowMap) { + mWindowChangeListeners.add(listener); + } + } + + public void removeWindowChangeListener(WindowChangeListener listener) { + synchronized(mWindowMap) { + mWindowChangeListeners.remove(listener); + } + } + + private void notifyWindowsChanged() { + WindowChangeListener[] windowChangeListeners; + synchronized(mWindowMap) { + if(mWindowChangeListeners.isEmpty()) { + return; + } + windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()]; + windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners); + } + int N = windowChangeListeners.length; + for(int i = 0; i < N; i++) { + windowChangeListeners[i].windowsChanged(); + } + } + + private void notifyFocusChanged() { + WindowChangeListener[] windowChangeListeners; + synchronized(mWindowMap) { + if(mWindowChangeListeners.isEmpty()) { + return; + } + windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()]; + windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners); + } + int N = windowChangeListeners.length; + for(int i = 0; i < N; i++) { + windowChangeListeners[i].focusChanged(); + } + } + private WindowState findWindow(int hashCode) { if (hashCode == -1) { return getFocusedWindow(); } synchronized (mWindowMap) { - final ArrayList windows = mWindows; + final ArrayList<WindowState> windows = mWindows; final int count = windows.size(); for (int i = 0; i < count; i++) { - WindowState w = (WindowState) windows.get(i); + WindowState w = windows.get(i); if (System.identityHashCode(w) == hashCode) { return w; } @@ -4908,7 +4946,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mDisplay == null) { return false; } - mQueue.getInputConfiguration(config); + + mInputManager.getInputConfiguration(config); // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 @@ -4951,8 +4990,12 @@ public class WindowManagerService extends IWindowManager.Stub mScreenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL | Configuration.SCREENLAYOUT_LONG_NO; } else { - // Is this a large screen? - if (longSize > 640 && shortSize >= 480) { + // What size is this screen screen? + if (longSize >= 800 && shortSize >= 600) { + // SVGA or larger screens at medium density are the point + // at which we consider it to be an extra large screen. + mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE; + } else if (longSize >= 640 && shortSize >= 480) { // VGA or larger screens at medium density are the point // at which we consider it to be a large screen. mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; @@ -4983,451 +5026,365 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.adjustConfigurationLw(config); return true; } - + // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- + + InputMonitor mInputMonitor = new InputMonitor(); + + /* Tracks the progress of input dispatch and ensures that input dispatch state + * is kept in sync with changes in window focus, visibility, registration, and + * other relevant Window Manager state transitions. */ + final class InputMonitor { + // Current window with input focus for keys and other non-touch events. May be null. + private WindowState mInputFocus; + + // When true, prevents input dispatch from proceeding until set to false again. + private boolean mInputDispatchFrozen; + + // When true, input dispatch proceeds normally. Otherwise all events are dropped. + private boolean mInputDispatchEnabled = true; - private final void wakeupIfNeeded(WindowState targetWin, int eventType) { - long curTime = SystemClock.uptimeMillis(); - - if (eventType == TOUCH_EVENT || eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT) { - if (mLastTouchEventType == eventType && - (curTime - mLastUserActivityCallTime) < MIN_TIME_BETWEEN_USERACTIVITIES) { - return; - } - mLastUserActivityCallTime = curTime; - mLastTouchEventType = eventType; - } - - if (targetWin == null - || targetWin.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD) { - mPowerManager.userActivity(curTime, false, eventType, false); - } - } - - // tells if it's a cheek event or not -- this function is stateful - private static final int EVENT_NONE = 0; - private static final int EVENT_UNKNOWN = 0; - private static final int EVENT_CHEEK = 0; - private static final int EVENT_IGNORE_DURATION = 300; // ms - private static final float CHEEK_THRESHOLD = 0.6f; - private int mEventState = EVENT_NONE; - private float mEventSize; - - private int eventType(MotionEvent ev) { - float size = ev.getSize(); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mEventSize = size; - return (mEventSize > CHEEK_THRESHOLD) ? CHEEK_EVENT : TOUCH_EVENT; - case MotionEvent.ACTION_UP: - if (size > mEventSize) mEventSize = size; - return (mEventSize > CHEEK_THRESHOLD) ? CHEEK_EVENT : TOUCH_UP_EVENT; - case MotionEvent.ACTION_MOVE: - final int N = ev.getHistorySize(); - if (size > mEventSize) mEventSize = size; - if (mEventSize > CHEEK_THRESHOLD) return CHEEK_EVENT; - for (int i=0; i<N; i++) { - size = ev.getHistoricalSize(i); - if (size > mEventSize) mEventSize = size; - if (mEventSize > CHEEK_THRESHOLD) return CHEEK_EVENT; - } - if (ev.getEventTime() < ev.getDownTime() + EVENT_IGNORE_DURATION) { - return TOUCH_EVENT; - } else { - return LONG_TOUCH_EVENT; + // Temporary list of windows information to provide to the input dispatcher. + private InputWindowList mTempInputWindows = new InputWindowList(); + + // Temporary input application object to provide to the input dispatcher. + private InputApplication mTempInputApplication = new InputApplication(); + + /* Notifies the window manager about a broken input channel. + * + * Called by the InputManager. + */ + public void notifyInputChannelBroken(InputChannel inputChannel) { + synchronized (mWindowMap) { + WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); + if (windowState == null) { + return; // irrelevant + } + + Slog.i(TAG, "WINDOW DIED " + windowState); + removeWindowLocked(windowState.mSession, windowState); } - default: - // not good - return OTHER_EVENT; } - } - - /** - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - private int dispatchPointer(QueuedEvent qev, MotionEvent ev, int pid, int uid) { - if (DEBUG_INPUT || WindowManagerPolicy.WATCH_POINTER) Slog.v(TAG, - "dispatchPointer " + ev); - - if (MEASURE_LATENCY) { - lt.sample("3 Wait for last dispatch ", System.nanoTime() - qev.whenNano); + + /* Notifies the window manager about an input channel that is not responding. + * The method can either cause dispatching to be aborted by returning -2 or + * return a new timeout in nanoseconds. + * + * Called by the InputManager. + */ + public long notifyInputChannelANR(InputChannel inputChannel) { + AppWindowToken token; + synchronized (mWindowMap) { + WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); + if (windowState == null) { + return -2; // irrelevant, abort dispatching (-2) + } + + Slog.i(TAG, "Input event dispatching timed out sending to " + + windowState.mAttrs.getTitle()); + token = windowState.mAppToken; + } + + return notifyANRInternal(token); } - - Object targetObj = mKeyWaiter.waitForNextEventTarget(null, qev, - ev, true, false, pid, uid); - - if (MEASURE_LATENCY) { - lt.sample("3 Last dispatch finished ", System.nanoTime() - qev.whenNano); + + /* Notifies the window manager about an input channel spontaneously recovering from ANR + * by successfully delivering the event that originally timed out. + * + * Called by the InputManager. + */ + public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) { + // Nothing to do just now. + // Just wait for the user to dismiss the ANR dialog. } + + /* Notifies the window manager about an application that is not responding + * in general rather than with respect to a particular input channel. + * The method can either cause dispatching to be aborted by returning -2 or + * return a new timeout in nanoseconds. + * + * Called by the InputManager. + */ + public long notifyANR(Object token) { + AppWindowToken appWindowToken = (AppWindowToken) token; - int action = ev.getAction(); - - if (action == MotionEvent.ACTION_UP) { - // let go of our target - mKeyWaiter.mMotionTarget = null; - mPowerManager.logPointerUpEvent(); - } else if (action == MotionEvent.ACTION_DOWN) { - mPowerManager.logPointerDownEvent(); + Slog.i(TAG, "Input event dispatching timed out sending to application " + + appWindowToken.stringName); + return notifyANRInternal(appWindowToken); } - - if (targetObj == null) { - // In this case we are either dropping the event, or have received - // a move or up without a down. It is common to receive move - // events in such a way, since this means the user is moving the - // pointer without actually pressing down. All other cases should - // be atypical, so let's log them. - if (action != MotionEvent.ACTION_MOVE) { - Slog.w(TAG, "No window to dispatch pointer action " + ev.getAction()); - } - synchronized (mWindowMap) { - dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), true); - } - if (qev != null) { - mQueue.recycleEvent(qev); + + private long notifyANRInternal(AppWindowToken token) { + if (token != null && token.appToken != null) { + try { + // Notify the activity manager about the timeout and let it decide whether + // to abort dispatching or keep waiting. + boolean abort = token.appToken.keyDispatchingTimedOut(); + if (! abort) { + // The activity manager declined to abort dispatching. + // Wait a bit longer and timeout again later. + return token.inputDispatchingTimeoutNanos; + } + } catch (RemoteException ex) { + } } - ev.recycle(); - return INJECT_FAILED; + return -2; // abort dispatching } - if (targetObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { + + private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { synchronized (mWindowMap) { - dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), true); + return getWindowStateForInputChannelLocked(inputChannel); } - if (qev != null) { - mQueue.recycleEvent(qev); - } - ev.recycle(); - return INJECT_SUCCEEDED; } - - WindowState target = (WindowState)targetObj; - - final long eventTime = ev.getEventTime(); - final long eventTimeNano = ev.getEventTimeNano(); - - //Slog.i(TAG, "Sending " + ev + " to " + target); - - if (uid != 0 && uid != target.mSession.mUid) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, pid, uid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting pointer event from pid " - + pid + " uid " + uid + " to window " + target - + " owned by uid " + target.mSession.mUid); - if (qev != null) { - mQueue.recycleEvent(qev); + + private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) { + int windowCount = mWindows.size(); + for (int i = 0; i < windowCount; i++) { + WindowState windowState = mWindows.get(i); + if (windowState.mInputChannel == inputChannel) { + return windowState; } - ev.recycle(); - return INJECT_NO_PERMISSION; } + + return null; } - - if (MEASURE_LATENCY) { - lt.sample("4 in dispatchPointer ", System.nanoTime() - eventTimeNano); - } - - if ((target.mAttrs.flags & - WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) { - //target wants to ignore fat touch events - boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(ev); - //explicit flag to return without processing event further - boolean returnFlag = false; - if((action == MotionEvent.ACTION_DOWN)) { - mFatTouch = false; - if(cheekPress) { - mFatTouch = true; - returnFlag = true; + + /* Updates the cached window information provided to the input dispatcher. */ + public void updateInputWindowsLw() { + // Populate the input window list with information about all of the windows that + // could potentially receive input. + // As an optimization, we could try to prune the list of windows but this turns + // out to be difficult because only the native code knows for sure which window + // currently has touch focus. + final ArrayList<WindowState> windows = mWindows; + final int N = windows.size(); + for (int i = N - 1; i >= 0; i--) { + final WindowState child = windows.get(i); + if (child.mInputChannel == null || child.mRemoved) { + // Skip this window because it cannot possibly receive input. + continue; } - } else { - if(action == MotionEvent.ACTION_UP) { - if(mFatTouch) { - //earlier even was invalid doesnt matter if current up is cheekpress or not - mFatTouch = false; - returnFlag = true; - } else if(cheekPress) { - //cancel the earlier event - ev.setAction(MotionEvent.ACTION_CANCEL); - action = MotionEvent.ACTION_CANCEL; + + final int flags = child.mAttrs.flags; + final int type = child.mAttrs.type; + + final boolean hasFocus = (child == mInputFocus); + final boolean isVisible = child.isVisibleLw(); + final boolean hasWallpaper = (child == mWallpaperTarget) + && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); + + // Add a window to our list of input windows. + final InputWindow inputWindow = mTempInputWindows.add(); + inputWindow.inputChannel = child.mInputChannel; + inputWindow.layoutParamsFlags = flags; + inputWindow.layoutParamsType = type; + inputWindow.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); + inputWindow.visible = isVisible; + inputWindow.hasFocus = hasFocus; + inputWindow.hasWallpaper = hasWallpaper; + inputWindow.paused = child.mAppToken != null ? child.mAppToken.paused : false; + inputWindow.ownerPid = child.mSession.mPid; + inputWindow.ownerUid = child.mSession.mUid; + + final Rect frame = child.mFrame; + inputWindow.frameLeft = frame.left; + inputWindow.frameTop = frame.top; + + switch (child.mTouchableInsets) { + default: + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + inputWindow.touchableAreaLeft = frame.left; + inputWindow.touchableAreaTop = frame.top; + inputWindow.touchableAreaRight = frame.right; + inputWindow.touchableAreaBottom = frame.bottom; + break; + + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: { + Rect inset = child.mGivenContentInsets; + inputWindow.touchableAreaLeft = frame.left + inset.left; + inputWindow.touchableAreaTop = frame.top + inset.top; + inputWindow.touchableAreaRight = frame.right - inset.right; + inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; + break; } - } else if(action == MotionEvent.ACTION_MOVE) { - if(mFatTouch) { - //two cases here - //an invalid down followed by 0 or moves(valid or invalid) - //a valid down, invalid move, more moves. want to ignore till up - returnFlag = true; - } else if(cheekPress) { - //valid down followed by invalid moves - //an invalid move have to cancel earlier action - ev.setAction(MotionEvent.ACTION_CANCEL); - action = MotionEvent.ACTION_CANCEL; - if (DEBUG_INPUT) Slog.v(TAG, "Sending cancel for invalid ACTION_MOVE"); - //note that the subsequent invalid moves will not get here - mFatTouch = true; + + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: { + Rect inset = child.mGivenVisibleInsets; + inputWindow.touchableAreaLeft = frame.left + inset.left; + inputWindow.touchableAreaTop = frame.top + inset.top; + inputWindow.touchableAreaRight = frame.right - inset.right; + inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; + break; } } - } //else if action - if(returnFlag) { - //recycle que, ev - if (qev != null) { - mQueue.recycleEvent(qev); - } - ev.recycle(); - return INJECT_FAILED; } - } //end if target - // Enable this for testing the "right" value - if (false && action == MotionEvent.ACTION_DOWN) { - int max_events_per_sec = 35; - try { - max_events_per_sec = Integer.parseInt(SystemProperties - .get("windowsmgr.max_events_per_sec")); - if (max_events_per_sec < 1) { - max_events_per_sec = 35; - } - } catch (NumberFormatException e) { + // Send windows to native code. + mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray()); + + // Clear the list in preparation for the next round. + // Also avoids keeping InputChannel objects referenced unnecessarily. + mTempInputWindows.clear(); + } + + /* Provides feedback for a virtual key down. */ + public void virtualKeyDownFeedback() { + synchronized (mWindowMap) { + mPolicy.performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false); } - mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec; } - - /* - * Throttle events to minimize CPU usage when there's a flood of events - * e.g. constant contact with the screen + + /* Notifies that an app switch key (BACK / HOME) has just been pressed. + * This essentially starts a .5 second timeout for the application to process + * subsequent input events while waiting for the app switch to occur. If it takes longer + * than this, the pending events will be dropped. */ - if (action == MotionEvent.ACTION_MOVE) { - long nextEventTime = mLastTouchEventTime + mMinWaitTimeBetweenTouchEvents; - long now = SystemClock.uptimeMillis(); - if (now < nextEventTime) { - try { - Thread.sleep(nextEventTime - now); - } catch (InterruptedException e) { - } - mLastTouchEventTime = nextEventTime; - } else { - mLastTouchEventTime = now; - } + public void notifyAppSwitchComing() { + // TODO Not implemented yet. Should go in the native side. } - - if (MEASURE_LATENCY) { - lt.sample("5 in dispatchPointer ", System.nanoTime() - eventTimeNano); + + /* Notifies that the lid switch changed state. */ + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); } - - synchronized(mWindowMap) { - if (!target.isVisibleLw()) { - // During this motion dispatch, the target window has become - // invisible. - dispatchPointerElsewhereLocked(null, null, ev, ev.getEventTime(), false); - if (qev != null) { - mQueue.recycleEvent(qev); - } - ev.recycle(); - return INJECT_SUCCEEDED; - } - - if (qev != null && action == MotionEvent.ACTION_MOVE) { - mKeyWaiter.bindTargetWindowLocked(target, - KeyWaiter.RETURN_PENDING_POINTER, qev); - ev = null; - } else { - if (action == MotionEvent.ACTION_DOWN) { - WindowState out = mKeyWaiter.mOutsideTouchTargets; - if (out != null) { - MotionEvent oev = MotionEvent.obtain(ev); - oev.setAction(MotionEvent.ACTION_OUTSIDE); - do { - final Rect frame = out.mFrame; - oev.offsetLocation(-(float)frame.left, -(float)frame.top); - try { - out.mClient.dispatchPointer(oev, eventTime, false); - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during outside motion dispatch: " + out); - } - oev.offsetLocation((float)frame.left, (float)frame.top); - out = out.mNextOutsideTouch; - } while (out != null); - mKeyWaiter.mOutsideTouchTargets = null; + + /* Provides an opportunity for the window manager policy to intercept early key + * processing as soon as the key has been read from the device. */ + public int interceptKeyBeforeQueueing(long whenNanos, int keyCode, boolean down, + int policyFlags, boolean isScreenOn) { + return mPolicy.interceptKeyBeforeQueueing(whenNanos, + keyCode, down, policyFlags, isScreenOn); + } + + /* Provides an opportunity for the window manager policy to process a key before + * ordinary dispatch. */ + public boolean interceptKeyBeforeDispatching(InputChannel focus, + int action, int flags, int keyCode, int metaState, int repeatCount, + int policyFlags) { + WindowState windowState = getWindowStateForInputChannel(focus); + return mPolicy.interceptKeyBeforeDispatching(windowState, action, flags, + keyCode, metaState, repeatCount, policyFlags); + } + + /* Called when the current input focus changes. + * Layer assignment is assumed to be complete by the time this is called. + */ + public void setInputFocusLw(WindowState newWindow) { + if (DEBUG_INPUT) { + Slog.d(TAG, "Input focus has changed to " + newWindow); + } + + if (newWindow != mInputFocus) { + if (newWindow != null && newWindow.canReceiveKeys()) { + // If the new input focus is an error window or appears above the current + // input focus, preempt any pending synchronous dispatch so that we can + // start delivering events to the new input focus as soon as possible. + if ((newWindow.mAttrs.flags & FLAG_SYSTEM_ERROR) != 0) { + if (DEBUG_INPUT) { + Slog.v(TAG, "New SYSTEM_ERROR window; resetting state"); + } + preemptInputDispatchLw(); + } else if (mInputFocus != null && newWindow.mLayer > mInputFocus.mLayer) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Transferring focus to new window at higher layer: " + + "old win layer=" + mInputFocus.mLayer + + ", new win layer=" + newWindow.mLayer); + } + preemptInputDispatchLw(); } + + // Displaying a window implicitly causes dispatching to be unpaused. + // This is to protect against bugs if someone pauses dispatching but + // forgets to resume. + newWindow.mToken.paused = false; } - - dispatchPointerElsewhereLocked(target, null, ev, ev.getEventTime(), false); - - final Rect frame = target.mFrame; - ev.offsetLocation(-(float)frame.left, -(float)frame.top); - mKeyWaiter.bindTargetWindowLocked(target); + + mInputFocus = newWindow; + updateInputWindowsLw(); } } - - // finally offset the event to the target's coordinate system and - // dispatch the event. - try { - if (DEBUG_INPUT || DEBUG_FOCUS || WindowManagerPolicy.WATCH_POINTER) { - Slog.v(TAG, "Delivering pointer " + qev + " to " + target); - } - - if (MEASURE_LATENCY) { - lt.sample("6 before svr->client ipc ", System.nanoTime() - eventTimeNano); - } - - target.mClient.dispatchPointer(ev, eventTime, true); - - if (MEASURE_LATENCY) { - lt.sample("7 after svr->client ipc ", System.nanoTime() - eventTimeNano); - } - return INJECT_SUCCEEDED; - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during motion dispatch: " + target); - mKeyWaiter.mMotionTarget = null; - try { - removeWindow(target.mSession, target.mClient); - } catch (java.util.NoSuchElementException ex) { - // This will happen if the window has already been - // removed. - } + + /* Tells the dispatcher to stop waiting for its current synchronous event targets. + * Essentially, just makes those dispatches asynchronous so a new dispatch cycle + * can begin. + */ + private void preemptInputDispatchLw() { + mInputManager.preemptInputDispatch(); } - return INJECT_FAILED; - } - - /** - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - private int dispatchTrackball(QueuedEvent qev, MotionEvent ev, int pid, int uid) { - if (DEBUG_INPUT) Slog.v( - TAG, "dispatchTrackball [" + ev.getAction() +"] <" + ev.getX() + ", " + ev.getY() + ">"); - - Object focusObj = mKeyWaiter.waitForNextEventTarget(null, qev, - ev, false, false, pid, uid); - if (focusObj == null) { - Slog.w(TAG, "No focus window, dropping trackball: " + ev); - if (qev != null) { - mQueue.recycleEvent(qev); + + public void setFocusedAppLw(AppWindowToken newApp) { + // Focused app has changed. + if (newApp == null) { + mInputManager.setFocusedApplication(null); + } else { + mTempInputApplication.name = newApp.toString(); + mTempInputApplication.dispatchingTimeoutNanos = + newApp.inputDispatchingTimeoutNanos; + mTempInputApplication.token = newApp; + + mInputManager.setFocusedApplication(mTempInputApplication); } - ev.recycle(); - return INJECT_FAILED; } - if (focusObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { - if (qev != null) { - mQueue.recycleEvent(qev); + + public void pauseDispatchingLw(WindowToken window) { + if (! window.paused) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Pausing WindowToken " + window); + } + + window.paused = true; + updateInputWindowsLw(); } - ev.recycle(); - return INJECT_SUCCEEDED; } - - WindowState focus = (WindowState)focusObj; - - if (uid != 0 && uid != focus.mSession.mUid) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, pid, uid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting key event from pid " - + pid + " uid " + uid + " to window " + focus - + " owned by uid " + focus.mSession.mUid); - if (qev != null) { - mQueue.recycleEvent(qev); + + public void resumeDispatchingLw(WindowToken window) { + if (window.paused) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Resuming WindowToken " + window); } - ev.recycle(); - return INJECT_NO_PERMISSION; + + window.paused = false; + updateInputWindowsLw(); } } - - final long eventTime = ev.getEventTime(); - - synchronized(mWindowMap) { - if (qev != null && ev.getAction() == MotionEvent.ACTION_MOVE) { - mKeyWaiter.bindTargetWindowLocked(focus, - KeyWaiter.RETURN_PENDING_TRACKBALL, qev); - // We don't deliver movement events to the client, we hold - // them and wait for them to call back. - ev = null; - } else { - mKeyWaiter.bindTargetWindowLocked(focus); + + public void freezeInputDispatchingLw() { + if (! mInputDispatchFrozen) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Freezing input dispatching"); + } + + mInputDispatchFrozen = true; + updateInputDispatchModeLw(); } } - - try { - focus.mClient.dispatchTrackball(ev, eventTime, true); - return INJECT_SUCCEEDED; - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during key dispatch: " + focus); - try { - removeWindow(focus.mSession, focus.mClient); - } catch (java.util.NoSuchElementException ex) { - // This will happen if the window has already been - // removed. + + public void thawInputDispatchingLw() { + if (mInputDispatchFrozen) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Thawing input dispatching"); + } + + mInputDispatchFrozen = false; + updateInputDispatchModeLw(); } } - - return INJECT_FAILED; - } - - /** - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - private int dispatchKey(KeyEvent event, int pid, int uid) { - if (DEBUG_INPUT) Slog.v(TAG, "Dispatch key: " + event); - - Object focusObj = mKeyWaiter.waitForNextEventTarget(event, null, - null, false, false, pid, uid); - if (focusObj == null) { - Slog.w(TAG, "No focus window, dropping: " + event); - return INJECT_FAILED; - } - if (focusObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) { - return INJECT_SUCCEEDED; - } - - // Okay we have finished waiting for the last event to be processed. - // First off, if this is a repeat event, check to see if there is - // a corresponding up event in the queue. If there is, we will - // just drop the repeat, because it makes no sense to repeat after - // the user has released a key. (This is especially important for - // long presses.) - if (event.getRepeatCount() > 0 && mQueue.hasKeyUpEvent(event)) { - return INJECT_SUCCEEDED; - } - - WindowState focus = (WindowState)focusObj; - - if (DEBUG_INPUT) Slog.v( - TAG, "Dispatching to " + focus + ": " + event); - - if (uid != 0 && uid != focus.mSession.mUid) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, pid, uid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting key event from pid " - + pid + " uid " + uid + " to window " + focus - + " owned by uid " + focus.mSession.mUid); - return INJECT_NO_PERMISSION; + + public void setEventDispatchingLw(boolean enabled) { + if (mInputDispatchEnabled != enabled) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Setting event dispatching to " + enabled); + } + + mInputDispatchEnabled = enabled; + updateInputDispatchModeLw(); } } - - synchronized(mWindowMap) { - mKeyWaiter.bindTargetWindowLocked(focus); - } - - // NOSHIP extra state logging - mKeyWaiter.recordDispatchState(event, focus); - // END NOSHIP - - try { - if (DEBUG_INPUT || DEBUG_FOCUS) { - Slog.v(TAG, "Delivering key " + event.getKeyCode() - + " to " + focus); - } - focus.mClient.dispatchKey(event); - return INJECT_SUCCEEDED; - } catch (android.os.RemoteException e) { - Slog.i(TAG, "WINDOW DIED during key dispatch: " + focus); - try { - removeWindow(focus.mSession, focus.mClient); - } catch (java.util.NoSuchElementException ex) { - // This will happen if the window has already been - // removed. - } + + private void updateInputDispatchModeLw() { + mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen); } - - return INJECT_FAILED; } public void pauseKeyDispatching(IBinder _token) { @@ -5439,7 +5396,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowToken token = mTokenMap.get(_token); if (token != null) { - mKeyWaiter.pauseDispatchingLocked(token); + mInputMonitor.pauseDispatchingLw(token); } } } @@ -5453,7 +5410,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowToken token = mTokenMap.get(_token); if (token != null) { - mKeyWaiter.resumeDispatchingLocked(token); + mInputMonitor.resumeDispatchingLw(token); } } } @@ -5465,12 +5422,14 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized (mWindowMap) { - mKeyWaiter.setEventDispatchingLocked(enabled); + mInputMonitor.setEventDispatchingLw(enabled); } } /** * Injects a keystroke event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the keystroke action. (Be sure to use * {@link SystemClock#uptimeMillis()} as the timebase.) @@ -5487,33 +5446,35 @@ public class WindowManagerService extends IWindowManager.Stub int metaState = ev.getMetaState(); int deviceId = ev.getDeviceId(); int scancode = ev.getScanCode(); + int source = ev.getSource(); + + if (source == InputDevice.SOURCE_UNKNOWN) { + source = InputDevice.SOURCE_KEYBOARD; + } if (eventTime == 0) eventTime = SystemClock.uptimeMillis(); if (downTime == 0) downTime = eventTime; KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, - deviceId, scancode, KeyEvent.FLAG_FROM_SYSTEM); + deviceId, scancode, KeyEvent.FLAG_FROM_SYSTEM, source); final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchKey(newEvent, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); - } + + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + Binder.restoreCallingIdentity(ident); - switch (result) { - case INJECT_NO_PERMISSION: - throw new SecurityException( - "Injecting to another application requires INJECT_EVENTS permission"); - case INJECT_SUCCEEDED: - return true; - } - return false; + return reportInjectionResult(result); } /** * Inject a pointer (touch) event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the pointer (touch) action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -5525,23 +5486,25 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchPointer(null, ev, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + MotionEvent newEvent = MotionEvent.obtain(ev); + if ((newEvent.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + newEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); } + + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + Binder.restoreCallingIdentity(ident); - switch (result) { - case INJECT_NO_PERMISSION: - throw new SecurityException( - "Injecting to another application requires INJECT_EVENTS permission"); - case INJECT_SUCCEEDED: - return true; - } - return false; + return reportInjectionResult(result); } /** * Inject a trackball (navigation device) event into the UI. + * Even when sync is false, this method may block while waiting for current + * input events to be dispatched. * * @param ev A motion event describing the trackball action. (As noted in * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use @@ -5553,19 +5516,59 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchTrackball(null, ev, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + MotionEvent newEvent = MotionEvent.obtain(ev); + if ((newEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) { + newEvent.setSource(InputDevice.SOURCE_TRACKBALL); } + + final int result = mInputManager.injectInputEvent(newEvent, pid, uid, + sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH + : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, + INJECTION_TIMEOUT_MILLIS); + Binder.restoreCallingIdentity(ident); + return reportInjectionResult(result); + } + + /** + * Inject an input event into the UI without waiting for dispatch to commence. + * This variant is useful for fire-and-forget input event injection. It does not + * block any longer than it takes to enqueue the input event. + * + * @param ev An input event. (Be sure to set the input source correctly.) + * @return Returns true if event was dispatched, false if it was dropped for any reason + */ + public boolean injectInputEventNoWait(InputEvent ev) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + + final int result = mInputManager.injectInputEvent(ev, pid, uid, + InputManager.INPUT_EVENT_INJECTION_SYNC_NONE, + INJECTION_TIMEOUT_MILLIS); + + Binder.restoreCallingIdentity(ident); + return reportInjectionResult(result); + } + + private boolean reportInjectionResult(int result) { switch (result) { - case INJECT_NO_PERMISSION: + case InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED: + Slog.w(TAG, "Input event injection permission denied."); throw new SecurityException( "Injecting to another application requires INJECT_EVENTS permission"); - case INJECT_SUCCEEDED: + case InputManager.INPUT_EVENT_INJECTION_SUCCEEDED: + //Slog.v(TAG, "Input event injection succeeded."); return true; + case InputManager.INPUT_EVENT_INJECTION_TIMED_OUT: + Slog.w(TAG, "Input event injection timed out."); + return false; + case InputManager.INPUT_EVENT_INJECTION_FAILED: + default: + Slog.w(TAG, "Input event injection failed."); + return false; } - return false; } private WindowState getFocusedWindow() { @@ -5578,894 +5581,6 @@ public class WindowManagerService extends IWindowManager.Stub return mCurrentFocus; } - /** - * This class holds the state for dispatching key events. This state - * is protected by the KeyWaiter instance, NOT by the window lock. You - * can be holding the main window lock while acquire the KeyWaiter lock, - * but not the other way around. - */ - final class KeyWaiter { - // NOSHIP debugging - public class DispatchState { - private KeyEvent event; - private WindowState focus; - private long time; - private WindowState lastWin; - private IBinder lastBinder; - private boolean finished; - private boolean gotFirstWindow; - private boolean eventDispatching; - private long timeToSwitch; - private boolean wasFrozen; - private boolean focusPaused; - private WindowState curFocus; - - DispatchState(KeyEvent theEvent, WindowState theFocus) { - focus = theFocus; - event = theEvent; - time = System.currentTimeMillis(); - // snapshot KeyWaiter state - lastWin = mLastWin; - lastBinder = mLastBinder; - finished = mFinished; - gotFirstWindow = mGotFirstWindow; - eventDispatching = mEventDispatching; - timeToSwitch = mTimeToSwitch; - wasFrozen = mWasFrozen; - curFocus = mCurrentFocus; - // cache the paused state at ctor time as well - if (theFocus == null || theFocus.mToken == null) { - focusPaused = false; - } else { - focusPaused = theFocus.mToken.paused; - } - } - - public String toString() { - return "{{" + event + " to " + focus + " @ " + time - + " lw=" + lastWin + " lb=" + lastBinder - + " fin=" + finished + " gfw=" + gotFirstWindow - + " ed=" + eventDispatching + " tts=" + timeToSwitch - + " wf=" + wasFrozen + " fp=" + focusPaused - + " mcf=" + curFocus + "}}"; - } - }; - private DispatchState mDispatchState = null; - public void recordDispatchState(KeyEvent theEvent, WindowState theFocus) { - mDispatchState = new DispatchState(theEvent, theFocus); - } - // END NOSHIP - - public static final int RETURN_NOTHING = 0; - public static final int RETURN_PENDING_POINTER = 1; - public static final int RETURN_PENDING_TRACKBALL = 2; - - final Object SKIP_TARGET_TOKEN = new Object(); - final Object CONSUMED_EVENT_TOKEN = new Object(); - - private WindowState mLastWin = null; - private IBinder mLastBinder = null; - private boolean mFinished = true; - private boolean mGotFirstWindow = false; - private boolean mEventDispatching = true; - private long mTimeToSwitch = 0; - /* package */ boolean mWasFrozen = false; - - // Target of Motion events - WindowState mMotionTarget; - - // Windows above the target who would like to receive an "outside" - // touch event for any down events outside of them. - WindowState mOutsideTouchTargets; - - /** - * Wait for the last event dispatch to complete, then find the next - * target that should receive the given event and wait for that one - * to be ready to receive it. - */ - Object waitForNextEventTarget(KeyEvent nextKey, QueuedEvent qev, - MotionEvent nextMotion, boolean isPointerEvent, - boolean failIfTimeout, int callingPid, int callingUid) { - long startTime = SystemClock.uptimeMillis(); - long keyDispatchingTimeout = 5 * 1000; - long waitedFor = 0; - - while (true) { - // Figure out which window we care about. It is either the - // last window we are waiting to have process the event or, - // if none, then the next window we think the event should go - // to. Note: we retrieve mLastWin outside of the lock, so - // it may change before we lock. Thus we must check it again. - WindowState targetWin = mLastWin; - boolean targetIsNew = targetWin == null; - if (DEBUG_INPUT) Slog.v( - TAG, "waitForLastKey: mFinished=" + mFinished + - ", mLastWin=" + mLastWin); - if (targetIsNew) { - Object target = findTargetWindow(nextKey, qev, nextMotion, - isPointerEvent, callingPid, callingUid); - if (target == SKIP_TARGET_TOKEN) { - // The user has pressed a special key, and we are - // dropping all pending events before it. - if (DEBUG_INPUT) Slog.v(TAG, "Skipping: " + nextKey - + " " + nextMotion); - return null; - } - if (target == CONSUMED_EVENT_TOKEN) { - if (DEBUG_INPUT) Slog.v(TAG, "Consumed: " + nextKey - + " " + nextMotion); - return target; - } - targetWin = (WindowState)target; - } - - AppWindowToken targetApp = null; - - // Now: is it okay to send the next event to this window? - synchronized (this) { - // First: did we come here based on the last window not - // being null, but it changed by the time we got here? - // If so, try again. - if (!targetIsNew && mLastWin == null) { - continue; - } - - // We never dispatch events if not finished with the - // last one, or the display is frozen. - if (mFinished && !mDisplayFrozen) { - // If event dispatching is disabled, then we - // just consume the events. - if (!mEventDispatching) { - if (DEBUG_INPUT) Slog.v(TAG, - "Skipping event; dispatching disabled: " - + nextKey + " " + nextMotion); - return null; - } - if (targetWin != null) { - // If this is a new target, and that target is not - // paused or unresponsive, then all looks good to - // handle the event. - if (targetIsNew && !targetWin.mToken.paused) { - return targetWin; - } - - // If we didn't find a target window, and there is no - // focused app window, then just eat the events. - } else if (mFocusedApp == null) { - if (DEBUG_INPUT) Slog.v(TAG, - "Skipping event; no focused app: " - + nextKey + " " + nextMotion); - return null; - } - } - - if (DEBUG_INPUT) Slog.v( - TAG, "Waiting for last key in " + mLastBinder - + " target=" + targetWin - + " mFinished=" + mFinished - + " mDisplayFrozen=" + mDisplayFrozen - + " targetIsNew=" + targetIsNew - + " paused=" - + (targetWin != null ? targetWin.mToken.paused : false) - + " mFocusedApp=" + mFocusedApp - + " mCurrentFocus=" + mCurrentFocus); - - targetApp = targetWin != null - ? targetWin.mAppToken : mFocusedApp; - - long curTimeout = keyDispatchingTimeout; - if (mTimeToSwitch != 0) { - long now = SystemClock.uptimeMillis(); - if (mTimeToSwitch <= now) { - // If an app switch key has been pressed, and we have - // waited too long for the current app to finish - // processing keys, then wait no more! - doFinishedKeyLocked(false); - continue; - } - long switchTimeout = mTimeToSwitch - now; - if (curTimeout > switchTimeout) { - curTimeout = switchTimeout; - } - } - - try { - // after that continue - // processing keys, so we don't get stuck. - if (DEBUG_INPUT) Slog.v( - TAG, "Waiting for key dispatch: " + curTimeout); - wait(curTimeout); - if (DEBUG_INPUT) Slog.v(TAG, "Finished waiting @" - + SystemClock.uptimeMillis() + " startTime=" - + startTime + " switchTime=" + mTimeToSwitch - + " target=" + targetWin + " mLW=" + mLastWin - + " mLB=" + mLastBinder + " fin=" + mFinished - + " mCurrentFocus=" + mCurrentFocus); - } catch (InterruptedException e) { - } - } - - // If we were frozen during configuration change, restart the - // timeout checks from now; otherwise look at whether we timed - // out before awakening. - if (mWasFrozen) { - waitedFor = 0; - mWasFrozen = false; - } else { - waitedFor = SystemClock.uptimeMillis() - startTime; - } - - if (waitedFor >= keyDispatchingTimeout && mTimeToSwitch == 0) { - IApplicationToken at = null; - synchronized (this) { - Slog.w(TAG, "Key dispatching timed out sending to " + - (targetWin != null ? targetWin.mAttrs.getTitle() - : "<null>: no window ready for key dispatch")); - // NOSHIP debugging - Slog.w(TAG, "Previous dispatch state: " + mDispatchState); - Slog.w(TAG, "Current dispatch state: " + - new DispatchState(nextKey, targetWin)); - // END NOSHIP - //dump(); - if (targetWin != null) { - at = targetWin.getAppToken(); - } else if (targetApp != null) { - at = targetApp.appToken; - } - } - - boolean abort = true; - if (at != null) { - try { - long timeout = at.getKeyDispatchingTimeout(); - if (timeout > waitedFor) { - // we did not wait the proper amount of time for this application. - // set the timeout to be the real timeout and wait again. - keyDispatchingTimeout = timeout - waitedFor; - continue; - } else { - abort = at.keyDispatchingTimedOut(); - } - } catch (RemoteException ex) { - } - } - - synchronized (this) { - if (abort && (mLastWin == targetWin || targetWin == null)) { - mFinished = true; - if (mLastWin != null) { - if (DEBUG_INPUT) Slog.v(TAG, - "Window " + mLastWin + - " timed out on key input"); - if (mLastWin.mToken.paused) { - Slog.w(TAG, "Un-pausing dispatching to this window"); - mLastWin.mToken.paused = false; - } - } - if (mMotionTarget == targetWin) { - mMotionTarget = null; - } - mLastWin = null; - mLastBinder = null; - if (failIfTimeout || targetWin == null) { - return null; - } - } else { - Slog.w(TAG, "Continuing to wait for key to be dispatched"); - startTime = SystemClock.uptimeMillis(); - } - } - } - } - } - - Object findTargetWindow(KeyEvent nextKey, QueuedEvent qev, - MotionEvent nextMotion, boolean isPointerEvent, - int callingPid, int callingUid) { - mOutsideTouchTargets = null; - - if (nextKey != null) { - // Find the target window for a normal key event. - final int keycode = nextKey.getKeyCode(); - final int repeatCount = nextKey.getRepeatCount(); - final boolean down = nextKey.getAction() != KeyEvent.ACTION_UP; - boolean dispatch = mKeyWaiter.checkShouldDispatchKey(keycode); - - if (!dispatch) { - if (callingUid == 0 || - mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, - callingPid, callingUid) - == PackageManager.PERMISSION_GRANTED) { - mPolicy.interceptKeyTi(null, keycode, - nextKey.getMetaState(), down, repeatCount, - nextKey.getFlags()); - } - Slog.w(TAG, "Event timeout during app switch: dropping " - + nextKey); - return SKIP_TARGET_TOKEN; - } - - // System.out.println("##### [" + SystemClock.uptimeMillis() + "] WindowManagerService.dispatchKey(" + keycode + ", " + down + ", " + repeatCount + ")"); - - WindowState focus = null; - synchronized(mWindowMap) { - focus = getFocusedWindowLocked(); - } - - wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - - if (callingUid == 0 || - (focus != null && callingUid == focus.mSession.mUid) || - mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, - callingPid, callingUid) - == PackageManager.PERMISSION_GRANTED) { - if (mPolicy.interceptKeyTi(focus, - keycode, nextKey.getMetaState(), down, repeatCount, - nextKey.getFlags())) { - return CONSUMED_EVENT_TOKEN; - } - } - - return focus; - - } else if (!isPointerEvent) { - boolean dispatch = mKeyWaiter.checkShouldDispatchKey(-1); - if (!dispatch) { - Slog.w(TAG, "Event timeout during app switch: dropping trackball " - + nextMotion); - return SKIP_TARGET_TOKEN; - } - - WindowState focus = null; - synchronized(mWindowMap) { - focus = getFocusedWindowLocked(); - } - - wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - return focus; - } - - if (nextMotion == null) { - return SKIP_TARGET_TOKEN; - } - - boolean dispatch = mKeyWaiter.checkShouldDispatchKey( - KeyEvent.KEYCODE_UNKNOWN); - if (!dispatch) { - Slog.w(TAG, "Event timeout during app switch: dropping pointer " - + nextMotion); - return SKIP_TARGET_TOKEN; - } - - // Find the target window for a pointer event. - int action = nextMotion.getAction(); - final float xf = nextMotion.getX(); - final float yf = nextMotion.getY(); - final long eventTime = nextMotion.getEventTime(); - - final boolean screenWasOff = qev != null - && (qev.flags&WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0; - - WindowState target = null; - - synchronized(mWindowMap) { - synchronized (this) { - if (action == MotionEvent.ACTION_DOWN) { - if (mMotionTarget != null) { - // this is weird, we got a pen down, but we thought it was - // already down! - // XXX: We should probably send an ACTION_UP to the current - // target. - Slog.w(TAG, "Pointer down received while already down in: " - + mMotionTarget); - mMotionTarget = null; - } - - // ACTION_DOWN is special, because we need to lock next events to - // the window we'll land onto. - final int x = (int)xf; - final int y = (int)yf; - - final ArrayList windows = mWindows; - final int N = windows.size(); - WindowState topErrWindow = null; - final Rect tmpRect = mTempRect; - for (int i=N-1; i>=0; i--) { - WindowState child = (WindowState)windows.get(i); - //Slog.i(TAG, "Checking dispatch to: " + child); - final int flags = child.mAttrs.flags; - if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) { - if (topErrWindow == null) { - topErrWindow = child; - } - } - if (!child.isVisibleLw()) { - //Slog.i(TAG, "Not visible!"); - continue; - } - if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { - //Slog.i(TAG, "Not touchable!"); - if ((flags & WindowManager.LayoutParams - .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { - child.mNextOutsideTouch = mOutsideTouchTargets; - mOutsideTouchTargets = child; - } - continue; - } - tmpRect.set(child.mFrame); - if (child.mTouchableInsets == ViewTreeObserver - .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) { - // The touch is inside of the window if it is - // inside the frame, AND the content part of that - // frame that was given by the application. - tmpRect.left += child.mGivenContentInsets.left; - tmpRect.top += child.mGivenContentInsets.top; - tmpRect.right -= child.mGivenContentInsets.right; - tmpRect.bottom -= child.mGivenContentInsets.bottom; - } else if (child.mTouchableInsets == ViewTreeObserver - .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) { - // The touch is inside of the window if it is - // inside the frame, AND the visible part of that - // frame that was given by the application. - tmpRect.left += child.mGivenVisibleInsets.left; - tmpRect.top += child.mGivenVisibleInsets.top; - tmpRect.right -= child.mGivenVisibleInsets.right; - tmpRect.bottom -= child.mGivenVisibleInsets.bottom; - } - final int touchFlags = flags & - (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); - if (tmpRect.contains(x, y) || touchFlags == 0) { - //Slog.i(TAG, "Using this target!"); - if (!screenWasOff || (flags & - WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) { - mMotionTarget = child; - } else { - //Slog.i(TAG, "Waking, skip!"); - mMotionTarget = null; - } - break; - } - - if ((flags & WindowManager.LayoutParams - .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { - child.mNextOutsideTouch = mOutsideTouchTargets; - mOutsideTouchTargets = child; - //Slog.i(TAG, "Adding to outside target list: " + child); - } - } - - // if there's an error window but it's not accepting - // focus (typically because it is not yet visible) just - // wait for it -- any other focused window may in fact - // be in ANR state. - if (topErrWindow != null && mMotionTarget != topErrWindow) { - mMotionTarget = null; - } - } - - target = mMotionTarget; - } - } - - wakeupIfNeeded(target, eventType(nextMotion)); - - // Pointer events are a little different -- if there isn't a - // target found for any event, then just drop it. - return target != null ? target : SKIP_TARGET_TOKEN; - } - - boolean checkShouldDispatchKey(int keycode) { - synchronized (this) { - if (mPolicy.isAppSwitchKeyTqTiLwLi(keycode)) { - mTimeToSwitch = 0; - return true; - } - if (mTimeToSwitch != 0 - && mTimeToSwitch < SystemClock.uptimeMillis()) { - return false; - } - return true; - } - } - - void bindTargetWindowLocked(WindowState win, - int pendingWhat, QueuedEvent pendingMotion) { - synchronized (this) { - bindTargetWindowLockedLocked(win, pendingWhat, pendingMotion); - } - } - - void bindTargetWindowLocked(WindowState win) { - synchronized (this) { - bindTargetWindowLockedLocked(win, RETURN_NOTHING, null); - } - } - - void bindTargetWindowLockedLocked(WindowState win, - int pendingWhat, QueuedEvent pendingMotion) { - mLastWin = win; - mLastBinder = win.mClient.asBinder(); - mFinished = false; - if (pendingMotion != null) { - final Session s = win.mSession; - if (pendingWhat == RETURN_PENDING_POINTER) { - releasePendingPointerLocked(s); - s.mPendingPointerMove = pendingMotion; - s.mPendingPointerWindow = win; - if (DEBUG_INPUT) Slog.v(TAG, - "bindTargetToWindow " + s.mPendingPointerMove); - } else if (pendingWhat == RETURN_PENDING_TRACKBALL) { - releasePendingTrackballLocked(s); - s.mPendingTrackballMove = pendingMotion; - s.mPendingTrackballWindow = win; - } - } - } - - void releasePendingPointerLocked(Session s) { - if (DEBUG_INPUT) Slog.v(TAG, - "releasePendingPointer " + s.mPendingPointerMove); - if (s.mPendingPointerMove != null) { - mQueue.recycleEvent(s.mPendingPointerMove); - s.mPendingPointerMove = null; - } - } - - void releasePendingTrackballLocked(Session s) { - if (s.mPendingTrackballMove != null) { - mQueue.recycleEvent(s.mPendingTrackballMove); - s.mPendingTrackballMove = null; - } - } - - void releaseMotionTarget(WindowState win) { - if (mMotionTarget == win) { - mMotionTarget = null; - } - } - - MotionEvent finishedKey(Session session, IWindow client, boolean force, - int returnWhat) { - if (DEBUG_INPUT) Slog.v( - TAG, "finishedKey: client=" + client + ", force=" + force); - - if (client == null) { - return null; - } - - MotionEvent res = null; - QueuedEvent qev = null; - WindowState win = null; - - synchronized (this) { - if (DEBUG_INPUT) Slog.v( - TAG, "finishedKey: client=" + client.asBinder() - + ", force=" + force + ", last=" + mLastBinder - + " (token=" + (mLastWin != null ? mLastWin.mToken : null) + ")"); - - if (returnWhat == RETURN_PENDING_POINTER) { - qev = session.mPendingPointerMove; - win = session.mPendingPointerWindow; - session.mPendingPointerMove = null; - session.mPendingPointerWindow = null; - } else if (returnWhat == RETURN_PENDING_TRACKBALL) { - qev = session.mPendingTrackballMove; - win = session.mPendingTrackballWindow; - session.mPendingTrackballMove = null; - session.mPendingTrackballWindow = null; - } - - if (mLastBinder == client.asBinder()) { - if (DEBUG_INPUT) Slog.v( - TAG, "finishedKey: last paused=" - + ((mLastWin != null) ? mLastWin.mToken.paused : "null")); - if (mLastWin != null && (!mLastWin.mToken.paused || force - || !mEventDispatching)) { - doFinishedKeyLocked(true); - } else { - // Make sure to wake up anyone currently waiting to - // dispatch a key, so they can re-evaluate their - // current situation. - mFinished = true; - notifyAll(); - } - } - - if (qev != null) { - res = (MotionEvent)qev.event; - if (DEBUG_INPUT) Slog.v(TAG, - "Returning pending motion: " + res); - mQueue.recycleEvent(qev); - if (win != null && returnWhat == RETURN_PENDING_POINTER) { - res.offsetLocation(-win.mFrame.left, -win.mFrame.top); - } - } - } - - if (res != null && returnWhat == RETURN_PENDING_POINTER) { - synchronized (mWindowMap) { - dispatchPointerElsewhereLocked(win, win, res, res.getEventTime(), false); - } - } - - return res; - } - - void tickle() { - synchronized (this) { - notifyAll(); - } - } - - void handleNewWindowLocked(WindowState newWindow) { - if (!newWindow.canReceiveKeys()) { - return; - } - synchronized (this) { - if (DEBUG_INPUT) Slog.v( - TAG, "New key dispatch window: win=" - + newWindow.mClient.asBinder() - + ", last=" + mLastBinder - + " (token=" + (mLastWin != null ? mLastWin.mToken : null) - + "), finished=" + mFinished + ", paused=" - + newWindow.mToken.paused); - - // Displaying a window implicitly causes dispatching to - // be unpaused. (This is to protect against bugs if someone - // pauses dispatching but forgets to resume.) - newWindow.mToken.paused = false; - - mGotFirstWindow = true; - - if ((newWindow.mAttrs.flags & FLAG_SYSTEM_ERROR) != 0) { - if (DEBUG_INPUT) Slog.v(TAG, - "New SYSTEM_ERROR window; resetting state"); - mLastWin = null; - mLastBinder = null; - mMotionTarget = null; - mFinished = true; - } else if (mLastWin != null) { - // If the new window is above the window we are - // waiting on, then stop waiting and let key dispatching - // start on the new guy. - if (DEBUG_INPUT) Slog.v( - TAG, "Last win layer=" + mLastWin.mLayer - + ", new win layer=" + newWindow.mLayer); - if (newWindow.mLayer >= mLastWin.mLayer) { - // The new window is above the old; finish pending input to the last - // window and start directing it to the new one. - mLastWin.mToken.paused = false; - doFinishedKeyLocked(false); // does a notifyAll() - return; - } - } - - // Now that we've put a new window state in place, make the event waiter - // take notice and retarget its attentions. - notifyAll(); - } - } - - void pauseDispatchingLocked(WindowToken token) { - synchronized (this) - { - if (DEBUG_INPUT) Slog.v(TAG, "Pausing WindowToken " + token); - token.paused = true; - - /* - if (mLastWin != null && !mFinished && mLastWin.mBaseLayer <= layer) { - mPaused = true; - } else { - if (mLastWin == null) { - Slog.i(TAG, "Key dispatching not paused: no last window."); - } else if (mFinished) { - Slog.i(TAG, "Key dispatching not paused: finished last key."); - } else { - Slog.i(TAG, "Key dispatching not paused: window in higher layer."); - } - } - */ - } - } - - void resumeDispatchingLocked(WindowToken token) { - synchronized (this) { - if (token.paused) { - if (DEBUG_INPUT) Slog.v( - TAG, "Resuming WindowToken " + token - + ", last=" + mLastBinder - + " (token=" + (mLastWin != null ? mLastWin.mToken : null) - + "), finished=" + mFinished + ", paused=" - + token.paused); - token.paused = false; - if (mLastWin != null && mLastWin.mToken == token && mFinished) { - doFinishedKeyLocked(false); - } else { - notifyAll(); - } - } - } - } - - void setEventDispatchingLocked(boolean enabled) { - synchronized (this) { - mEventDispatching = enabled; - notifyAll(); - } - } - - void appSwitchComing() { - synchronized (this) { - // Don't wait for more than .5 seconds for app to finish - // processing the pending events. - long now = SystemClock.uptimeMillis() + 500; - if (DEBUG_INPUT) Slog.v(TAG, "appSwitchComing: " + now); - if (mTimeToSwitch == 0 || now < mTimeToSwitch) { - mTimeToSwitch = now; - } - notifyAll(); - } - } - - private final void doFinishedKeyLocked(boolean force) { - if (mLastWin != null) { - releasePendingPointerLocked(mLastWin.mSession); - releasePendingTrackballLocked(mLastWin.mSession); - } - - if (force || mLastWin == null || !mLastWin.mToken.paused - || !mLastWin.isVisibleLw()) { - // If the current window has been paused, we aren't -really- - // finished... so let the waiters still wait. - mLastWin = null; - mLastBinder = null; - } - mFinished = true; - notifyAll(); - } - } - - private class KeyQ extends KeyInputQueue - implements KeyInputQueue.FilterCallback { - PowerManager.WakeLock mHoldingScreen; - - KeyQ() { - super(mContext, WindowManagerService.this); - PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, - "KEEP_SCREEN_ON_FLAG"); - mHoldingScreen.setReferenceCounted(false); - } - - @Override - boolean preprocessEvent(InputDevice device, RawInputEvent event) { - if (mPolicy.preprocessInputEventTq(event)) { - return true; - } - - switch (event.type) { - case RawInputEvent.EV_KEY: { - // XXX begin hack - if (DEBUG) { - if (event.keycode == KeyEvent.KEYCODE_G) { - if (event.value != 0) { - // G down - mPolicy.screenTurnedOff(WindowManagerPolicy.OFF_BECAUSE_OF_USER); - } - return false; - } - if (event.keycode == KeyEvent.KEYCODE_D) { - if (event.value != 0) { - //dump(); - } - return false; - } - } - // XXX end hack - - boolean screenIsOff = !mPowerManager.isScreenOn(); - boolean screenIsDim = !mPowerManager.isScreenBright(); - int actions = mPolicy.interceptKeyTq(event, !screenIsOff); - - if ((actions & WindowManagerPolicy.ACTION_GO_TO_SLEEP) != 0) { - mPowerManager.goToSleep(event.when); - } - - if (screenIsOff) { - event.flags |= WindowManagerPolicy.FLAG_WOKE_HERE; - } - if (screenIsDim) { - event.flags |= WindowManagerPolicy.FLAG_BRIGHT_HERE; - } - if ((actions & WindowManagerPolicy.ACTION_POKE_USER_ACTIVITY) != 0) { - mPowerManager.userActivity(event.when, false, - LocalPowerManager.BUTTON_EVENT, false); - } - - if ((actions & WindowManagerPolicy.ACTION_PASS_TO_USER) != 0) { - if (event.value != 0 && mPolicy.isAppSwitchKeyTqTiLwLi(event.keycode)) { - filterQueue(this); - mKeyWaiter.appSwitchComing(); - } - return true; - } else { - return false; - } - } - - case RawInputEvent.EV_REL: { - boolean screenIsOff = !mPowerManager.isScreenOn(); - boolean screenIsDim = !mPowerManager.isScreenBright(); - if (screenIsOff) { - if (!mPolicy.isWakeRelMovementTq(event.deviceId, - device.classes, event)) { - //Slog.i(TAG, "dropping because screenIsOff and !isWakeKey"); - return false; - } - event.flags |= WindowManagerPolicy.FLAG_WOKE_HERE; - } - if (screenIsDim) { - event.flags |= WindowManagerPolicy.FLAG_BRIGHT_HERE; - } - return true; - } - - case RawInputEvent.EV_ABS: { - boolean screenIsOff = !mPowerManager.isScreenOn(); - boolean screenIsDim = !mPowerManager.isScreenBright(); - if (screenIsOff) { - if (!mPolicy.isWakeAbsMovementTq(event.deviceId, - device.classes, event)) { - //Slog.i(TAG, "dropping because screenIsOff and !isWakeKey"); - return false; - } - event.flags |= WindowManagerPolicy.FLAG_WOKE_HERE; - } - if (screenIsDim) { - event.flags |= WindowManagerPolicy.FLAG_BRIGHT_HERE; - } - return true; - } - - default: - return true; - } - } - - public int filterEvent(QueuedEvent ev) { - switch (ev.classType) { - case RawInputEvent.CLASS_KEYBOARD: - KeyEvent ke = (KeyEvent)ev.event; - if (mPolicy.isMovementKeyTi(ke.getKeyCode())) { - Slog.w(TAG, "Dropping movement key during app switch: " - + ke.getKeyCode() + ", action=" + ke.getAction()); - return FILTER_REMOVE; - } - return FILTER_ABORT; - default: - return FILTER_KEEP; - } - } - - /** - * Must be called with the main window manager lock held. - */ - void setHoldScreenLocked(boolean holding) { - boolean state = mHoldingScreen.isHeld(); - if (holding != state) { - if (holding) { - mHoldingScreen.acquire(); - } else { - mPolicy.screenOnStoppedLw(); - mHoldingScreen.release(); - } - } - } - } - public boolean detectSafeMode() { mSafeMode = mPolicy.detectSafeMode(); return mSafeMode; @@ -6475,219 +5590,6 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.systemReady(); } - private final class InputDispatcherThread extends Thread { - // Time to wait when there is nothing to do: 9999 seconds. - static final int LONG_WAIT=9999*1000; - - public InputDispatcherThread() { - super("InputDispatcher"); - } - - @Override - public void run() { - while (true) { - try { - process(); - } catch (Exception e) { - Slog.e(TAG, "Exception in input dispatcher", e); - } - } - } - - private void process() { - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY); - - // The last key event we saw - KeyEvent lastKey = null; - - // Last keydown time for auto-repeating keys - long lastKeyTime = SystemClock.uptimeMillis(); - long nextKeyTime = lastKeyTime+LONG_WAIT; - long downTime = 0; - - // How many successive repeats we generated - int keyRepeatCount = 0; - - // Need to report that configuration has changed? - boolean configChanged = false; - - while (true) { - long curTime = SystemClock.uptimeMillis(); - - if (DEBUG_INPUT) Slog.v( - TAG, "Waiting for next key: now=" + curTime - + ", repeat @ " + nextKeyTime); - - // Retrieve next event, waiting only as long as the next - // repeat timeout. If the configuration has changed, then - // don't wait at all -- we'll report the change as soon as - // we have processed all events. - QueuedEvent ev = mQueue.getEvent( - (int)((!configChanged && curTime < nextKeyTime) - ? (nextKeyTime-curTime) : 0)); - - if (DEBUG_INPUT && ev != null) Slog.v( - TAG, "Event: type=" + ev.classType + " data=" + ev.event); - - if (MEASURE_LATENCY) { - lt.sample("2 got event ", System.nanoTime() - ev.whenNano); - } - - if (lastKey != null && !mPolicy.allowKeyRepeat()) { - // cancel key repeat at the request of the policy. - lastKey = null; - downTime = 0; - lastKeyTime = curTime; - nextKeyTime = curTime + LONG_WAIT; - } - try { - if (ev != null) { - curTime = SystemClock.uptimeMillis(); - int eventType; - if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) { - eventType = eventType((MotionEvent)ev.event); - } else if (ev.classType == RawInputEvent.CLASS_KEYBOARD || - ev.classType == RawInputEvent.CLASS_TRACKBALL) { - eventType = LocalPowerManager.BUTTON_EVENT; - } else { - eventType = LocalPowerManager.OTHER_EVENT; - } - try { - if ((curTime - mLastBatteryStatsCallTime) - >= MIN_TIME_BETWEEN_USERACTIVITIES) { - mLastBatteryStatsCallTime = curTime; - mBatteryStats.noteInputEvent(); - } - } catch (RemoteException e) { - // Ignore - } - - if (ev.classType == RawInputEvent.CLASS_CONFIGURATION_CHANGED) { - // do not wake screen in this case - } else if (eventType != TOUCH_EVENT - && eventType != LONG_TOUCH_EVENT - && eventType != CHEEK_EVENT) { - mPowerManager.userActivity(curTime, false, - eventType, false); - } else if (mLastTouchEventType != eventType - || (curTime - mLastUserActivityCallTime) - >= MIN_TIME_BETWEEN_USERACTIVITIES) { - mLastUserActivityCallTime = curTime; - mLastTouchEventType = eventType; - mPowerManager.userActivity(curTime, false, - eventType, false); - } - - switch (ev.classType) { - case RawInputEvent.CLASS_KEYBOARD: - KeyEvent ke = (KeyEvent)ev.event; - if (ke.isDown()) { - lastKeyTime = curTime; - if (lastKey != null && - ke.getKeyCode() == lastKey.getKeyCode()) { - keyRepeatCount++; - // Arbitrary long timeout to block - // repeating here since we know that - // the device driver takes care of it. - nextKeyTime = lastKeyTime + LONG_WAIT; - if (DEBUG_INPUT) Slog.v( - TAG, "Received repeated key down"); - } else { - downTime = curTime; - keyRepeatCount = 0; - nextKeyTime = lastKeyTime - + ViewConfiguration.getLongPressTimeout(); - if (DEBUG_INPUT) Slog.v( - TAG, "Received key down: first repeat @ " - + nextKeyTime); - } - lastKey = ke; - } else { - lastKey = null; - downTime = 0; - keyRepeatCount = 0; - // Arbitrary long timeout. - lastKeyTime = curTime; - nextKeyTime = curTime + LONG_WAIT; - if (DEBUG_INPUT) Slog.v( - TAG, "Received key up: ignore repeat @ " - + nextKeyTime); - } - if (keyRepeatCount > 0) { - dispatchKey(KeyEvent.changeTimeRepeat(ke, - ke.getEventTime(), keyRepeatCount), 0, 0); - } else { - dispatchKey(ke, 0, 0); - } - mQueue.recycleEvent(ev); - break; - case RawInputEvent.CLASS_TOUCHSCREEN: - //Slog.i(TAG, "Read next event " + ev); - dispatchPointer(ev, (MotionEvent)ev.event, 0, 0); - break; - case RawInputEvent.CLASS_TRACKBALL: - dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0); - break; - case RawInputEvent.CLASS_CONFIGURATION_CHANGED: - configChanged = true; - break; - default: - mQueue.recycleEvent(ev); - break; - } - - } else if (configChanged) { - configChanged = false; - sendNewConfiguration(); - - } else if (lastKey != null) { - curTime = SystemClock.uptimeMillis(); - - // Timeout occurred while key was down. If it is at or - // past the key repeat time, dispatch the repeat. - if (DEBUG_INPUT) Slog.v( - TAG, "Key timeout: repeat=" + nextKeyTime - + ", now=" + curTime); - if (curTime < nextKeyTime) { - continue; - } - - lastKeyTime = nextKeyTime; - nextKeyTime = nextKeyTime + KEY_REPEAT_DELAY; - keyRepeatCount++; - if (DEBUG_INPUT) Slog.v( - TAG, "Key repeat: count=" + keyRepeatCount - + ", next @ " + nextKeyTime); - KeyEvent newEvent; - if (downTime != 0 && (downTime - + ViewConfiguration.getLongPressTimeout()) - <= curTime) { - newEvent = KeyEvent.changeTimeRepeat(lastKey, - curTime, keyRepeatCount, - lastKey.getFlags() | KeyEvent.FLAG_LONG_PRESS); - downTime = 0; - } else { - newEvent = KeyEvent.changeTimeRepeat(lastKey, - curTime, keyRepeatCount); - } - dispatchKey(newEvent, 0, 0); - - } else { - curTime = SystemClock.uptimeMillis(); - - lastKeyTime = curTime; - nextKeyTime = curTime + LONG_WAIT; - } - - } catch (Exception e) { - Slog.e(TAG, - "Input thread received uncaught exception: " + e, e); - } - } - } - } - // ------------------------------------------------------------- // Client Session State // ------------------------------------------------------------- @@ -6703,20 +5605,6 @@ public class WindowManagerService extends IWindowManager.Stub int mNumWindow = 0; boolean mClientDead = false; - /** - * Current pointer move event being dispatched to client window... must - * hold key lock to access. - */ - QueuedEvent mPendingPointerMove; - WindowState mPendingPointerWindow; - - /** - * Current trackball move event being dispatched to client window... must - * hold key lock to access. - */ - QueuedEvent mPendingTrackballMove; - WindowState mPendingTrackballWindow; - public Session(IInputMethodClient client, IInputContext inputContext) { mClient = client; mInputContext = inputContext; @@ -6792,8 +5680,14 @@ public class WindowManagerService extends IWindowManager.Stub } public int add(IWindow window, WindowManager.LayoutParams attrs, + int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { + return addWindow(this, window, attrs, viewVisibility, outContentInsets, + outInputChannel); + } + + public int addWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets) { - return addWindow(this, window, attrs, viewVisibility, outContentInsets); + return addWindow(this, window, attrs, viewVisibility, outContentInsets, null); } public void remove(IWindow window) { @@ -6829,27 +5723,6 @@ public class WindowManagerService extends IWindowManager.Stub finishDrawingWindow(this, window); } - public void finishKey(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow finishKey called for " + window); - mKeyWaiter.finishedKey(this, window, false, - KeyWaiter.RETURN_NOTHING); - } - - public MotionEvent getPendingPointerMove(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow getPendingMotionEvent called for " + window); - return mKeyWaiter.finishedKey(this, window, false, - KeyWaiter.RETURN_PENDING_POINTER); - } - - public MotionEvent getPendingTrackballMove(IWindow window) { - if (localLOGV) Slog.v( - TAG, "IWindow getPendingMotionEvent called for " + window); - return mKeyWaiter.finishedKey(this, window, false, - KeyWaiter.RETURN_PENDING_TRACKBALL); - } - public void setInTouchMode(boolean mode) { synchronized(mWindowMap) { mInTouchMode = mode; @@ -6953,16 +5826,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow); pw.print(" mClientDead="); pw.print(mClientDead); pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); - if (mPendingPointerWindow != null || mPendingPointerMove != null) { - pw.print(prefix); - pw.print("mPendingPointerWindow="); pw.print(mPendingPointerWindow); - pw.print(" mPendingPointerMove="); pw.println(mPendingPointerMove); - } - if (mPendingTrackballWindow != null || mPendingTrackballMove != null) { - pw.print(prefix); - pw.print("mPendingTrackballWindow="); pw.print(mPendingTrackballWindow); - pw.print(" mPendingTrackballMove="); pw.println(mPendingTrackballMove); - } } @Override @@ -6985,7 +5848,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams(); final DeathRecipient mDeathRecipient; final WindowState mAttachedWindow; - final ArrayList mChildWindows = new ArrayList(); + final ArrayList<WindowState> mChildWindows = new ArrayList<WindowState>(); final int mBaseLayer; final int mSubLayer; final boolean mLayoutAttached; @@ -7013,8 +5876,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean mObscured; boolean mTurnOnScreen; - WindowState mNextOutsideTouch; - int mLayoutSeq = -1; Configuration mConfiguration = null; @@ -7162,6 +6023,9 @@ public class WindowManagerService extends IWindowManager.Stub int mSurfaceLayer; float mSurfaceAlpha; + // Input channel + InputChannel mInputChannel; + WindowState(Session s, IWindow c, WindowToken token, WindowState attachedWindow, WindowManager.LayoutParams a, int viewVisibility) { @@ -7389,6 +6253,12 @@ public class WindowManagerService extends IWindowManager.Stub public IApplicationToken getAppToken() { return mAppToken != null ? mAppToken.appToken : null; } + + public long getInputDispatchingTimeoutNanos() { + return mAppToken != null + ? mAppToken.inputDispatchingTimeoutNanos + : DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + } public boolean hasAppShownWindows() { return mAppToken != null ? mAppToken.firstWindowDrawn : false; @@ -7522,14 +6392,6 @@ public class WindowManagerService extends IWindowManager.Stub } void destroySurfaceLocked() { - // Window is no longer on-screen, so can no longer receive - // key events... if we were waiting for it to finish - // handling a key event, the wait is over! - mKeyWaiter.finishedKey(mSession, mClient, true, - KeyWaiter.RETURN_NOTHING); - mKeyWaiter.releasePendingPointerLocked(mSession); - mKeyWaiter.releasePendingTrackballLocked(mSession); - if (mAppToken != null && this == mAppToken.startingWindow) { mAppToken.startingDisplayed = false; } @@ -7542,7 +6404,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = mChildWindows.size(); while (i > 0) { i--; - WindowState c = (WindowState)mChildWindows.get(i); + WindowState c = mChildWindows.get(i); c.mAttachedHidden = true; } @@ -7653,7 +6515,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = mChildWindows.size(); while (i > 0) { i--; - WindowState c = (WindowState)mChildWindows.get(i); + WindowState c = mChildWindows.get(i); if (c.mAttachedHidden) { c.mAttachedHidden = false; if (c.mSurface != null) { @@ -7826,7 +6688,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mChildWindows.size(); for (int i=0; i<N; i++) { - ((WindowState)mChildWindows.get(i)).finishExit(); + mChildWindows.get(i).finishExit(); } if (!mExiting) { @@ -7851,7 +6713,6 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Error hiding surface in " + this, e); } mLastHidden = true; - mKeyWaiter.releasePendingPointerLocked(mSession); } mExiting = false; if (mRemoveOnExit) { @@ -8175,6 +7036,8 @@ public class WindowManagerService extends IWindowManager.Stub } void removeLocked() { + disposeInputChannel(); + if (mAttachedWindow != null) { mAttachedWindow.mChildWindows.remove(this); } @@ -8187,6 +7050,15 @@ public class WindowManagerService extends IWindowManager.Stub // we are doing this as part of processing a death note.) } } + + void disposeInputChannel() { + if (mInputChannel != null) { + mInputManager.unregisterInputChannel(mInputChannel); + + mInputChannel.dispose(); + mInputChannel = null; + } + } private class DeathRecipient implements IBinder.DeathRecipient { public void binderDied() { @@ -8428,6 +7300,11 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); } } + + String makeInputChannelName() { + return Integer.toHexString(System.identityHashCode(this)) + + " " + mAttrs.getTitle(); + } @Override public String toString() { @@ -8530,6 +7407,9 @@ public class WindowManagerService extends IWindowManager.Stub int groupId = -1; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + + // The input dispatching timeout for this application token in nanoseconds. + long inputDispatchingTimeoutNanos; // These are used for determining when all windows associated with // an activity have been drawn, so they can be made visible together @@ -8737,7 +7617,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = windows.size(); for (int i=0; i<N; i++) { - ((WindowState)windows.get(i)).finishExit(); + windows.get(i).finishExit(); } updateReportedVisibilityLocked(); @@ -8936,6 +7816,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int ENABLE_SCREEN = 16; public static final int APP_FREEZE_TIMEOUT = 17; public static final int SEND_NEW_CONFIGURATION = 18; + public static final int REPORT_WINDOWS_CHANGE = 19; private Session mLastReportedHold; @@ -8977,6 +7858,7 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RemoteException e) { // Ignore if process has died. } + notifyFocusChanged(); } if (lastFocus != null) { @@ -9161,7 +8043,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = mWindows.size(); while (i > 0) { i--; - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mOrientationChanging) { w.mOrientationChanging = false; Slog.w(TAG, "Force clearing orientation change: " + w); @@ -9267,6 +8149,16 @@ public class WindowManagerService extends IWindowManager.Stub break; } + case REPORT_WINDOWS_CHANGE: { + if (mWindowsChanged) { + synchronized (mWindowMap) { + mWindowsChanged = false; + } + notifyWindowsChanged(); + } + break; + } + } } } @@ -9279,7 +8171,8 @@ public class WindowManagerService extends IWindowManager.Stub IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); - return new Session(client, inputContext); + Session session = new Session(client, inputContext); + return session; } public boolean inputMethodClientHasFocus(IInputMethodClient client) { @@ -9289,7 +8182,7 @@ public class WindowManagerService extends IWindowManager.Stub int idx = findDesiredInputMethodWindowIndexLocked(false); WindowState imFocus; if (idx > 0) { - imFocus = (WindowState)mWindows.get(idx-1); + imFocus = mWindows.get(idx-1); if (imFocus != null) { if (imFocus.mSession.mClient != null && imFocus.mSession.mClient.asBinder() == client.asBinder()) { @@ -9347,9 +8240,10 @@ public class WindowManagerService extends IWindowManager.Stub // First remove all existing app windows. i=0; while (i < NW) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mAppToken != null) { - WindowState win = (WindowState)mWindows.remove(i); + WindowState win = mWindows.remove(i); + mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Rebuild removing window: " + win); NW--; @@ -9396,7 +8290,7 @@ public class WindowManagerService extends IWindowManager.Stub int i; for (i=0; i<N; i++) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) { curLayer += WINDOW_LAYER_MULTIPLIER; @@ -9485,6 +8379,10 @@ public class WindowManagerService extends IWindowManager.Stub requestAnimationLocked(0); } } + if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) { + mH.removeMessages(H.REPORT_WINDOWS_CHANGE); + mH.sendMessage(mH.obtainMessage(H.REPORT_WINDOWS_CHANGE)); + } } catch (RuntimeException e) { mInLayout = false; Slog.e(TAG, "Unhandled exception while layout out windows", e); @@ -9517,7 +8415,7 @@ public class WindowManagerService extends IWindowManager.Stub // to another window). int topAttached = -1; for (i = N-1; i >= 0; i--) { - WindowState win = (WindowState) mWindows.get(i); + WindowState win = mWindows.get(i); // Don't do layout of a window if it is not visible, or // soon won't be visible, to avoid wasting time and funky @@ -9566,7 +8464,7 @@ public class WindowManagerService extends IWindowManager.Stub // XXX does not deal with windows that are attached to windows // that are themselves attached. for (i = topAttached; i >= 0; i--) { - WindowState win = (WindowState) mWindows.get(i); + WindowState win = mWindows.get(i); // If this view is GONE, then skip it -- keep the current // frame, and let the caller know so they can ignore it @@ -9589,6 +8487,9 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + // Window frames may have changed. Tell the input dispatcher about it. + mInputMonitor.updateInputWindowsLw(); return mPolicy.finishLayoutLw(); } @@ -9710,7 +8611,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mWindows.size(); for (i=N-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); final WindowManager.LayoutParams attrs = w.mAttrs; @@ -10143,7 +9044,7 @@ public class WindowManagerService extends IWindowManager.Stub // Clear them out. forceHiding = false; for (i=N-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); if (w.mSurface != null) { final WindowManager.LayoutParams attrs = w.mAttrs; if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { @@ -10193,6 +9094,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x" + Integer.toHexString(changes)); + mInputMonitor.updateInputWindowsLw(); } while (changes != 0); // THIRD LOOP: Update the surfaces of all windows. @@ -10209,7 +9111,7 @@ public class WindowManagerService extends IWindowManager.Stub final int N = mWindows.size(); for (i=N-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); boolean displayed = false; final WindowManager.LayoutParams attrs = w.mAttrs; @@ -10389,7 +9291,6 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Exception hiding surface in " + w); } } - mKeyWaiter.releasePendingPointerLocked(w.mSession); } // If we are waiting for this window to handle an // orientation change, well, it is hidden, so @@ -10649,6 +9550,8 @@ public class WindowManagerService extends IWindowManager.Stub Slog.e(TAG, "Unhandled exception in Window Manager", e); } + mInputMonitor.updateInputWindowsLw(); + Surface.closeTransaction(); if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG, @@ -10774,10 +9677,12 @@ public class WindowManagerService extends IWindowManager.Stub requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis()); } + mInputMonitor.updateInputWindowsLw(); + if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen + " holdScreen=" + holdScreen); if (!mDisplayFrozen) { - mQueue.setHoldScreenLocked(holdScreen != null); + setHoldScreenLocked(holdScreen != null); if (screenBrightness < 0 || screenBrightness > 1.0f) { mPowerManager.setScreenBrightnessOverride(-1); } else { @@ -10808,6 +9713,21 @@ public class WindowManagerService extends IWindowManager.Stub // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); } + + /** + * Must be called with the main window manager lock held. + */ + void setHoldScreenLocked(boolean holding) { + boolean state = mHoldingScreenWakeLock.isHeld(); + if (holding != state) { + if (holding) { + mHoldingScreenWakeLock.acquire(); + } else { + mPolicy.screenOnStoppedLw(); + mHoldingScreenWakeLock.release(); + } + } + } void requestAnimationLocked(long delay) { if (!mAnimationPending) { @@ -10865,7 +9785,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean leakedSurface = false; Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); for (int i=0; i<N; i++) { - WindowState ws = (WindowState)mWindows.get(i); + WindowState ws = mWindows.get(i); if (ws.mSurface != null) { if (!mSessions.contains(ws.mSession)) { Slog.w(TAG, "LEAKED SURFACE (session doesn't exist): " @@ -10897,7 +9817,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "No leaked surfaces; killing applicatons!"); SparseIntArray pidCandidates = new SparseIntArray(); for (int i=0; i<N; i++) { - WindowState ws = (WindowState)mWindows.get(i); + WindowState ws = mWindows.get(i); if (ws.mSurface != null) { pidCandidates.append(ws.mSession.mPid, ws.mSession.mPid); } @@ -10964,14 +9884,20 @@ public class WindowManagerService extends IWindowManager.Stub assignLayersLocked(); } } - - if (newFocus != null && mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { - mKeyWaiter.handleNewWindowLocked(newFocus); + + if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { + // If we defer assigning layers, then the caller is responsible for + // doing this part. + finishUpdateFocusedWindowAfterAssignLayersLocked(); } return true; } return false; } + + private void finishUpdateFocusedWindowAfterAssignLayersLocked() { + mInputMonitor.setInputFocusLw(mCurrentFocus); + } private WindowState computeFocusedWindowLocked() { WindowState result = null; @@ -10983,7 +9909,7 @@ public class WindowManagerService extends IWindowManager.Stub ? mAppTokens.get(nextAppIndex) : null; while (i >= 0) { - win = (WindowState)mWindows.get(i); + win = mWindows.get(i); if (localLOGV || DEBUG_FOCUS) Slog.v( TAG, "Looking for focus: " + i @@ -11044,15 +9970,6 @@ public class WindowManagerService extends IWindowManager.Stub private void startFreezingDisplayLocked() { if (mDisplayFrozen) { - // Freezing the display also suspends key event delivery, to - // keep events from going astray while the display is reconfigured. - // If someone has changed orientation again while the screen is - // still frozen, the events will continue to be blocked while the - // successive orientation change is processed. To prevent spurious - // ANRs, we reset the event dispatch timeout in this case. - synchronized (mKeyWaiter) { - mKeyWaiter.mWasFrozen = true; - } return; } @@ -11074,6 +9991,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); mDisplayFrozen = true; + + mInputMonitor.freezeInputDispatchingLw(); + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; mNextAppTransitionPackage = null; @@ -11105,12 +10025,7 @@ public class WindowManagerService extends IWindowManager.Stub } Surface.unfreezeDisplay(0); - // Reset the key delivery timeout on unfreeze, too. We force a wakeup here - // too because regular key delivery processing should resume immediately. - synchronized (mKeyWaiter) { - mKeyWaiter.mWasFrozen = true; - mKeyWaiter.notifyAll(); - } + mInputMonitor.thawInputDispatchingLw(); // While the display is frozen we don't re-compute the orientation // to avoid inconsistent states. However, something interesting @@ -11142,14 +10057,13 @@ public class WindowManagerService extends IWindowManager.Stub return; } - pw.println("Input State:"); - mQueue.dump(pw, " "); + mInputManager.dump(pw); pw.println(" "); synchronized(mWindowMap) { pw.println("Current Window Manager state:"); for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = (WindowState)mWindows.get(i); + WindowState w = mWindows.get(i); pw.print(" Window #"); pw.print(i); pw.print(' '); pw.print(w); pw.println(":"); w.dump(pw, " "); @@ -11360,24 +10274,13 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); - pw.println(" KeyWaiter state:"); - pw.print(" mLastWin="); pw.print(mKeyWaiter.mLastWin); - pw.print(" mLastBinder="); pw.println(mKeyWaiter.mLastBinder); - pw.print(" mFinished="); pw.print(mKeyWaiter.mFinished); - pw.print(" mGotFirstWindow="); pw.print(mKeyWaiter.mGotFirstWindow); - pw.print(" mEventDispatching="); pw.print(mKeyWaiter.mEventDispatching); - pw.print(" mTimeToSwitch="); pw.println(mKeyWaiter.mTimeToSwitch); } } + // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection). public void monitor() { synchronized (mWindowMap) { } synchronized (mKeyguardTokenWatcher) { } - synchronized (mKeyWaiter) { } - } - - public void virtualKeyFeedback(KeyEvent event) { - mPolicy.keyFeedbackFromInput(event); } /** diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 804af9c..b2cb941 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -16,6 +16,8 @@ package com.android.server.am; +import com.android.internal.R; +import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.os.BatteryStatsImpl; import com.android.server.AttributeCache; import com.android.server.IntentResolver; @@ -24,6 +26,7 @@ import com.android.server.ProcessStats; import com.android.server.SystemServer; import com.android.server.Watchdog; import com.android.server.WindowManagerService; +import com.android.server.am.ActivityStack.ActivityState; import dalvik.system.Zygote; @@ -32,6 +35,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.AlertDialog; +import android.app.AppGlobals; import android.app.ApplicationErrorReport; import android.app.Dialog; import android.app.IActivityController; @@ -39,10 +43,12 @@ import android.app.IActivityManager; import android.app.IActivityWatcher; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; +import android.app.INotificationManager; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; import android.app.Instrumentation; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.ResultInfo; import android.app.Service; @@ -70,6 +76,7 @@ import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; @@ -93,6 +100,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; @@ -177,47 +185,21 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Maximum number of recent tasks that we can remember. static final int MAX_RECENT_TASKS = 20; - + // Amount of time after a call to stopAppSwitches() during which we will // prevent further untrusted switches from happening. static final long APP_SWITCH_DELAY_TIME = 5*1000; - - // How long until we reset a task when the user returns to it. Currently - // 30 minutes. - static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30; - - // Set to true to disable the icon that is shown while a new activity - // is being started. - static final boolean SHOW_APP_STARTING_ICON = true; - - // How long we wait until giving up on the last activity to pause. This - // is short because it directly impacts the responsiveness of starting the - // next activity. - static final int PAUSE_TIMEOUT = 500; - - /** - * How long we can hold the launch wake lock before giving up. - */ - static final int LAUNCH_TIMEOUT = 10*1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; - // How long we wait until giving up on the last activity telling us it - // is idle. - static final int IDLE_TIMEOUT = 10*1000; - // How long to wait after going idle before forcing apps to GC. static final int GC_TIMEOUT = 5*1000; // The minimum amount of time between successive GC requests for a process. static final int GC_MIN_INTERVAL = 60*1000; - // How long we wait until giving up on an activity telling us it has - // finished destroying itself. - static final int DESTROY_TIMEOUT = 10*1000; - // How long we allow a receiver to run before giving up on it. static final int BROADCAST_TIMEOUT = 10*1000; @@ -284,6 +266,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // system/rootdir/init.rc on startup. static final int SECONDARY_SERVER_ADJ; + // This is a process with a heavy-weight application. It is in the + // background, but we want to try to avoid killing it. Value set in + // system/rootdir/init.rc on startup. + static final int HEAVY_WEIGHT_APP_ADJ; + + // This is a process only hosting components that are perceptible to the + // user, and we really want to avoid killing them, but they are not + // immediately visible. An example is background music playback. Value set in + // system/rootdir/init.rc on startup. + static final int PERCEPTIBLE_APP_ADJ; + // This is a process only hosting activities that are visible to the // user, so we'd prefer they don't disappear. Value set in // system/rootdir/init.rc on startup. @@ -309,6 +302,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final int HOME_APP_MEM; static final int BACKUP_APP_MEM; static final int SECONDARY_SERVER_MEM; + static final int HEAVY_WEIGHT_APP_MEM; + static final int PERCEPTIBLE_APP_MEM; static final int VISIBLE_APP_MEM; static final int FOREGROUND_APP_MEM; @@ -329,68 +324,55 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // been idle for less than 120 seconds. static final long EMPTY_APP_IDLE_OFFSET = 120*1000; + static int getIntProp(String name, boolean allowZero) { + String str = SystemProperties.get(name); + if (str == null) { + throw new IllegalArgumentException("Property not defined: " + name); + } + int val = Integer.valueOf(str); + if (val == 0 && !allowZero) { + throw new IllegalArgumentException("Property must not be zero: " + name); + } + return val; + } + static { // These values are set in system/rootdir/init.rc on startup. - FOREGROUND_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.FOREGROUND_APP_ADJ")); - VISIBLE_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.VISIBLE_APP_ADJ")); - SECONDARY_SERVER_ADJ = - Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_ADJ")); - BACKUP_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.BACKUP_APP_ADJ")); - HOME_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.HOME_APP_ADJ")); - HIDDEN_APP_MIN_ADJ = - Integer.valueOf(SystemProperties.get("ro.HIDDEN_APP_MIN_ADJ")); - EMPTY_APP_ADJ = - Integer.valueOf(SystemProperties.get("ro.EMPTY_APP_ADJ")); - HIDDEN_APP_MAX_ADJ = EMPTY_APP_ADJ-1; - FOREGROUND_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.FOREGROUND_APP_MEM"))*PAGE_SIZE; - VISIBLE_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.VISIBLE_APP_MEM"))*PAGE_SIZE; - SECONDARY_SERVER_MEM = - Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_MEM"))*PAGE_SIZE; - BACKUP_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.BACKUP_APP_MEM"))*PAGE_SIZE; - HOME_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.HOME_APP_MEM"))*PAGE_SIZE; - HIDDEN_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.HIDDEN_APP_MEM"))*PAGE_SIZE; - EMPTY_APP_MEM = - Integer.valueOf(SystemProperties.get("ro.EMPTY_APP_MEM"))*PAGE_SIZE; + FOREGROUND_APP_ADJ = getIntProp("ro.FOREGROUND_APP_ADJ", true); + VISIBLE_APP_ADJ = getIntProp("ro.VISIBLE_APP_ADJ", true); + PERCEPTIBLE_APP_ADJ = getIntProp("ro.PERCEPTIBLE_APP_ADJ", true); + HEAVY_WEIGHT_APP_ADJ = getIntProp("ro.HEAVY_WEIGHT_APP_ADJ", true); + SECONDARY_SERVER_ADJ = getIntProp("ro.SECONDARY_SERVER_ADJ", true); + BACKUP_APP_ADJ = getIntProp("ro.BACKUP_APP_ADJ", true); + HOME_APP_ADJ = getIntProp("ro.HOME_APP_ADJ", true); + HIDDEN_APP_MIN_ADJ = getIntProp("ro.HIDDEN_APP_MIN_ADJ", true); + EMPTY_APP_ADJ = getIntProp("ro.EMPTY_APP_ADJ", true); + // These days we use the last empty slot for hidden apps as well. + HIDDEN_APP_MAX_ADJ = EMPTY_APP_ADJ; + FOREGROUND_APP_MEM = getIntProp("ro.FOREGROUND_APP_MEM", false)*PAGE_SIZE; + VISIBLE_APP_MEM = getIntProp("ro.VISIBLE_APP_MEM", false)*PAGE_SIZE; + PERCEPTIBLE_APP_MEM = getIntProp("ro.PERCEPTIBLE_APP_MEM", false)*PAGE_SIZE; + HEAVY_WEIGHT_APP_MEM = getIntProp("ro.HEAVY_WEIGHT_APP_MEM", false)*PAGE_SIZE; + SECONDARY_SERVER_MEM = getIntProp("ro.SECONDARY_SERVER_MEM", false)*PAGE_SIZE; + BACKUP_APP_MEM = getIntProp("ro.BACKUP_APP_MEM", false)*PAGE_SIZE; + HOME_APP_MEM = getIntProp("ro.HOME_APP_MEM", false)*PAGE_SIZE; + HIDDEN_APP_MEM = getIntProp("ro.HIDDEN_APP_MEM", false)*PAGE_SIZE; + EMPTY_APP_MEM = getIntProp("ro.EMPTY_APP_MEM", false)*PAGE_SIZE; } static final int MY_PID = Process.myPid(); static final String[] EMPTY_STRING_ARRAY = new String[0]; - enum ActivityState { - INITIALIZING, - RESUMED, - PAUSING, - PAUSED, - STOPPING, - STOPPED, - FINISHING, - DESTROYING, - DESTROYED - } - - /** - * The back history of all previous (and possibly still - * running) activities. It contains HistoryRecord objects. - */ - final ArrayList mHistory = new ArrayList(); - + public ActivityStack mMainStack; + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. */ - class PendingActivityLaunch { - HistoryRecord r; - HistoryRecord sourceRecord; + static class PendingActivityLaunch { + ActivityRecord r; + ActivityRecord sourceRecord; Uri[] grantedUriPermissions; int grantedMode; boolean onlyIfNeeded; @@ -400,18 +382,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<PendingActivityLaunch>(); /** - * List of people waiting to find out about the next launched activity. - */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched - = new ArrayList<IActivityManager.WaitResult>(); - - /** - * List of people waiting to find out about the next visible activity. - */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible - = new ArrayList<IActivityManager.WaitResult>(); - - /** * List of all active broadcasts that are to be executed immediately * (without waiting for another broadcast to finish). Currently this only * contains broadcasts to registered receivers, to avoid spinning up @@ -441,56 +411,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean mBroadcastsScheduled = false; /** - * Set to indicate whether to issue an onUserLeaving callback when a - * newly launched activity is being brought in front of us. - */ - boolean mUserLeaving = false; - - /** - * When we are in the process of pausing an activity, before starting the - * next one, this variable holds the activity that is currently being paused. - */ - HistoryRecord mPausingActivity = null; - - /** - * Current activity that is resumed, or null if there is none. - */ - HistoryRecord mResumedActivity = null; - - /** * Activity we have told the window manager to have key focus. */ - HistoryRecord mFocusedActivity = null; - - /** - * This is the last activity that we put into the paused state. This is - * used to determine if we need to do an activity transition while sleeping, - * when we normally hold the top activity paused. - */ - HistoryRecord mLastPausedActivity = null; - - /** - * List of activities that are waiting for a new activity - * to become visible before completing whatever operation they are - * supposed to do. - */ - final ArrayList mWaitingVisibleActivities = new ArrayList(); - - /** - * List of activities that are ready to be stopped, but waiting - * for the next activity to settle down before doing so. It contains - * HistoryRecord objects. - */ - final ArrayList<HistoryRecord> mStoppingActivities - = new ArrayList<HistoryRecord>(); - - /** - * Animations that for the current transition have requested not to - * be considered for the transition animation. - */ - final ArrayList<HistoryRecord> mNoAnimActivities - = new ArrayList<HistoryRecord>(); - + ActivityRecord mFocusedActivity = null; /** * List of intents that were used to start the most recent tasks. */ @@ -498,13 +421,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<TaskRecord>(); /** - * List of activities that are ready to be finished, but waiting - * for the previous activity to settle down before doing so. It contains - * HistoryRecord objects. - */ - final ArrayList mFinishingActivities = new ArrayList(); - - /** * All of the applications we currently have running organized by name. * The keys are strings of the application package name (as * returned by the package manager), and the keys are ApplicationRecord @@ -514,6 +430,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ProcessMap<ProcessRecord>(); /** + * The currently running heavy-weight process, if any. + */ + ProcessRecord mHeavyWeightProcess = null; + + /** * The last time that various processes have crashed. */ final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>(); @@ -599,16 +520,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * This is the process holding what we currently consider to be * the "home" activity. */ - private ProcessRecord mHomeProcess; + ProcessRecord mHomeProcess; /** - * List of running activities, sorted by recent usage. - * The first entry in the list is the least recently used. - * It contains HistoryRecord objects. - */ - private final ArrayList mLRUActivities = new ArrayList(); - - /** * Set of PendingResultRecord objects that are currently active. */ final HashSet mPendingResultRecords = new HashSet(); @@ -620,6 +534,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>(); /** + * Fingerprints (String.hashCode()) of stack traces that we've + * already logged DropBox entries for. Guarded by itself. If + * something (rogue user app) forces this over + * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared. + */ + private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>(); + private static final int MAX_DUP_SUPPRESSED_STACKS = 5000; + + /** + * Strict Mode background batched logging state. + * + * The string buffer is guarded by itself, and its lock is also + * used to determine if another batched write is already + * in-flight. + */ + private final StringBuilder mStrictModeBuffer = new StringBuilder(); + + /** * Intent broadcast that we have tried to start, but are * waiting for its application's process to be created. We only * need one (instead of a list) because we always process broadcasts @@ -727,21 +659,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * that a single provider may be published under multiple names, so * there may be multiple entries here for a single one in mProvidersByClass. */ - final HashMap mProvidersByName = new HashMap(); + final HashMap<String, ContentProviderRecord> mProvidersByName + = new HashMap<String, ContentProviderRecord>(); /** * All of the currently running global content providers. Keys are a * string containing the provider's implementation class and values are a * ContentProviderRecord object containing the data about it. */ - final HashMap mProvidersByClass = new HashMap(); + final HashMap<String, ContentProviderRecord> mProvidersByClass + = new HashMap<String, ContentProviderRecord>(); /** * List of content providers who have clients waiting for them. The * application is currently being launched and the provider will be * removed from this list once it is published. */ - final ArrayList mLaunchingProviders = new ArrayList(); + final ArrayList<ContentProviderRecord> mLaunchingProviders + = new ArrayList<ContentProviderRecord>(); /** * Global set of specific Uri permissions that have been granted. @@ -790,12 +725,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int mConfigurationSeq = 0; /** - * Set when we know we are going to be calling updateConfiguration() - * soon, so want to skip intermediate config checks. - */ - boolean mConfigWillChange; - - /** * Hardware-reported OpenGLES version. */ final int GL_ES_VERSION; @@ -851,21 +780,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * Set if we are shutting down the system, similar to sleeping. */ boolean mShuttingDown = false; - - /** - * Set when the system is going to sleep, until we have - * successfully paused the current activity and released our wake lock. - * At that point the system is allowed to actually sleep. - */ - PowerManager.WakeLock mGoingToSleep; - - /** - * We don't want to allow the device to go to sleep while in the process - * of launching an activity. This is primarily to allow alarm intent - * receivers to launch an activity and get that to run before the device - * goes back to sleep. - */ - PowerManager.WakeLock mLaunchingActivity; /** * Task identifier that activities are currently being started @@ -945,8 +859,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long mLastWriteTime = 0; - long mInitialStartTime = 0; - /** * Set to true after the system has finished booting. */ @@ -978,7 +890,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (localLOGV) Slog.v( TAG, "Death received in " + this + " for thread " + mAppThread.asBinder()); - removeRequestedPss(mApp); synchronized(ActivityManagerService.this) { appDiedLocked(mApp, mPid, mAppThread); } @@ -993,20 +904,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final int WAIT_FOR_DEBUGGER_MSG = 6; static final int BROADCAST_INTENT_MSG = 7; static final int BROADCAST_TIMEOUT_MSG = 8; - static final int PAUSE_TIMEOUT_MSG = 9; - static final int IDLE_TIMEOUT_MSG = 10; - static final int IDLE_NOW_MSG = 11; static final int SERVICE_TIMEOUT_MSG = 12; static final int UPDATE_TIME_ZONE = 13; static final int SHOW_UID_ERROR_MSG = 14; static final int IM_FEELING_LUCKY_MSG = 15; - static final int LAUNCH_TIMEOUT_MSG = 16; - static final int DESTROY_TIMEOUT_MSG = 17; - static final int RESUME_TOP_ACTIVITY_MSG = 19; static final int PROC_START_TIMEOUT_MSG = 20; static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21; static final int KILL_APPLICATION_MSG = 22; static final int FINALIZE_PENDING_INTENT_MSG = 23; + static final int POST_HEAVY_NOTIFICATION_MSG = 24; + static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25; + static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26; AlertDialog mUidAlert; @@ -1053,13 +961,38 @@ public final class ActivityManagerService extends ActivityManagerNative implemen false, false, MY_PID, Process.SYSTEM_UID); Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, - mContext, proc, (HistoryRecord)data.get("activity")); + mContext, proc, (ActivityRecord)data.get("activity")); d.show(); proc.anrDialog = d; } ensureBootCompleted(); } break; + case SHOW_STRICT_MODE_VIOLATION_MSG: { + HashMap<String, Object> data = (HashMap<String, Object>) msg.obj; + synchronized (ActivityManagerService.this) { + ProcessRecord proc = (ProcessRecord) data.get("app"); + if (proc == null) { + Slog.e(TAG, "App not found when showing strict mode dialog."); + break; + } + if (proc.crashDialog != null) { + Slog.e(TAG, "App already has strict mode dialog: " + proc); + return; + } + AppErrorResult res = (AppErrorResult) data.get("result"); + if (!mSleeping && !mShuttingDown) { + Dialog d = new StrictModeViolationDialog(mContext, res, proc); + d.show(); + proc.crashDialog = d; + } else { + // The device is asleep, so just pretend that the user + // saw a crash dialog and hit "force quit". + res.set(0); + } + } + ensureBootCompleted(); + } break; case SHOW_FACTORY_ERROR_MSG: { Dialog d = new FactoryErrorDialog( mContext, msg.getData().getCharSequence("msg")); @@ -1114,38 +1047,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen broadcastTimeout(); } } break; - case PAUSE_TIMEOUT_MSG: { - IBinder token = (IBinder)msg.obj; - // We don't at this point know if the activity is fullscreen, - // so we need to be conservative and assume it isn't. - Slog.w(TAG, "Activity pause timeout for " + token); - activityPaused(token, null, true); - } break; - case IDLE_TIMEOUT_MSG: { - if (mDidDexOpt) { - mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); - nmsg.obj = msg.obj; - mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); - return; - } - // We don't at this point know if the activity is fullscreen, - // so we need to be conservative and assume it isn't. - IBinder token = (IBinder)msg.obj; - Slog.w(TAG, "Activity idle timeout for " + token); - activityIdleInternal(token, true, null); - } break; - case DESTROY_TIMEOUT_MSG: { - IBinder token = (IBinder)msg.obj; - // We don't at this point know if the activity is fullscreen, - // so we need to be conservative and assume it isn't. - Slog.w(TAG, "Activity destroy timeout for " + token); - activityDestroyed(token); - } break; - case IDLE_NOW_MSG: { - IBinder token = (IBinder)msg.obj; - activityIdle(token, null); - } break; case SERVICE_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; @@ -1188,25 +1089,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mUidAlert = null; } } break; - case LAUNCH_TIMEOUT_MSG: { - if (mDidDexOpt) { - mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); - mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT); - return; - } - synchronized (ActivityManagerService.this) { - if (mLaunchingActivity.isHeld()) { - Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); - mLaunchingActivity.release(); - } - } - } break; - case RESUME_TOP_ACTIVITY_MSG: { - synchronized (ActivityManagerService.this) { - resumeTopActivityLocked(null); - } - } break; case PROC_START_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; @@ -1236,6 +1118,62 @@ public final class ActivityManagerService extends ActivityManagerNative implemen case FINALIZE_PENDING_INTENT_MSG: { ((PendingIntentRecord)msg.obj).completeFinalize(); } break; + case POST_HEAVY_NOTIFICATION_MSG: { + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + + ActivityRecord root = (ActivityRecord)msg.obj; + ProcessRecord process = root.app; + if (process == null) { + return; + } + + try { + Context context = mContext.createPackageContext(process.info.packageName, 0); + String text = mContext.getString(R.string.heavy_weight_notification, + context.getApplicationInfo().loadLabel(context.getPackageManager())); + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_adb; //context.getApplicationInfo().icon; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT; + notification.tickerText = text; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + notification.setLatestEventInfo(context, text, + mContext.getText(R.string.heavy_weight_notification_detail), + PendingIntent.getActivity(mContext, 0, root.intent, + PendingIntent.FLAG_CANCEL_CURRENT)); + + try { + int[] outId = new int[1]; + inm.enqueueNotification("android", R.string.heavy_weight_notification, + notification, outId); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error showing notification for heavy-weight app", e); + } catch (RemoteException e) { + } + } catch (NameNotFoundException e) { + Log.w(TAG, "Unable to create context for heavy notification", e); + } + } break; + case CANCEL_HEAVY_NOTIFICATION_MSG: { + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + try { + inm.cancelNotification("android", + R.string.heavy_weight_notification); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error canceling notification for service", e); + } catch (RemoteException e) { + } + } break; } } }; @@ -1299,11 +1237,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Context context = at.getSystemContext(); m.mContext = context; m.mFactoryTest = factoryTest; - PowerManager pm = - (PowerManager)context.getSystemService(Context.POWER_SERVICE); - m.mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - m.mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); - m.mLaunchingActivity.setReferenceCounted(false); + m.mMainStack = new ActivityStack(m, context, true); m.mBatteryStatsService.publish(context); m.mUsageStatsService.publish(context); @@ -1335,6 +1269,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); ActivityManagerService m = new ActivityManagerService(); @@ -1592,7 +1527,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return mAppBindArgs; } - private final void setFocusedActivityLocked(HistoryRecord r) { + final void setFocusedActivityLocked(ActivityRecord r) { if (mFocusedActivity != r) { mFocusedActivity = r; mWindowManager.setFocusedApp(r, true); @@ -1677,65 +1612,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void updateLruProcessLocked(ProcessRecord app, + final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean updateActivityTime) { mLruSeq++; updateLruProcessInternalLocked(app, oomAdj, updateActivityTime, 0); } - private final boolean updateLRUListLocked(HistoryRecord r) { - final boolean hadit = mLRUActivities.remove(r); - mLRUActivities.add(r); - return hadit; - } - - private final HistoryRecord topRunningActivityLocked(HistoryRecord notTop) { - int i = mHistory.size()-1; - while (i >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing && r != notTop) { - return r; - } - i--; - } - return null; - } - - private final HistoryRecord topRunningNonDelayedActivityLocked(HistoryRecord notTop) { - int i = mHistory.size()-1; - while (i >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing && !r.delayedResume && r != notTop) { - return r; - } - i--; - } - return null; - } - - /** - * This is a simplified version of topRunningActivityLocked that provides a number of - * optional skip-over modes. It is intended for use with the ActivityController hook only. - * - * @param token If non-null, any history records matching this token will be skipped. - * @param taskId If non-zero, we'll attempt to skip over records with the same task ID. - * - * @return Returns the HistoryRecord of the next activity on the stack. - */ - private final HistoryRecord topRunningActivityLocked(IBinder token, int taskId) { - int i = mHistory.size()-1; - while (i >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - // Note: the taskId check depends on real taskId fields being non-zero - if (!r.finishing && (token != r) && (taskId != r.task.taskId)) { - return r; - } - i--; - } - return null; - } - - private final ProcessRecord getProcessRecordLocked( + final ProcessRecord getProcessRecordLocked( String processName, int uid) { if (uid == Process.SYSTEM_UID) { // The system gets to run in any process. If there are multiple @@ -1749,8 +1632,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return proc; } - private void ensurePackageDexOpt(String packageName) { - IPackageManager pm = ActivityThread.getPackageManager(); + void ensurePackageDexOpt(String packageName) { + IPackageManager pm = AppGlobals.getPackageManager(); try { if (pm.performDexOpt(packageName)) { mDidDexOpt = true; @@ -1759,157 +1642,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private boolean isNextTransitionForward() { + boolean isNextTransitionForward() { int transit = mWindowManager.getPendingAppTransition(); return transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN || transit == WindowManagerPolicy.TRANSIT_TASK_OPEN || transit == WindowManagerPolicy.TRANSIT_TASK_TO_FRONT; } - private final boolean realStartActivityLocked(HistoryRecord r, - ProcessRecord app, boolean andResume, boolean checkConfig) - throws RemoteException { - - r.startFreezingScreenLocked(app, 0); - mWindowManager.setAppVisibility(r, true); - - // Have the window manager re-evaluate the orientation of - // the screen based on the new activity order. Note that - // as a result of this, it can call back into the activity - // manager with a new orientation. We don't care about that, - // because the activity is not currently running so we are - // just restarting it anyway. - if (checkConfig) { - Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - r.mayFreezeScreenLocked(app) ? r : null); - updateConfigurationLocked(config, r); - } - - r.app = app; - - if (localLOGV) Slog.v(TAG, "Launching: " + r); - - int idx = app.activities.indexOf(r); - if (idx < 0) { - app.activities.add(r); - } - updateLruProcessLocked(app, true, true); - - try { - if (app.thread == null) { - throw new RemoteException(); - } - List<ResultInfo> results = null; - List<Intent> newIntents = null; - if (andResume) { - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r - + " icicle=" + r.icicle - + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); - if (andResume) { - EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, - System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - } - if (r.isHomeActivity) { - mHomeProcess = app; - } - ensurePackageDexOpt(r.intent.getComponent().getPackageName()); - app.thread.scheduleLaunchActivity(new Intent(r.intent), r, - System.identityHashCode(r), - r.info, r.icicle, results, newIntents, !andResume, - isNextTransitionForward()); - } catch (RemoteException e) { - if (r.launchFailed) { - // This is the second time we failed -- finish activity - // and give up. - Slog.e(TAG, "Second failure launching " - + r.intent.getComponent().flattenToShortString() - + ", giving up", e); - appDiedLocked(app, app.pid, app.thread); - requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, - "2nd-crash"); - return false; - } - - // This is the first time we failed -- restart process and - // retry. - app.activities.remove(r); - throw e; - } - - r.launchFailed = false; - if (updateLRUListLocked(r)) { - Slog.w(TAG, "Activity " + r - + " being launched, but already in LRU list"); - } - - if (andResume) { - // As part of the process of launching, ActivityThread also performs - // a resume. - r.state = ActivityState.RESUMED; - r.icicle = null; - r.haveState = false; - r.stopped = false; - mResumedActivity = r; - r.task.touchActiveTime(); - completeResumeLocked(r); - pauseIfSleepingLocked(); - } else { - // This activity is not starting in the resumed state... which - // should look like we asked it to pause+stop (but remain visible), - // and it has done so and reported back the current icicle and - // other state. - r.state = ActivityState.STOPPED; - r.stopped = true; - } - - // Launch the new version setup screen if needed. We do this -after- - // launching the initial activity (that is, home), so that it can have - // a chance to initialize itself while in the background, making the - // switch back to it faster and look better. - startSetupActivityLocked(); - - return true; - } - - private final void startSpecificActivityLocked(HistoryRecord r, - boolean andResume, boolean checkConfig) { - // Is this activity's application already running? - ProcessRecord app = getProcessRecordLocked(r.processName, - r.info.applicationInfo.uid); - - if (r.startTime == 0) { - r.startTime = SystemClock.uptimeMillis(); - if (mInitialStartTime == 0) { - mInitialStartTime = r.startTime; - } - } else if (mInitialStartTime == 0) { - mInitialStartTime = SystemClock.uptimeMillis(); - } - - if (app != null && app.thread != null) { - try { - realStartActivityLocked(r, app, andResume, checkConfig); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when starting activity " - + r.intent.getComponent().flattenToShortString(), e); - } - - // If a dead object exception was thrown -- fall through to - // restart the application. - } - - startProcessLocked(r.processName, r.info.applicationInfo, true, 0, - "activity", r.intent.getComponent(), false); - } - - private final ProcessRecord startProcessLocked(String processName, + final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName, boolean allowWhileBooting) { ProcessRecord app = getProcessRecordLocked(processName, info.uid); @@ -2055,7 +1795,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen hostingNameStr != null ? hostingNameStr : ""); if (app.persistent) { - Watchdog.getInstance().processStarted(app, app.processName, pid); + Watchdog.getInstance().processStarted(app.processName, pid); } StringBuilder buf = mStringBuilder; @@ -2110,365 +1850,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { - if (mPausingActivity != null) { - RuntimeException e = new RuntimeException(); - Slog.e(TAG, "Trying to pause when pause is already pending for " - + mPausingActivity, e); - } - HistoryRecord prev = mResumedActivity; - if (prev == null) { - RuntimeException e = new RuntimeException(); - Slog.e(TAG, "Trying to pause when nothing is resumed", e); - resumeTopActivityLocked(null); - return; - } - if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev); - mResumedActivity = null; - mPausingActivity = prev; - mLastPausedActivity = prev; - prev.state = ActivityState.PAUSING; - prev.task.touchActiveTime(); - - updateCpuStats(); - - if (prev.app != null && prev.app.thread != null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev); - try { - EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, - System.identityHashCode(prev), - prev.shortComponentName); - prev.app.thread.schedulePauseActivity(prev, prev.finishing, userLeaving, - prev.configChangeFlags); - updateUsageStats(prev, false); - } catch (Exception e) { - // Ignore exception, if process died other code will cleanup. - Slog.w(TAG, "Exception thrown during pause", e); - mPausingActivity = null; - mLastPausedActivity = null; - } - } else { - mPausingActivity = null; - mLastPausedActivity = null; - } - - // If we are not going to sleep, we want to ensure the device is - // awake until the next activity is started. - if (!mSleeping && !mShuttingDown) { - mLaunchingActivity.acquire(); - if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { - // To be safe, don't allow the wake lock to be held for too long. - Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); - mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT); - } - } - - - if (mPausingActivity != null) { - // Have the window manager pause its key dispatching until the new - // activity has started. If we're pausing the activity just because - // the screen is being turned off and the UI is sleeping, don't interrupt - // key dispatch; the same activity will pick it up again on wakeup. - if (!uiSleeping) { - prev.pauseKeyDispatchingLocked(); - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off"); - } - - // Schedule a pause timeout in case the app doesn't respond. - // We don't give it much time because this directly impacts the - // responsiveness seen by the user. - Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); - msg.obj = prev; - mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); - if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); - } else { - // This activity failed to schedule the - // pause, so just treat it as being paused now. - if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next."); - resumeTopActivityLocked(null); - } - } - - private final void completePauseLocked() { - HistoryRecord prev = mPausingActivity; - if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); - - if (prev != null) { - if (prev.finishing) { - if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); - prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE); - } else if (prev.app != null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); - if (prev.waitingVisible) { - prev.waitingVisible = false; - mWaitingVisibleActivities.remove(prev); - if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v( - TAG, "Complete pause, no longer waiting: " + prev); - } - if (prev.configDestroy) { - // The previous is being paused because the configuration - // is changing, which means it is actually stopping... - // To juggle the fact that we are also starting a new - // instance right now, we need to first completely stop - // the current instance before starting the new one. - if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev); - destroyActivityLocked(prev, true); - } else { - mStoppingActivities.add(prev); - if (mStoppingActivities.size() > 3) { - // If we already have a few activities waiting to stop, - // then give up on things going idle and start clearing - // them out. - if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle"); - Message msg = Message.obtain(); - msg.what = ActivityManagerService.IDLE_NOW_MSG; - mHandler.sendMessage(msg); - } - } - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev); - prev = null; - } - mPausingActivity = null; - } - - if (!mSleeping && !mShuttingDown) { - resumeTopActivityLocked(prev); - } else { - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); - } - if (mShuttingDown) { - notifyAll(); - } - } - - if (prev != null) { - prev.resumeKeyDispatchingLocked(); - } - - if (prev.app != null && prev.cpuTimeAtResume > 0 && mBatteryStatsService.isOnBattery()) { - long diff = 0; - synchronized (mProcessStatsThread) { - diff = mProcessStats.getCpuTimeForPid(prev.app.pid) - prev.cpuTimeAtResume; - } - if (diff > 0) { - BatteryStatsImpl bsi = mBatteryStatsService.getActiveStatistics(); - synchronized (bsi) { - BatteryStatsImpl.Uid.Proc ps = - bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, - prev.info.packageName); - if (ps != null) { - ps.addForegroundTimeLocked(diff); - } - } - } - } - prev.cpuTimeAtResume = 0; // reset it - } - - /** - * Once we know that we have asked an application to put an activity in - * the resumed state (either by launching it or explicitly telling it), - * this function updates the rest of our state to match that fact. - */ - private final void completeResumeLocked(HistoryRecord next) { - next.idle = false; - next.results = null; - next.newIntents = null; - - // schedule an idle timeout in case the app doesn't do it for us. - Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); - msg.obj = next; - mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); - - if (false) { - // The activity was never told to pause, so just keep - // things going as-is. To maintain our own state, - // we need to emulate it coming back and saying it is - // idle. - msg = mHandler.obtainMessage(IDLE_NOW_MSG); - msg.obj = next; - mHandler.sendMessage(msg); - } - - reportResumedActivityLocked(next); - - next.thumbnail = null; - setFocusedActivityLocked(next); - next.resumeKeyDispatchingLocked(); - ensureActivitiesVisibleLocked(null, 0); - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - - // Mark the point when the activity is resuming - // TODO: To be more accurate, the mark should be before the onCreate, - // not after the onResume. But for subsequent starts, onResume is fine. - if (next.app != null) { - synchronized (mProcessStatsThread) { - next.cpuTimeAtResume = mProcessStats.getCpuTimeForPid(next.app.pid); - } - } else { - next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process - } - } - - /** - * Make sure that all activities that need to be visible (that is, they - * currently can be seen by the user) actually are. - */ - private final void ensureActivitiesVisibleLocked(HistoryRecord top, - HistoryRecord starting, String onlyThisProcess, int configChanges) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "ensureActivitiesVisible behind " + top - + " configChanges=0x" + Integer.toHexString(configChanges)); - - // If the top activity is not fullscreen, then we need to - // make sure any activities under it are now visible. - final int count = mHistory.size(); - int i = count-1; - while (mHistory.get(i) != top) { - i--; - } - HistoryRecord r; - boolean behindFullscreen = false; - for (; i>=0; i--) { - r = (HistoryRecord)mHistory.get(i); - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make visible? " + r + " finishing=" + r.finishing - + " state=" + r.state); - if (r.finishing) { - continue; - } - - final boolean doThisProcess = onlyThisProcess == null - || onlyThisProcess.equals(r.processName); - - // First: if this is not the current activity being started, make - // sure it matches the current configuration. - if (r != starting && doThisProcess) { - ensureActivityConfigurationLocked(r, 0); - } - - if (r.app == null || r.app.thread == null) { - if (onlyThisProcess == null - || onlyThisProcess.equals(r.processName)) { - // This activity needs to be visible, but isn't even - // running... get it started, but don't resume it - // at this point. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Start and freeze screen for " + r); - if (r != starting) { - r.startFreezingScreenLocked(r.app, configChanges); - } - if (!r.visible) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Starting and making visible: " + r); - mWindowManager.setAppVisibility(r, true); - } - if (r != starting) { - startSpecificActivityLocked(r, false, false); - } - } - - } else if (r.visible) { - // If this activity is already visible, then there is nothing - // else to do here. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Skipping: already visible at " + r); - r.stopFreezingScreenLocked(false); - - } else if (onlyThisProcess == null) { - // This activity is not currently visible, but is running. - // Tell it to become visible. - r.visible = true; - if (r.state != ActivityState.RESUMED && r != starting) { - // If this activity is paused, tell it - // to now show its window. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Making visible and scheduling visibility: " + r); - try { - mWindowManager.setAppVisibility(r, true); - r.app.thread.scheduleWindowVisibility(r, true); - r.stopFreezingScreenLocked(false); - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making visibile: " - + r.intent.getComponent(), e); - } - } - } - - // Aggregate current change flags. - configChanges |= r.configChangeFlags; - - if (r.fullscreen) { - // At this point, nothing else needs to be shown - if (DEBUG_VISBILITY) Slog.v( - TAG, "Stopping: fullscreen at " + r); - behindFullscreen = true; - i--; - break; - } - } - - // Now for any activities that aren't visible to the user, make - // sure they no longer are keeping the screen frozen. - while (i >= 0) { - r = (HistoryRecord)mHistory.get(i); - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make invisible? " + r + " finishing=" + r.finishing - + " state=" + r.state - + " behindFullscreen=" + behindFullscreen); - if (!r.finishing) { - if (behindFullscreen) { - if (r.visible) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Making invisible: " + r); - r.visible = false; - try { - mWindowManager.setAppVisibility(r, false); - if ((r.state == ActivityState.STOPPING - || r.state == ActivityState.STOPPED) - && r.app != null && r.app.thread != null) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Scheduling invisibility: " + r); - r.app.thread.scheduleWindowVisibility(r, false); - } - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making hidden: " - + r.intent.getComponent(), e); - } - } else { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Already invisible: " + r); - } - } else if (r.fullscreen) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Now behindFullscreen: " + r); - behindFullscreen = true; - } - } - i--; - } - } - - /** - * Version of ensureActivitiesVisible that can easily be called anywhere. - */ - private final void ensureActivitiesVisibleLocked(HistoryRecord starting, - int configChanges) { - HistoryRecord r = topRunningActivityLocked(null); - if (r != null) { - ensureActivitiesVisibleLocked(r, starting, null, configChanges); - } - } - - private void updateUsageStats(HistoryRecord resumedComponent, boolean resumed) { + void updateUsageStats(ActivityRecord resumedComponent, boolean resumed) { if (resumed) { mUsageStatsService.noteResumeComponent(resumedComponent.realActivity); } else { @@ -2476,7 +1858,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private boolean startHomeActivityLocked() { + boolean startHomeActivityLocked() { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -2503,7 +1885,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen aInfo.applicationInfo.uid); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityLocked(null, intent, null, null, 0, aInfo, + mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, null, null, 0, 0, 0, false, false); } } @@ -2515,7 +1897,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen /** * Starts the "new version setup screen" if appropriate. */ - private void startSetupActivityLocked() { + void startSetupActivityLocked() { // Only do this once per boot. if (mCheckedForSetup) { return; @@ -2559,14 +1941,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - startActivityLocked(null, intent, null, null, 0, ri.activityInfo, + mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, null, null, 0, 0, 0, false, false); } } } } - private void reportResumedActivityLocked(HistoryRecord r) { + void reportResumedActivityLocked(ActivityRecord r) { //Slog.i(TAG, "**** REPORT RESUME: " + r); final int identHash = System.identityHashCode(r); @@ -2585,1210 +1967,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mWatchers.finishBroadcast(); } - - /** - * Ensure that the top activity in the stack is resumed. - * - * @param prev The previously resumed activity, for when in the process - * of pausing; can be null to call from elsewhere. - * - * @return Returns true if something is being resumed, or false if - * nothing happened. - */ - private final boolean resumeTopActivityLocked(HistoryRecord prev) { - // Find the first activity that is not finishing. - HistoryRecord next = topRunningActivityLocked(null); - - // Remember how we'll process this pause/resume situation, and ensure - // that the state is reset however we wind up proceeding. - final boolean userLeaving = mUserLeaving; - mUserLeaving = false; - - if (next == null) { - // There are no more activities! Let's just start up the - // Launcher... - return startHomeActivityLocked(); - } - - next.delayedResume = false; - - // If the top activity is the resumed one, nothing to do. - if (mResumedActivity == next && next.state == ActivityState.RESUMED) { - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return false; - } - // If we are sleeping, and there is no resumed activity, and the top - // activity is paused, well that is the state we want. - if ((mSleeping || mShuttingDown) - && mLastPausedActivity == next && next.state == ActivityState.PAUSED) { - // Make sure we have executed any pending transitions, since there - // should be nothing left to do at this point. - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return false; - } - - // The activity may be waiting for stop, but that is no longer - // appropriate for it. - mStoppingActivities.remove(next); - mWaitingVisibleActivities.remove(next); - - if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); - - // If we are currently pausing an activity, then don't do anything - // until that is done. - if (mPausingActivity != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: pausing=" + mPausingActivity); - return false; - } - - // We need to start pausing the current activity so the top one - // can be resumed... - if (mResumedActivity != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to start pausing"); - startPausingLocked(userLeaving, false); - return true; - } - - if (prev != null && prev != next) { - if (!prev.waitingVisible && next != null && !next.nowVisible) { - prev.waitingVisible = true; - mWaitingVisibleActivities.add(prev); - if (DEBUG_SWITCH) Slog.v( - TAG, "Resuming top, waiting visible to hide: " + prev); - } else { - // The next activity is already visible, so hide the previous - // activity's windows right now so we can show the new one ASAP. - // We only do this if the previous is finishing, which should mean - // it is on top of the one being resumed so hiding it quickly - // is good. Otherwise, we want to do the normal route of allowing - // the resumed activity to be shown so we can decide if the - // previous should actually be hidden depending on whether the - // new one is found to be full-screen or not. - if (prev.finishing) { - mWindowManager.setAppVisibility(prev, false); - if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: " - + prev + ", waitingVisible=" - + (prev != null ? prev.waitingVisible : null) - + ", nowVisible=" + next.nowVisible); - } else { - if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: " - + prev + ", waitingVisible=" - + (prev != null ? prev.waitingVisible : null) - + ", nowVisible=" + next.nowVisible); - } - } - } - - // We are starting up the next activity, so tell the window manager - // that the previous one will be hidden soon. This way it can know - // to ignore it when computing the desired screen orientation. - if (prev != null) { - if (prev.finishing) { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare close transition: prev=" + prev); - if (mNoAnimActivities.contains(prev)) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - } else { - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE - : WindowManagerPolicy.TRANSIT_TASK_CLOSE); - } - mWindowManager.setAppWillBeHidden(prev); - mWindowManager.setAppVisibility(prev, false); - } else { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: prev=" + prev); - if (mNoAnimActivities.contains(next)) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - } else { - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - : WindowManagerPolicy.TRANSIT_TASK_OPEN); - } - } - if (false) { - mWindowManager.setAppWillBeHidden(prev); - mWindowManager.setAppVisibility(prev, false); - } - } else if (mHistory.size() > 1) { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: no previous"); - if (mNoAnimActivities.contains(next)) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - } else { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); - } - } - - if (next.app != null && next.app.thread != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); - - // This activity is now becoming visible. - mWindowManager.setAppVisibility(next, true); - - HistoryRecord lastResumedActivity = mResumedActivity; - ActivityState lastState = next.state; - - updateCpuStats(); - - next.state = ActivityState.RESUMED; - mResumedActivity = next; - next.task.touchActiveTime(); - updateLruProcessLocked(next.app, true, true); - updateLRUListLocked(next); - - // Have the window manager re-evaluate the orientation of - // the screen based on the new activity order. - boolean updated; - synchronized (this) { - Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - next.mayFreezeScreenLocked(next.app) ? next : null); - if (config != null) { - next.frozenBeforeDestroy = true; - } - updated = updateConfigurationLocked(config, next); - } - if (!updated) { - // The configuration update wasn't able to keep the existing - // instance of the activity, and instead started a new one. - // We should be all done, but let's just make sure our activity - // is still at the top and schedule another run if something - // weird happened. - HistoryRecord nextNext = topRunningActivityLocked(null); - if (DEBUG_SWITCH) Slog.i(TAG, - "Activity config changed during resume: " + next - + ", new next: " + nextNext); - if (nextNext != next) { - // Do over! - mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); - } - setFocusedActivityLocked(next); - ensureActivitiesVisibleLocked(null, 0); - mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return true; - } - - try { - // Deliver all pending results. - ArrayList a = next.results; - if (a != null) { - final int N = a.size(); - if (!next.finishing && N > 0) { - if (DEBUG_RESULTS) Slog.v( - TAG, "Delivering results to " + next - + ": " + a); - next.app.thread.scheduleSendResult(next, a); - } - } - - if (next.newIntents != null) { - next.app.thread.scheduleNewIntent(next.newIntents, next); - } - - EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, - System.identityHashCode(next), - next.task.taskId, next.shortComponentName); - - next.app.thread.scheduleResumeActivity(next, - isNextTransitionForward()); - - pauseIfSleepingLocked(); - - } catch (Exception e) { - // Whoops, need to restart this activity! - next.state = lastState; - mResumedActivity = lastResumedActivity; - Slog.i(TAG, "Restarting because process died: " + next); - if (!next.hasBeenLaunched) { - next.hasBeenLaunched = true; - } else { - if (SHOW_APP_STARTING_ICON) { - mWindowManager.setAppStartingWindow( - next, next.packageName, next.theme, - next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); - } - } - startSpecificActivityLocked(next, true, false); - return true; - } - - // From this point on, if something goes wrong there is no way - // to recover the activity. - try { - next.visible = true; - completeResumeLocked(next); - } catch (Exception e) { - // If any exception gets thrown, toss away this - // activity and try the next one. - Slog.w(TAG, "Exception thrown during resume of " + next, e); - requestFinishActivityLocked(next, Activity.RESULT_CANCELED, null, - "resume-exception"); - return true; - } - - // Didn't need to use the icicle, and it is now out of date. - next.icicle = null; - next.haveState = false; - next.stopped = false; - - } else { - // Whoops, need to restart this activity! - if (!next.hasBeenLaunched) { - next.hasBeenLaunched = true; - } else { - if (SHOW_APP_STARTING_ICON) { - mWindowManager.setAppStartingWindow( - next, next.packageName, next.theme, - next.nonLocalizedLabel, - next.labelRes, next.icon, null, true); - } - if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); - } - startSpecificActivityLocked(next, true, true); - } - - return true; - } - - private final void startActivityLocked(HistoryRecord r, boolean newTask, - boolean doResume) { - final int NH = mHistory.size(); - - int addPos = -1; - - if (!newTask) { - // If starting in an existing task, find where that is... - HistoryRecord next = null; - boolean startIt = true; - for (int i = NH-1; i >= 0; i--) { - HistoryRecord p = (HistoryRecord)mHistory.get(i); - if (p.finishing) { - continue; - } - if (p.task == r.task) { - // Here it is! Now, if this is not yet visible to the - // user, then just add it without starting; it will - // get started when the user navigates back to it. - addPos = i+1; - if (!startIt) { - mHistory.add(addPos, r); - r.inHistory = true; - r.task.numActivities++; - mWindowManager.addAppToken(addPos, r, r.task.taskId, - r.info.screenOrientation, r.fullscreen); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - return; - } - break; - } - if (p.fullscreen) { - startIt = false; - } - next = p; - } - } - - // Place a new activity at top of stack, so it is next to interact - // with the user. - if (addPos < 0) { - addPos = mHistory.size(); - } - - // If we are not placing the new activity frontmost, we do not want - // to deliver the onUserLeaving callback to the actual frontmost - // activity - if (addPos < NH) { - mUserLeaving = false; - if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() behind front, mUserLeaving=false"); - } - - // Slot the activity into the history stack and proceed - mHistory.add(addPos, r); - r.inHistory = true; - r.frontOfTask = newTask; - r.task.numActivities++; - if (NH > 0) { - // We want to show the starting preview window if we are - // switching to a new task, or the next activity's process is - // not currently running. - boolean showStartingIcon = newTask; - ProcessRecord proc = r.app; - if (proc == null) { - proc = mProcessNames.get(r.processName, r.info.applicationInfo.uid); - } - if (proc == null || proc.thread == null) { - showStartingIcon = true; - } - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: starting " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - mNoAnimActivities.add(r); - } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_OPEN); - mNoAnimActivities.remove(r); - } else { - mWindowManager.prepareAppTransition(newTask - ? WindowManagerPolicy.TRANSIT_TASK_OPEN - : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); - mNoAnimActivities.remove(r); - } - mWindowManager.addAppToken( - addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); - boolean doShow = true; - if (newTask) { - // Even though this activity is starting fresh, we still need - // to reset it to make sure we apply affinities to move any - // existing activities from other tasks in to it. - // If the caller has requested that the target task be - // reset, then do so. - if ((r.intent.getFlags() - &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - resetTaskIfNeededLocked(r, r); - doShow = topRunningNonDelayedActivityLocked(null) == r; - } - } - if (SHOW_APP_STARTING_ICON && doShow) { - // Figure out if we are transitioning from another activity that is - // "has the same starting icon" as the next one. This allows the - // window manager to keep the previous window it had previously - // created, if it still had one. - HistoryRecord prev = mResumedActivity; - if (prev != null) { - // We don't want to reuse the previous starting preview if: - // (1) The current activity is in a different task. - if (prev.task != r.task) prev = null; - // (2) The current activity is already displayed. - else if (prev.nowVisible) prev = null; - } - mWindowManager.setAppStartingWindow( - r, r.packageName, r.theme, r.nonLocalizedLabel, - r.labelRes, r.icon, prev, showStartingIcon); - } - } else { - // If this is the first activity, don't do any fancy animations, - // because there is nothing for it to animate on top of. - mWindowManager.addAppToken(addPos, r, r.task.taskId, - r.info.screenOrientation, r.fullscreen); - } - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - - if (doResume) { - resumeTopActivityLocked(null); - } - } - - /** - * Perform clear operation as requested by - * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the - * stack to the given task, then look for - * an instance of that activity in the stack and, if found, finish all - * activities on top of it and return the instance. - * - * @param newR Description of the new activity being started. - * @return Returns the old activity that should be continue to be used, - * or null if none was found. - */ - private final HistoryRecord performClearTaskLocked(int taskId, - HistoryRecord newR, int launchFlags, boolean doClear) { - int i = mHistory.size(); - - // First find the requested task. - while (i > 0) { - i--; - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (r.task.taskId == taskId) { - i++; - break; - } - } - - // Now clear it. - while (i > 0) { - i--; - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (r.task.taskId != taskId) { - return null; - } - if (r.realActivity.equals(newR.realActivity)) { - // Here it is! Now finish everything in front... - HistoryRecord ret = r; - if (doClear) { - while (i < (mHistory.size()-1)) { - i++; - r = (HistoryRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } - } - } - - // Finally, if this is a normal launch mode (that is, not - // expecting onNewIntent()), then we will finish the current - // instance of the activity so a new fresh one can be started. - if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE - && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { - if (!ret.finishing) { - int index = indexOfTokenLocked(ret); - if (index >= 0) { - finishActivityLocked(ret, index, Activity.RESULT_CANCELED, - null, "clear"); - } - return null; - } - } - - return ret; - } - } - - return null; - } - - /** - * Find the activity in the history stack within the given task. Returns - * the index within the history at which it's found, or < 0 if not found. - */ - private final int findActivityInHistoryLocked(HistoryRecord r, int task) { - int i = mHistory.size(); - while (i > 0) { - i--; - HistoryRecord candidate = (HistoryRecord)mHistory.get(i); - if (candidate.task.taskId != task) { - break; - } - if (candidate.realActivity.equals(r.realActivity)) { - return i; - } - } - - return -1; - } - - /** - * Reorder the history stack so that the activity at the given index is - * brought to the front. - */ - private final HistoryRecord moveActivityToFrontLocked(int where) { - HistoryRecord newTop = (HistoryRecord)mHistory.remove(where); - int top = mHistory.size(); - HistoryRecord oldTop = (HistoryRecord)mHistory.get(top-1); - mHistory.add(top, newTop); - oldTop.frontOfTask = false; - newTop.frontOfTask = true; - return newTop; - } - - /** - * Deliver a new Intent to an existing activity, so that its onNewIntent() - * method will be called at the proper time. - */ - private final void deliverNewIntentLocked(HistoryRecord r, Intent intent) { - boolean sent = false; - if (r.state == ActivityState.RESUMED - && r.app != null && r.app.thread != null) { - try { - ArrayList<Intent> ar = new ArrayList<Intent>(); - ar.add(new Intent(intent)); - r.app.thread.scheduleNewIntent(ar, r); - sent = true; - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending new intent to " + r, e); - } - } - if (!sent) { - r.addNewIntentLocked(new Intent(intent)); - } - } - - private final void logStartActivity(int tag, HistoryRecord r, - TaskRecord task) { - EventLog.writeEvent(tag, - System.identityHashCode(r), task.taskId, - r.shortComponentName, r.intent.getAction(), - r.intent.getType(), r.intent.getDataString(), - r.intent.getFlags()); - } - - private final int startActivityLocked(IApplicationThread caller, - Intent intent, String resolvedType, - Uri[] grantedUriPermissions, - int grantedMode, ActivityInfo aInfo, IBinder resultTo, - String resultWho, int requestCode, - int callingPid, int callingUid, boolean onlyIfNeeded, - boolean componentSpecified) { - Slog.i(TAG, "Starting activity: " + intent); - - HistoryRecord sourceRecord = null; - HistoryRecord resultRecord = null; - if (resultTo != null) { - int index = indexOfTokenLocked(resultTo); - if (DEBUG_RESULTS) Slog.v( - TAG, "Sending result to " + resultTo + " (index " + index + ")"); - if (index >= 0) { - sourceRecord = (HistoryRecord)mHistory.get(index); - if (requestCode >= 0 && !sourceRecord.finishing) { - resultRecord = sourceRecord; - } - } - } - - int launchFlags = intent.getFlags(); - - if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 - && sourceRecord != null) { - // Transfer the result target from the source activity to the new - // one being started, including any failures. - if (requestCode >= 0) { - return START_FORWARD_AND_REQUEST_CONFLICT; - } - resultRecord = sourceRecord.resultTo; - resultWho = sourceRecord.resultWho; - requestCode = sourceRecord.requestCode; - sourceRecord.resultTo = null; - if (resultRecord != null) { - resultRecord.removeResultsLocked( - sourceRecord, resultWho, requestCode); - } - } - - int err = START_SUCCESS; - - if (intent.getComponent() == null) { - // We couldn't find a class that can handle the given Intent. - // That's the end of that! - err = START_INTENT_NOT_RESOLVED; - } - - if (err == START_SUCCESS && aInfo == null) { - // We couldn't find the specific class specified in the Intent. - // Also the end of the line. - err = START_CLASS_NOT_FOUND; - } - - ProcessRecord callerApp = null; - if (err == START_SUCCESS && caller != null) { - callerApp = getRecordForAppLocked(caller); - if (callerApp != null) { - callingPid = callerApp.pid; - callingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - err = START_PERMISSION_DENIED; - } - } - - if (err != START_SUCCESS) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - return err; - } - - final int perm = checkComponentPermission(aInfo.permission, callingPid, - callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid); - if (perm != PackageManager.PERMISSION_GRANTED) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - String msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ")" - + " requires " + aInfo.permission; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - if (mController != null) { - boolean abort = false; - try { - // The Intent we give to the watcher has the extra data - // stripped off, since it can contain private information. - Intent watchIntent = intent.cloneFilter(); - abort = !mController.activityStarting(watchIntent, - aInfo.applicationInfo.packageName); - } catch (RemoteException e) { - mController = null; - } - - if (abort) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - return START_SUCCESS; - } - } - - HistoryRecord r = new HistoryRecord(this, callerApp, callingUid, - intent, resolvedType, aInfo, mConfiguration, - resultRecord, resultWho, requestCode, componentSpecified); - - if (mResumedActivity == null - || mResumedActivity.info.applicationInfo.uid != callingUid) { - if (!checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { - PendingActivityLaunch pal = new PendingActivityLaunch(); - pal.r = r; - pal.sourceRecord = sourceRecord; - pal.grantedUriPermissions = grantedUriPermissions; - pal.grantedMode = grantedMode; - pal.onlyIfNeeded = onlyIfNeeded; - mPendingActivityLaunches.add(pal); - return START_SWITCHES_CANCELED; - } - } - - if (mDidAppSwitch) { - // This is the second allowed switch since we stopped switches, - // so now just generally allow switches. Use case: user presses - // home (switches disabled, switch to home, mDidAppSwitch now true); - // user taps a home icon (coming from home so allowed, we hit here - // and now allow anyone to switch again). - mAppSwitchesAllowedTime = 0; - } else { - mDidAppSwitch = true; - } - - doPendingActivityLaunchesLocked(false); - - return startActivityUncheckedLocked(r, sourceRecord, - grantedUriPermissions, grantedMode, onlyIfNeeded, true); - } - - private final void doPendingActivityLaunchesLocked(boolean doResume) { + final void doPendingActivityLaunchesLocked(boolean doResume) { final int N = mPendingActivityLaunches.size(); if (N <= 0) { return; } for (int i=0; i<N; i++) { PendingActivityLaunch pal = mPendingActivityLaunches.get(i); - startActivityUncheckedLocked(pal.r, pal.sourceRecord, + mMainStack.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.grantedUriPermissions, pal.grantedMode, pal.onlyIfNeeded, doResume && i == (N-1)); } mPendingActivityLaunches.clear(); } - private final int startActivityUncheckedLocked(HistoryRecord r, - HistoryRecord sourceRecord, Uri[] grantedUriPermissions, - int grantedMode, boolean onlyIfNeeded, boolean doResume) { - final Intent intent = r.intent; - final int callingUid = r.launchedFromUid; - - int launchFlags = intent.getFlags(); - - // We'll invoke onUserLeaving before onPause only if the launching - // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; - if (DEBUG_USER_LEAVING) Slog.v(TAG, - "startActivity() => mUserLeaving=" + mUserLeaving); - - // If the caller has asked not to resume at this point, we make note - // of this in the record so that we can skip it when trying to find - // the top running activity. - if (!doResume) { - r.delayedResume = true; - } - - HistoryRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) - != 0 ? r : null; - - // If the onlyIfNeeded flag is set, then we can do this if the activity - // being launched is the same as the one making the call... or, as - // a special case, if we do not know the caller then we count the - // current top activity as the caller. - if (onlyIfNeeded) { - HistoryRecord checkedCaller = sourceRecord; - if (checkedCaller == null) { - checkedCaller = topRunningNonDelayedActivityLocked(notTop); - } - if (!checkedCaller.realActivity.equals(r.realActivity)) { - // Caller is not the same as launcher, so always needed. - onlyIfNeeded = false; - } - } - - if (grantedUriPermissions != null && callingUid > 0) { - for (int i=0; i<grantedUriPermissions.length; i++) { - grantUriPermissionLocked(callingUid, r.packageName, - grantedUriPermissions[i], grantedMode, r); - } - } - - grantUriPermissionFromIntentLocked(callingUid, r.packageName, - intent, r); - - if (sourceRecord == null) { - // This activity is not being started from another... in this - // case we -always- start a new task. - if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { - Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: " - + intent); - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // The original activity who is starting us is running as a single - // instance... this new activity it is starting must go on its - // own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { - // The activity being started is a single instance... it always - // gets launched into its own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - - if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // For whatever reason this activity is being launched into a new - // task... yet the caller has requested a result back. Well, that - // is pretty messed up, so instead immediately send back a cancel - // and let the new task continue launched as normal without a - // dependency on its originator. - Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - r.resultTo = null; - } - - boolean addingToTask = false; - if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && - (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // If bring to front is requested, and no result is requested, and - // we can find a task that was started with this same - // component, then instead of launching bring that one to the front. - if (r.resultTo == null) { - // See if there is a task to bring to the front. If this is - // a SINGLE_INSTANCE activity, there can be one and only one - // instance of it in the history, and it is always in its own - // unique task, so we do a special search. - HistoryRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE - ? findTaskLocked(intent, r.info) - : findActivityLocked(intent, r.info); - if (taskTop != null) { - if (taskTop.task.intent == null) { - // This task was started because of movement of - // the activity based on affinity... now that we - // are actually launching it, we can assign the - // base intent. - taskTop.task.setIntent(intent, r.info); - } - // If the target task is not in the front, then we need - // to bring it to the front... except... well, with - // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like - // to have the same behavior as if a new instance was - // being started, which means not bringing it to the front - // if the caller is not itself in the front. - HistoryRecord curTop = topRunningNonDelayedActivityLocked(notTop); - if (curTop.task != taskTop.task) { - r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - boolean callerAtFront = sourceRecord == null - || curTop.task == sourceRecord.task; - if (callerAtFront) { - // We really do want to push this one into the - // user's face, right now. - moveTaskToFrontLocked(taskTop.task, r); - } - } - // If the caller has requested that the target task be - // reset, then do so. - if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - taskTop = resetTaskIfNeededLocked(taskTop, r); - } - if (onlyIfNeeded) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - return START_RETURN_INTENT_TO_CALLER; - } - if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // In this situation we want to remove all activities - // from the task up to the one being started. In most - // cases this means we are resetting the task to its - // initial state. - HistoryRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags, true); - if (top != null) { - if (top.frontOfTask) { - // Activity aliases may mean we use different - // intents for the top activity, so make sure - // the task now has the identity of the new - // intent. - top.task.setIntent(r.intent, r.info); - } - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - deliverNewIntentLocked(top, r.intent); - } else { - // A special case: we need to - // start the activity because it is not currently - // running, and the caller has asked to clear the - // current task to have this activity at the top. - addingToTask = true; - // Now pretend like this activity is being started - // by the top of its task, so it is put in the - // right place. - sourceRecord = taskTop; - } - } else if (r.realActivity.equals(taskTop.task.realActivity)) { - // In this case the top activity on the task is the - // same as the one being launched, so we take that - // as a request to bring the task to the foreground. - // If the top activity in the task is the root - // activity, deliver this new intent to it if it - // desires. - if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - && taskTop.realActivity.equals(r.realActivity)) { - logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task); - if (taskTop.frontOfTask) { - taskTop.task.setIntent(r.intent, r.info); - } - deliverNewIntentLocked(taskTop, r.intent); - } else if (!r.intent.filterEquals(taskTop.task.intent)) { - // In this case we are launching the root activity - // of the task, but with a different intent. We - // should start a new instance on top. - addingToTask = true; - sourceRecord = taskTop; - } - } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { - // In this case an activity is being launched in to an - // existing task, without resetting that task. This - // is typically the situation of launching an activity - // from a notification or shortcut. We want to place - // the new activity on top of the current task. - addingToTask = true; - sourceRecord = taskTop; - } else if (!taskTop.task.rootWasReset) { - // In this case we are launching in to an existing task - // that has not yet been started from its front door. - // The current task has been brought to the front. - // Ideally, we'd probably like to place this new task - // at the bottom of its stack, but that's a little hard - // to do with the current organization of the code so - // for now we'll just drop it. - taskTop.task.setIntent(r.intent, r.info); - } - if (!addingToTask) { - // We didn't do anything... but it was needed (a.k.a., client - // don't use that intent!) And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - return START_TASK_TO_FRONT; - } - } - } - } - - //String uri = r.intent.toURI(); - //Intent intent2 = new Intent(uri); - //Slog.i(TAG, "Given intent: " + r.intent); - //Slog.i(TAG, "URI is: " + uri); - //Slog.i(TAG, "To intent: " + intent2); - - if (r.packageName != null) { - // If the activity being launched is the same as the one currently - // at the top, then we need to check if it should only be launched - // once. - HistoryRecord top = topRunningNonDelayedActivityLocked(notTop); - if (top != null && r.resultTo == null) { - if (top.realActivity.equals(r.realActivity)) { - if (top.app != null && top.app.thread != null) { - if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { - logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - // For paranoia, make sure we have correctly - // resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - if (onlyIfNeeded) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! - return START_RETURN_INTENT_TO_CALLER; - } - deliverNewIntentLocked(top, r.intent); - return START_DELIVERED_TO_TOP; - } - } - } - } - - } else { - if (r.resultTo != null) { - sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - } - return START_CLASS_NOT_FOUND; - } - - boolean newTask = false; - - // Should this be considered a new task? - if (r.resultTo == null && !addingToTask - && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // todo: should do better management of integers. - mCurTask++; - if (mCurTask <= 0) { - mCurTask = 1; - } - r.task = new TaskRecord(mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new task " + r.task); - newTask = true; - addRecentTaskLocked(r.task); - - } else if (sourceRecord != null) { - if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - // In this case, we are adding the activity to an existing - // task, but the caller has asked to clear that task if the - // activity is already running. - HistoryRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags, true); - if (top != null) { - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - deliverNewIntentLocked(top, r.intent); - // For paranoia, make sure we have correctly - // resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - return START_DELIVERED_TO_TOP; - } - } else if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { - // In this case, we are launching an activity in our own task - // that may already be running somewhere in the history, and - // we want to shuffle it to the front of the stack if so. - int where = findActivityInHistoryLocked(r, sourceRecord.task.taskId); - if (where >= 0) { - HistoryRecord top = moveActivityToFrontLocked(where); - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - deliverNewIntentLocked(top, r.intent); - if (doResume) { - resumeTopActivityLocked(null); - } - return START_DELIVERED_TO_TOP; - } - } - // An existing activity is starting this new activity, so we want - // to keep the new one in the same task as the one that is starting - // it. - r.task = sourceRecord.task; - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in existing task " + r.task); - - } else { - // This not being started from an existing activity, and not part - // of a new task... just put it in the top task, though these days - // this case should never happen. - final int N = mHistory.size(); - HistoryRecord prev = - N > 0 ? (HistoryRecord)mHistory.get(N-1) : null; - r.task = prev != null - ? prev.task - : new TaskRecord(mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new guessed " + r.task); - } - if (newTask) { - EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); - } - logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - startActivityLocked(r, newTask, doResume); - return START_SUCCESS; - } - - void reportActivityLaunchedLocked(boolean timeout, HistoryRecord r, - long thisTime, long totalTime) { - for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { - WaitResult w = mWaitingActivityLaunched.get(i); - w.timeout = timeout; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); - } - w.thisTime = thisTime; - w.totalTime = totalTime; - } - notify(); - } - - void reportActivityVisibleLocked(HistoryRecord r) { - for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) { - WaitResult w = mWaitingActivityVisible.get(i); - w.timeout = false; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); - } - w.totalTime = SystemClock.uptimeMillis() - w.thisTime; - w.thisTime = w.totalTime; - } - notify(); - } - - private final int startActivityMayWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, WaitResult outResult, Configuration config) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - final boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - - // Collect information about the target of the Intent. - ActivityInfo aInfo; - try { - ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY - | STOCK_PM_FLAGS); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = null; - } - - if (aInfo != null) { - // Store the found target back into the intent, because now that - // we have it we never want to do this again. For example, if the - // user navigates back to this point in the history, we should - // always restart the exact same activity. - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - - // Don't debug things in the system process - if (debug) { - if (!aInfo.processName.equals("system")) { - setDebugApp(aInfo.processName, true, false); - } - } - } - - synchronized (this) { - int callingPid; - int callingUid; - if (caller == null) { - callingPid = Binder.getCallingPid(); - callingUid = Binder.getCallingUid(); - } else { - callingPid = callingUid = -1; - } - - mConfigWillChange = config != null && mConfiguration.diff(config) != 0; - if (DEBUG_CONFIGURATION) Slog.v(TAG, - "Starting activity when config will change = " + mConfigWillChange); - - final long origId = Binder.clearCallingIdentity(); - - int res = startActivityLocked(caller, intent, resolvedType, - grantedUriPermissions, grantedMode, aInfo, - resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified); - - if (mConfigWillChange) { - // If the caller also wants to switch to a new configuration, - // do so now. This allows a clean switch, as we are waiting - // for the current activity to pause (so we will not destroy - // it), and have not yet started the next activity. - enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, - "updateConfiguration()"); - mConfigWillChange = false; - if (DEBUG_CONFIGURATION) Slog.v(TAG, - "Updating to new configuration after starting activity."); - updateConfigurationLocked(config, null); - } - - Binder.restoreCallingIdentity(origId); - - if (outResult != null) { - outResult.result = res; - if (res == IActivityManager.START_SUCCESS) { - mWaitingActivityLaunched.add(outResult); - do { - try { - wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } else if (res == IActivityManager.START_TASK_TO_FRONT) { - HistoryRecord r = this.topRunningActivityLocked(null); - if (r.nowVisible) { - outResult.timeout = false; - outResult.who = new ComponentName(r.info.packageName, r.info.name); - outResult.totalTime = 0; - outResult.thisTime = 0; - } else { - outResult.thisTime = SystemClock.uptimeMillis(); - mWaitingActivityVisible.add(outResult); - do { - try { - wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } - } - } - - return res; - } - } - public final int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { - return startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); } @@ -3799,7 +1998,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { WaitResult res = new WaitResult(); - startActivityMayWait(caller, intent, resolvedType, + mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, res, null); return res; @@ -3810,7 +2009,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, config); } @@ -3834,8 +2033,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { // If this is coming from the currently resumed activity, it is // effectively saying that app switches are allowed at this point. - if (mResumedActivity != null - && mResumedActivity.info.applicationInfo.uid == + if (mMainStack.mResumedActivity != null + && mMainStack.mResumedActivity.info.applicationInfo.uid == Binder.getCallingUid()) { mAppSwitchesAllowedTime = 0; } @@ -3853,11 +2052,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized (this) { - int index = indexOfTokenLocked(callingActivity); + int index = mMainStack.indexOfTokenLocked(callingActivity); if (index < 0) { return false; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); if (r.app == null || r.app.thread == null) { // The caller is not running... d'oh! return false; @@ -3871,7 +2070,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityInfo aInfo = null; try { List<ResolveInfo> resolves = - ActivityThread.getPackageManager().queryIntentActivities( + AppGlobals.getPackageManager().queryIntentActivities( intent, r.resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); @@ -3915,7 +2114,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.finishing = true; // Propagate reply information over to the new activity. - final HistoryRecord resultTo = r.resultTo; + final ActivityRecord resultTo = r.resultTo; final String resultWho = r.resultWho; final int requestCode = r.requestCode; r.resultTo = null; @@ -3926,7 +2125,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); // XXX we are not dealing with propagating grantedUriPermissions... // those are not yet exposed to user code, so there is no need. - int res = startActivityLocked(r.app.thread, intent, + int res = mMainStack.startActivityLocked(r.app.thread, intent, r.resolvedType, null, 0, aInfo, resultTo, resultWho, requestCode, -1, r.launchedFromUid, false, false); Binder.restoreCallingIdentity(origId); @@ -3960,7 +2159,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityInfo aInfo; try { ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveIntent( + AppGlobals.getPackageManager().resolveIntent( intent, resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); aInfo = rInfo != null ? rInfo.activityInfo : null; @@ -3978,13 +2177,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized(this) { - return startActivityLocked(null, intent, resolvedType, + return mMainStack.startActivityLocked(null, intent, resolvedType, null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid, onlyIfNeeded, componentSpecified); } } - private final void addRecentTaskLocked(TaskRecord task) { + final void addRecentTaskLocked(TaskRecord task) { // Remove any existing entries that are the same kind of task. int N = mRecentTasks.size(); for (int i=0; i<N; i++) { @@ -4010,11 +2209,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void setRequestedOrientation(IBinder token, int requestedOrientation) { synchronized (this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); final long origId = Binder.clearCallingIdentity(); mWindowManager.setAppOrientation(r, requestedOrientation); Configuration config = mWindowManager.updateOrientationFromAppTokens( @@ -4023,7 +2222,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (config != null) { r.frozenBeforeDestroy = true; if (!updateConfigurationLocked(config, r)) { - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } } Binder.restoreCallingIdentity(origId); @@ -4032,250 +2231,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public int getRequestedOrientation(IBinder token) { synchronized (this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); return mWindowManager.getAppOrientation(r); } } - private final void stopActivityLocked(HistoryRecord r) { - if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 - || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { - if (!r.finishing) { - requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, - "no-history"); - } - } else if (r.app != null && r.app.thread != null) { - if (mFocusedActivity == r) { - setFocusedActivityLocked(topRunningActivityLocked(null)); - } - r.resumeKeyDispatchingLocked(); - try { - r.stopped = false; - r.state = ActivityState.STOPPING; - if (DEBUG_VISBILITY) Slog.v( - TAG, "Stopping visible=" + r.visible + " for " + r); - if (!r.visible) { - mWindowManager.setAppVisibility(r, false); - } - r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags); - } catch (Exception e) { - // Maybe just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - Slog.w(TAG, "Exception thrown during pause", e); - // Just in case, assume it to be stopped. - r.stopped = true; - r.state = ActivityState.STOPPED; - if (r.configDestroy) { - destroyActivityLocked(r, true); - } - } - } - } - - /** - * @return Returns true if the activity is being finished, false if for - * some reason it is being left as-is. - */ - private final boolean requestFinishActivityLocked(IBinder token, int resultCode, - Intent resultData, String reason) { - if (DEBUG_RESULTS) Slog.v( - TAG, "Finishing activity: token=" + token - + ", result=" + resultCode + ", data=" + resultData); - - int index = indexOfTokenLocked(token); - if (index < 0) { - return false; - } - HistoryRecord r = (HistoryRecord)mHistory.get(index); - - // Is this the last activity left? - boolean lastActivity = true; - for (int i=mHistory.size()-1; i>=0; i--) { - HistoryRecord p = (HistoryRecord)mHistory.get(i); - if (!p.finishing && p != r) { - lastActivity = false; - break; - } - } - - // If this is the last activity, but it is the home activity, then - // just don't finish it. - if (lastActivity) { - if (r.intent.hasCategory(Intent.CATEGORY_HOME)) { - return false; - } - } - - finishActivityLocked(r, index, resultCode, resultData, reason); - return true; - } - - /** - * @return Returns true if this activity has been removed from the history - * list, or false if it is still in the list and will be removed later. - */ - private final boolean finishActivityLocked(HistoryRecord r, int index, - int resultCode, Intent resultData, String reason) { - if (r.finishing) { - Slog.w(TAG, "Duplicate finish request for " + r); - return false; - } - - r.finishing = true; - EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, - System.identityHashCode(r), - r.task.taskId, r.shortComponentName, reason); - r.task.numActivities--; - if (index < (mHistory.size()-1)) { - HistoryRecord next = (HistoryRecord)mHistory.get(index+1); - if (next.task == r.task) { - if (r.frontOfTask) { - // The next activity is now the front of the task. - next.frontOfTask = true; - } - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { - // If the caller asked that this activity (and all above it) - // be cleared when the task is reset, don't lose that information, - // but propagate it up to the next activity. - next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - } - } - } - - r.pauseKeyDispatchingLocked(); - if (mFocusedActivity == r) { - setFocusedActivityLocked(topRunningActivityLocked(null)); - } - - // send the result - HistoryRecord resultTo = r.resultTo; - if (resultTo != null) { - if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo - + " who=" + r.resultWho + " req=" + r.requestCode - + " res=" + resultCode + " data=" + resultData); - if (r.info.applicationInfo.uid > 0) { - grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid, - r.packageName, resultData, r); - } - resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, - resultData); - r.resultTo = null; - } - else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r); - - // Make sure this HistoryRecord is not holding on to other resources, - // because clients have remote IPC references to this object so we - // can't assume that will go away and want to avoid circular IPC refs. - r.results = null; - r.pendingResults = null; - r.newIntents = null; - r.icicle = null; - - if (mPendingThumbnails.size() > 0) { - // There are clients waiting to receive thumbnails so, in case - // this is an activity that someone is waiting for, add it - // to the pending list so we can correctly update the clients. - mCancelledThumbnails.add(r); - } - - if (mResumedActivity == r) { - boolean endTask = index <= 0 - || ((HistoryRecord)mHistory.get(index-1)).task != r.task; - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare close transition: finishing " + r); - mWindowManager.prepareAppTransition(endTask - ? WindowManagerPolicy.TRANSIT_TASK_CLOSE - : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE); - - // Tell window manager to prepare for this one to be removed. - mWindowManager.setAppVisibility(r, false); - - if (mPausingActivity == null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); - if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); - startPausingLocked(false, false); - } - - } else if (r.state != ActivityState.PAUSING) { - // If the activity is PAUSING, we will complete the finish once - // it is done pausing; else we can just directly finish it here. - if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r); - return finishCurrentActivityLocked(r, index, - FINISH_AFTER_PAUSE) == null; - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); - } - - return false; - } - - private static final int FINISH_IMMEDIATELY = 0; - private static final int FINISH_AFTER_PAUSE = 1; - private static final int FINISH_AFTER_VISIBLE = 2; - - private final HistoryRecord finishCurrentActivityLocked(HistoryRecord r, - int mode) { - final int index = indexOfTokenLocked(r); - if (index < 0) { - return null; - } - - return finishCurrentActivityLocked(r, index, mode); - } - - private final HistoryRecord finishCurrentActivityLocked(HistoryRecord r, - int index, int mode) { - // First things first: if this activity is currently visible, - // and the resumed activity is not yet visible, then hold off on - // finishing until the resumed one becomes visible. - if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) { - if (!mStoppingActivities.contains(r)) { - mStoppingActivities.add(r); - if (mStoppingActivities.size() > 3) { - // If we already have a few activities waiting to stop, - // then give up on things going idle and start clearing - // them out. - Message msg = Message.obtain(); - msg.what = ActivityManagerService.IDLE_NOW_MSG; - mHandler.sendMessage(msg); - } - } - r.state = ActivityState.STOPPING; - updateOomAdjLocked(); - return r; - } - - // make sure the record is cleaned out of other places. - mStoppingActivities.remove(r); - mWaitingVisibleActivities.remove(r); - if (mResumedActivity == r) { - mResumedActivity = null; - } - final ActivityState prevState = r.state; - r.state = ActivityState.FINISHING; - - if (mode == FINISH_IMMEDIATELY - || prevState == ActivityState.STOPPED - || prevState == ActivityState.INITIALIZING) { - // If this activity is already stopped, we can just finish - // it right now. - return destroyActivityLocked(r, true) ? null : r; - } else { - // Need to go through the full pause cycle to get this - // activity into the stopped state and then finish it. - if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r); - mFinishingActivities.add(r); - resumeTopActivityLocked(null); - } - return r; - } - /** * This is the internal entry point for handling Activity.finish(). * @@ -4294,7 +2258,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (mController != null) { // Find the first activity that is not finishing. - HistoryRecord next = topRunningActivityLocked(token, 0); + ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0); if (next != null) { // ask watcher if this is allowed boolean resumeOK = true; @@ -4310,57 +2274,119 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } final long origId = Binder.clearCallingIdentity(); - boolean res = requestFinishActivityLocked(token, resultCode, + boolean res = mMainStack.requestFinishActivityLocked(token, resultCode, resultData, "app-request"); Binder.restoreCallingIdentity(origId); return res; } } - void sendActivityResultLocked(int callingUid, HistoryRecord r, - String resultWho, int requestCode, int resultCode, Intent data) { - - if (callingUid > 0) { - grantUriPermissionFromIntentLocked(callingUid, r.packageName, - data, r); + public final void finishHeavyWeightApp() { + if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: finishHeavyWeightApp() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES; + Slog.w(TAG, msg); + throw new SecurityException(msg); } - - if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r - + " : who=" + resultWho + " req=" + requestCode - + " res=" + resultCode + " data=" + data); - if (mResumedActivity == r && r.app != null && r.app.thread != null) { - try { - ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); - list.add(new ResultInfo(resultWho, requestCode, - resultCode, data)); - r.app.thread.scheduleSendResult(r, list); + + synchronized(this) { + if (mHeavyWeightProcess == null) { return; - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending result to " + r, e); } + + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>( + mHeavyWeightProcess.activities); + for (int i=0; i<activities.size(); i++) { + ActivityRecord r = activities.get(i); + if (!r.finishing) { + int index = mMainStack.indexOfTokenLocked(r); + if (index >= 0) { + mMainStack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, + null, "finish-heavy"); + } + } + } + + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); } - - r.addResultLocked(null, resultWho, requestCode, resultCode, data); } - + + public void crashApplication(int uid, int initialPid, String packageName, + String message) { + if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: crashApplication() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + synchronized(this) { + ProcessRecord proc = null; + + // Figure out which process to kill. We don't trust that initialPid + // still has any relation to current pids, so must scan through the + // list. + synchronized (mPidsSelfLocked) { + for (int i=0; i<mPidsSelfLocked.size(); i++) { + ProcessRecord p = mPidsSelfLocked.valueAt(i); + if (p.info.uid != uid) { + continue; + } + if (p.pid == initialPid) { + proc = p; + break; + } + for (String str : p.pkgList) { + if (str.equals(packageName)) { + proc = p; + } + } + } + } + + if (proc == null) { + Log.w(TAG, "crashApplication: nothing for uid=" + uid + + " initialPid=" + initialPid + + " packageName=" + packageName); + return; + } + + if (proc.thread != null) { + long ident = Binder.clearCallingIdentity(); + try { + proc.thread.scheduleCrash(message); + } catch (RemoteException e) { + } + Binder.restoreCallingIdentity(ident); + } + } + } + public final void finishSubActivity(IBinder token, String resultWho, int requestCode) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord self = (HistoryRecord)mHistory.get(index); + ActivityRecord self = (ActivityRecord)mMainStack.mHistory.get(index); final long origId = Binder.clearCallingIdentity(); int i; - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.resultTo == self && r.requestCode == requestCode) { if ((r.resultWho == null && resultWho == null) || (r.resultWho != null && r.resultWho.equals(resultWho))) { - finishActivityLocked(r, i, + mMainStack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "request-sub"); } } @@ -4373,8 +2399,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public boolean willActivityBeVisible(IBinder token) { synchronized(this) { int i; - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r == token) { return true; } @@ -4389,11 +2415,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord self = (HistoryRecord)mHistory.get(index); + ActivityRecord self = (ActivityRecord)mMainStack.mHistory.get(index); final long origId = Binder.clearCallingIdentity(); @@ -4408,188 +2434,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } /** - * Perform clean-up of service connections in an activity record. - */ - private final void cleanUpActivityServicesLocked(HistoryRecord r) { - // Throw away any services that have been bound by this activity. - if (r.connections != null) { - Iterator<ConnectionRecord> it = r.connections.iterator(); - while (it.hasNext()) { - ConnectionRecord c = it.next(); - removeConnectionLocked(c, null, r); - } - r.connections = null; - } - } - - /** - * Perform the common clean-up of an activity record. This is called both - * as part of destroyActivityLocked() (when destroying the client-side - * representation) and cleaning things up as a result of its hosting - * processing going away, in which case there is no remaining client-side - * state to destroy so only the cleanup here is needed. - */ - private final void cleanUpActivityLocked(HistoryRecord r, boolean cleanServices) { - if (mResumedActivity == r) { - mResumedActivity = null; - } - if (mFocusedActivity == r) { - mFocusedActivity = null; - } - - r.configDestroy = false; - r.frozenBeforeDestroy = false; - - // Make sure this record is no longer in the pending finishes list. - // This could happen, for example, if we are trimming activities - // down to the max limit while they are still waiting to finish. - mFinishingActivities.remove(r); - mWaitingVisibleActivities.remove(r); - - // Remove any pending results. - if (r.finishing && r.pendingResults != null) { - for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { - PendingIntentRecord rec = apr.get(); - if (rec != null) { - cancelIntentSenderLocked(rec, false); - } - } - r.pendingResults = null; - } - - if (cleanServices) { - cleanUpActivityServicesLocked(r); - } - - if (mPendingThumbnails.size() > 0) { - // There are clients waiting to receive thumbnails so, in case - // this is an activity that someone is waiting for, add it - // to the pending list so we can correctly update the clients. - mCancelledThumbnails.add(r); - } - - // Get rid of any pending idle timeouts. - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); - } - - private final void removeActivityFromHistoryLocked(HistoryRecord r) { - if (r.state != ActivityState.DESTROYED) { - mHistory.remove(r); - r.inHistory = false; - r.state = ActivityState.DESTROYED; - mWindowManager.removeAppToken(r); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - cleanUpActivityServicesLocked(r); - removeActivityUriPermissionsLocked(r); - } - } - - /** - * Destroy the current CLIENT SIDE instance of an activity. This may be - * called both when actually finishing an activity, or when performing - * a configuration switch where we destroy the current client-side object - * but then create a new client-side object for this same HistoryRecord. - */ - private final boolean destroyActivityLocked(HistoryRecord r, - boolean removeFromApp) { - if (DEBUG_SWITCH) Slog.v( - TAG, "Removing activity: token=" + r - + ", app=" + (r.app != null ? r.app.processName : "(null)")); - EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, - System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - - boolean removedFromHistory = false; - - cleanUpActivityLocked(r, false); - - final boolean hadApp = r.app != null; - - if (hadApp) { - if (removeFromApp) { - int idx = r.app.activities.indexOf(r); - if (idx >= 0) { - r.app.activities.remove(idx); - } - if (r.persistent) { - decPersistentCountLocked(r.app); - } - if (r.app.activities.size() == 0) { - // No longer have activities, so update location in - // LRU list. - updateLruProcessLocked(r.app, true, false); - } - } - - boolean skipDestroy = false; - - try { - if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r); - r.app.thread.scheduleDestroyActivity(r, r.finishing, - r.configChangeFlags); - } catch (Exception e) { - // We can just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - //Slog.w(TAG, "Exception thrown during finish", e); - if (r.finishing) { - removeActivityFromHistoryLocked(r); - removedFromHistory = true; - skipDestroy = true; - } - } - - r.app = null; - r.nowVisible = false; - - if (r.finishing && !skipDestroy) { - r.state = ActivityState.DESTROYING; - Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG); - msg.obj = r; - mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); - } else { - r.state = ActivityState.DESTROYED; - } - } else { - // remove this record from the history. - if (r.finishing) { - removeActivityFromHistoryLocked(r); - removedFromHistory = true; - } else { - r.state = ActivityState.DESTROYED; - } - } - - r.configChangeFlags = 0; - - if (!mLRUActivities.remove(r) && hadApp) { - Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); - } - - return removedFromHistory; - } - - private static void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app) { - int i = list.size(); - if (localLOGV) Slog.v( - TAG, "Removing app " + app + " from list " + list - + " with " + i + " entries"); - while (i > 0) { - i--; - HistoryRecord r = (HistoryRecord)list.get(i); - if (localLOGV) Slog.v( - TAG, "Record #" + i + " " + r + ": app=" + r.app); - if (r.app == app) { - if (localLOGV) Slog.v(TAG, "Removing this entry!"); - list.remove(i); - } - } - } - - /** * Main function for removing an existing process from the activity manager * as a result of that process going away. Clears out all connections * to the process. @@ -4602,30 +2446,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Just in case... - if (mPausingActivity != null && mPausingActivity.app == app) { - if (DEBUG_PAUSE) Slog.v(TAG, "App died while pausing: " + mPausingActivity); - mPausingActivity = null; + if (mMainStack.mPausingActivity != null && mMainStack.mPausingActivity.app == app) { + if (DEBUG_PAUSE) Slog.v(TAG, "App died while pausing: " +mMainStack.mPausingActivity); + mMainStack.mPausingActivity = null; } - if (mLastPausedActivity != null && mLastPausedActivity.app == app) { - mLastPausedActivity = null; + if (mMainStack.mLastPausedActivity != null && mMainStack.mLastPausedActivity.app == app) { + mMainStack.mLastPausedActivity = null; } // Remove this application's activities from active lists. - removeHistoryRecordsForAppLocked(mLRUActivities, app); - removeHistoryRecordsForAppLocked(mStoppingActivities, app); - removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app); - removeHistoryRecordsForAppLocked(mFinishingActivities, app); + mMainStack.removeHistoryRecordsForAppLocked(app); boolean atTop = true; boolean hasVisibleActivities = false; // Clean out the history list. - int i = mHistory.size(); + int i = mMainStack.mHistory.size(); if (localLOGV) Slog.v( TAG, "Removing app " + app + " from history with " + i + " entries"); while (i > 0) { i--; - HistoryRecord r = (HistoryRecord)mHistory.get(i); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (localLOGV) Slog.v( TAG, "Record #" + i + " " + r + ": app=" + r.app); if (r.app == app) { @@ -4633,14 +2474,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (localLOGV) Slog.v( TAG, "Removing this entry! frozen=" + r.haveState + " finishing=" + r.finishing); - mHistory.remove(i); + mMainStack.mHistory.remove(i); r.inHistory = false; mWindowManager.removeAppToken(r); if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); + mWindowManager.validateAppTokens(mMainStack.mHistory); } - removeActivityUriPermissionsLocked(r); + r.removeUriPermissionsLocked(); } else { // We have the current state for this activity, so @@ -4657,7 +2498,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - cleanUpActivityLocked(r, true); + r.stack.cleanUpActivityLocked(r, true); r.state = ActivityState.STOPPED; } atTop = false; @@ -4674,14 +2515,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (!restarting) { - if (!resumeTopActivityLocked(null)) { + if (!mMainStack.resumeTopActivityLocked(null)) { // If there was nothing to resume, and we are not already // restarting this process, but there is a visible activity that // is hosted by the process... then make sure all visible // activities are running, taking care of restarting this // process. if (hasVisibleActivities) { - ensureActivitiesVisibleLocked(null, 0); + mMainStack.ensureActivitiesVisibleLocked(null, 0); } } } @@ -4700,7 +2541,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return -1; } - private final ProcessRecord getRecordForAppLocked( + final ProcessRecord getRecordForAppLocked( IApplicationThread thread) { if (thread == null) { return null; @@ -4710,7 +2551,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return appIndex >= 0 ? mLruProcesses.get(appIndex) : null; } - private final void appDiedLocked(ProcessRecord app, int pid, + final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) { mProcDeaths[0]++; @@ -4752,8 +2593,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen (rec.lastLowMemory+GC_MIN_INTERVAL) <= now) { // The low memory report is overriding any current // state for a GC request. Make sure to do - // visible/foreground processes first. - if (rec.setAdj <= VISIBLE_APP_ADJ) { + // heavy/important/visible/foreground processes first. + if (rec.setAdj <= HEAVY_WEIGHT_APP_ADJ) { rec.lastRequestedGc = 0; } else { rec.lastRequestedGc = rec.lastLowMemory; @@ -4830,8 +2671,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return tracesFile; } - final void appNotResponding(ProcessRecord app, HistoryRecord activity, - HistoryRecord parent, final String annotation) { + final void appNotResponding(ProcessRecord app, ActivityRecord activity, + ActivityRecord parent, final String annotation) { ArrayList<Integer> pids = new ArrayList<Integer>(20); synchronized (this) { @@ -4950,8 +2791,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void decPersistentCountLocked(ProcessRecord app) - { + final void decPersistentCountLocked(ProcessRecord app) { app.persistentActivities--; if (app.persistentActivities > 0) { // Still more of 'em... @@ -4979,11 +2819,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); ProcessRecord app = r.app; if (localLOGV) Slog.v( @@ -5035,7 +2875,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int pid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); try { - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { @@ -5092,7 +2932,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long callingId = Binder.clearCallingIdentity(); try { - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { @@ -5124,7 +2964,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long callingId = Binder.clearCallingIdentity(); try { - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { @@ -5194,10 +3034,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mWindowManager.closeSystemDialogs(reason); - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { - finishActivityLocked(r, i, + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "close-sys"); } } @@ -5298,7 +3138,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (uid < 0) { try { - uid = ActivityThread.getPackageManager().getPackageUid(name); + uid = AppGlobals.getPackageManager().getPackageUid(name); } catch (RemoteException e) { } } @@ -5318,8 +3158,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean didSomething = killPackageProcessesLocked(name, uid, -100, callerWillRestart, doit); - for (i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.packageName.equals(name)) { if (!doit) { return true; @@ -5330,7 +3170,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.app.removed = true; } r.app = null; - finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "uninstall"); + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "uninstall"); } } @@ -5362,7 +3202,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ac.removePackage(name); } } - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } return didSomething; @@ -5376,6 +3216,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + "/" + uid + ")"); mProcessNames.remove(name, uid); + if (mHeavyWeightProcess == app) { + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); + } boolean needRestart = false; if (app.pid > 0 && app.pid != MY_PID) { int pid = app.pid; @@ -5417,6 +3261,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, pid, app.info.uid, app.processName); mProcessNames.remove(app.processName, app.info.uid); + if (mHeavyWeightProcess == app) { + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); + } // Take care of any launching providers waiting for this process. checkAppInLaunchingProvidersLocked(app, true); // Take care of any services that are waiting for the process. @@ -5583,12 +3431,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean didSomething = false; // See if the top visible activity is waiting to run in this process... - HistoryRecord hr = topRunningActivityLocked(null); + ActivityRecord hr = mMainStack.topRunningActivityLocked(null); if (hr != null && normalMode) { if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid && processName.equals(hr.processName)) { try { - if (realStartActivityLocked(hr, app, true, true)) { + if (mMainStack.realStartActivityLocked(hr, app, true, true)) { didSomething = true; } } catch (Exception e) { @@ -5597,7 +3445,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen badApp = true; } } else { - ensureActivitiesVisibleLocked(hr, null, processName, 0); + mMainStack.ensureActivitiesVisibleLocked(hr, null, processName, 0); } } @@ -5681,195 +3529,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public final void activityIdle(IBinder token, Configuration config) { final long origId = Binder.clearCallingIdentity(); - activityIdleInternal(token, false, config); + mMainStack.activityIdleInternal(token, false, config); Binder.restoreCallingIdentity(origId); } - final ArrayList<HistoryRecord> processStoppingActivitiesLocked( - boolean remove) { - int N = mStoppingActivities.size(); - if (N <= 0) return null; - - ArrayList<HistoryRecord> stops = null; - - final boolean nowVisible = mResumedActivity != null - && mResumedActivity.nowVisible - && !mResumedActivity.waitingVisible; - for (int i=0; i<N; i++) { - HistoryRecord s = mStoppingActivities.get(i); - if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" - + nowVisible + " waitingVisible=" + s.waitingVisible - + " finishing=" + s.finishing); - if (s.waitingVisible && nowVisible) { - mWaitingVisibleActivities.remove(s); - s.waitingVisible = false; - if (s.finishing) { - // If this activity is finishing, it is sitting on top of - // everyone else but we now know it is no longer needed... - // so get rid of it. Otherwise, we need to go through the - // normal flow and hide it once we determine that it is - // hidden by the activities in front of it. - if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); - mWindowManager.setAppVisibility(s, false); - } - } - if (!s.waitingVisible && remove) { - if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); - if (stops == null) { - stops = new ArrayList<HistoryRecord>(); - } - stops.add(s); - mStoppingActivities.remove(i); - N--; - i--; - } - } - - return stops; - } - void enableScreenAfterBoot() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN, SystemClock.uptimeMillis()); mWindowManager.enableScreenAfterBoot(); } - final void activityIdleInternal(IBinder token, boolean fromTimeout, - Configuration config) { - if (localLOGV) Slog.v(TAG, "Activity idle: " + token); - - ArrayList<HistoryRecord> stops = null; - ArrayList<HistoryRecord> finishes = null; - ArrayList<HistoryRecord> thumbnails = null; - int NS = 0; - int NF = 0; - int NT = 0; - IApplicationThread sendThumbnail = null; - boolean booting = false; - boolean enableScreen = false; - - synchronized (this) { - if (token != null) { - mHandler.removeMessages(IDLE_TIMEOUT_MSG, token); - } - - // Get the activity record. - int index = indexOfTokenLocked(token); - if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); - - if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, r, -1, -1); - } - - // This is a hack to semi-deal with a race condition - // in the client where it can be constructed with a - // newer configuration from when we asked it to launch. - // We'll update with whatever configuration it now says - // it used to launch. - if (config != null) { - r.configuration = config; - } - - // No longer need to keep the device awake. - if (mResumedActivity == r && mLaunchingActivity.isHeld()) { - mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); - mLaunchingActivity.release(); - } - - // We are now idle. If someone is waiting for a thumbnail from - // us, we can now deliver. - r.idle = true; - scheduleAppGcsLocked(); - if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { - sendThumbnail = r.app.thread; - r.thumbnailNeeded = false; - } - - // If this activity is fullscreen, set up to hide those under it. - - if (DEBUG_VISBILITY) Slog.v(TAG, "Idle activity for " + r); - ensureActivitiesVisibleLocked(null, 0); - - //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); - if (!mBooted && !fromTimeout) { - mBooted = true; - enableScreen = true; - } - - } else if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, null, -1, -1); - } - - // Atomically retrieve all of the other things to do. - stops = processStoppingActivitiesLocked(true); - NS = stops != null ? stops.size() : 0; - if ((NF=mFinishingActivities.size()) > 0) { - finishes = new ArrayList<HistoryRecord>(mFinishingActivities); - mFinishingActivities.clear(); - } - if ((NT=mCancelledThumbnails.size()) > 0) { - thumbnails = new ArrayList<HistoryRecord>(mCancelledThumbnails); - mCancelledThumbnails.clear(); - } - - booting = mBooting; - mBooting = false; - } - - int i; - - // Send thumbnail if requested. - if (sendThumbnail != null) { - try { - sendThumbnail.requestThumbnail(token); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown when requesting thumbnail", e); - sendPendingThumbnail(null, token, null, null, true); - } - } - - // Stop any activities that are scheduled to do so but have been - // waiting for the next one to start. - for (i=0; i<NS; i++) { - HistoryRecord r = (HistoryRecord)stops.get(i); - synchronized (this) { - if (r.finishing) { - finishCurrentActivityLocked(r, FINISH_IMMEDIATELY); - } else { - stopActivityLocked(r); - } - } - } - - // Finish any activities that are scheduled to do so but have been - // waiting for the next one to start. - for (i=0; i<NF; i++) { - HistoryRecord r = (HistoryRecord)finishes.get(i); - synchronized (this) { - destroyActivityLocked(r, true); - } - } - - // Report back to any thumbnail receivers. - for (i=0; i<NT; i++) { - HistoryRecord r = (HistoryRecord)thumbnails.get(i); - sendPendingThumbnail(r, null, null, null, true); - } - - if (booting) { - finishBooting(); - } - - trimApplications(); - //dump(); - //mWindowManager.dump(); - - if (enableScreen) { - enableScreenAfterBoot(); - } - } - final void finishBooting() { IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); @@ -5940,60 +3609,31 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } final long origId = Binder.clearCallingIdentity(); - activityPaused(token, icicle, false); + mMainStack.activityPaused(token, icicle, false); Binder.restoreCallingIdentity(origId); } - final void activityPaused(IBinder token, Bundle icicle, boolean timeout) { - if (DEBUG_PAUSE) Slog.v( - TAG, "Activity paused: token=" + token + ", icicle=" + icicle - + ", timeout=" + timeout); - - HistoryRecord r = null; - - synchronized (this) { - int index = indexOfTokenLocked(token); - if (index >= 0) { - r = (HistoryRecord)mHistory.get(index); - if (!timeout) { - r.icicle = icicle; - r.haveState = true; - } - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - if (mPausingActivity == r) { - r.state = ActivityState.PAUSED; - completePauseLocked(); - } else { - EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, - System.identityHashCode(r), r.shortComponentName, - mPausingActivity != null - ? mPausingActivity.shortComponentName : "(none)"); - } - } - } - } - public final void activityStopped(IBinder token, Bitmap thumbnail, CharSequence description) { if (localLOGV) Slog.v( TAG, "Activity stopped: token=" + token); - HistoryRecord r = null; + ActivityRecord r = null; final long origId = Binder.clearCallingIdentity(); synchronized (this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - r = (HistoryRecord)mHistory.get(index); + r = (ActivityRecord)mMainStack.mHistory.get(index); r.thumbnail = thumbnail; r.description = description; r.stopped = true; r.state = ActivityState.STOPPED; if (!r.finishing) { if (r.configDestroy) { - destroyActivityLocked(r, true); - resumeTopActivityLocked(null); + r.stack.destroyActivityLocked(r, true); + r.stack.resumeTopActivityLocked(null); } } } @@ -6010,39 +3650,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public final void activityDestroyed(IBinder token) { if (DEBUG_SWITCH) Slog.v(TAG, "ACTIVITY DESTROYED: " + token); - synchronized (this) { - mHandler.removeMessages(DESTROY_TIMEOUT_MSG, token); - - int index = indexOfTokenLocked(token); - if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); - if (r.state == ActivityState.DESTROYING) { - final long origId = Binder.clearCallingIdentity(); - removeActivityFromHistoryLocked(r); - Binder.restoreCallingIdentity(origId); - } - } - } + mMainStack.activityDestroyed(token); } public String getCallingPackage(IBinder token) { synchronized (this) { - HistoryRecord r = getCallingRecordLocked(token); + ActivityRecord r = getCallingRecordLocked(token); return r != null && r.app != null ? r.info.packageName : null; } } public ComponentName getCallingActivity(IBinder token) { synchronized (this) { - HistoryRecord r = getCallingRecordLocked(token); + ActivityRecord r = getCallingRecordLocked(token); return r != null ? r.intent.getComponent() : null; } } - private HistoryRecord getCallingRecordLocked(IBinder token) { - int index = indexOfTokenLocked(token); + private ActivityRecord getCallingRecordLocked(IBinder token) { + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); if (r != null) { return r.resultTo; } @@ -6052,9 +3680,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public ComponentName getActivityClassForToken(IBinder token) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); return r.intent.getComponent(); } return null; @@ -6063,9 +3691,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public String getPackageForToken(IBinder token) { synchronized(this) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(index); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); return r.packageName; } return null; @@ -6092,7 +3720,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { if (callingUid != 0 && callingUid != Process.SYSTEM_UID && Process.supportsProcesses()) { - int uid = ActivityThread.getPackageManager() + int uid = AppGlobals.getPackageManager() .getPackageUid(packageName); if (uid != Binder.getCallingUid()) { String msg = "Permission Denial: getIntentSender() from pid=" @@ -6104,57 +3732,66 @@ public final class ActivityManagerService extends ActivityManagerNative implemen throw new SecurityException(msg); } } + + return getIntentSenderLocked(type, packageName, callingUid, + token, resultWho, requestCode, intent, resolvedType, flags); + } catch (RemoteException e) { throw new SecurityException(e); } - HistoryRecord activity = null; - if (type == INTENT_SENDER_ACTIVITY_RESULT) { - int index = indexOfTokenLocked(token); - if (index < 0) { - return null; - } - activity = (HistoryRecord)mHistory.get(index); - if (activity.finishing) { - return null; - } + } + } + + IIntentSender getIntentSenderLocked(int type, + String packageName, int callingUid, IBinder token, String resultWho, + int requestCode, Intent intent, String resolvedType, int flags) { + ActivityRecord activity = null; + if (type == INTENT_SENDER_ACTIVITY_RESULT) { + int index = mMainStack.indexOfTokenLocked(token); + if (index < 0) { + return null; } + activity = (ActivityRecord)mMainStack.mHistory.get(index); + if (activity.finishing) { + return null; + } + } - final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0; - final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0; - final boolean updateCurrent = (flags&PendingIntent.FLAG_UPDATE_CURRENT) != 0; - flags &= ~(PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_CANCEL_CURRENT - |PendingIntent.FLAG_UPDATE_CURRENT); + final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0; + final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0; + final boolean updateCurrent = (flags&PendingIntent.FLAG_UPDATE_CURRENT) != 0; + flags &= ~(PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_CANCEL_CURRENT + |PendingIntent.FLAG_UPDATE_CURRENT); - PendingIntentRecord.Key key = new PendingIntentRecord.Key( - type, packageName, activity, resultWho, - requestCode, intent, resolvedType, flags); - WeakReference<PendingIntentRecord> ref; - ref = mIntentSenderRecords.get(key); - PendingIntentRecord rec = ref != null ? ref.get() : null; - if (rec != null) { - if (!cancelCurrent) { - if (updateCurrent) { - rec.key.requestIntent.replaceExtras(intent); - } - return rec; + PendingIntentRecord.Key key = new PendingIntentRecord.Key( + type, packageName, activity, resultWho, + requestCode, intent, resolvedType, flags); + WeakReference<PendingIntentRecord> ref; + ref = mIntentSenderRecords.get(key); + PendingIntentRecord rec = ref != null ? ref.get() : null; + if (rec != null) { + if (!cancelCurrent) { + if (updateCurrent) { + rec.key.requestIntent.replaceExtras(intent); } - rec.canceled = true; - mIntentSenderRecords.remove(key); - } - if (noCreate) { return rec; } - rec = new PendingIntentRecord(this, key, callingUid); - mIntentSenderRecords.put(key, rec.ref); - if (type == INTENT_SENDER_ACTIVITY_RESULT) { - if (activity.pendingResults == null) { - activity.pendingResults - = new HashSet<WeakReference<PendingIntentRecord>>(); - } - activity.pendingResults.add(rec.ref); - } + rec.canceled = true; + mIntentSenderRecords.remove(key); + } + if (noCreate) { return rec; } + rec = new PendingIntentRecord(this, key, callingUid); + mIntentSenderRecords.put(key, rec.ref); + if (type == INTENT_SENDER_ACTIVITY_RESULT) { + if (activity.pendingResults == null) { + activity.pendingResults + = new HashSet<WeakReference<PendingIntentRecord>>(); + } + activity.pendingResults.add(rec.ref); + } + return rec; } public void cancelIntentSender(IIntentSender sender) { @@ -6164,7 +3801,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { PendingIntentRecord rec = (PendingIntentRecord)sender; try { - int uid = ActivityThread.getPackageManager() + int uid = AppGlobals.getPackageManager() .getPackageUid(rec.key.packageName); if (uid != Binder.getCallingUid()) { String msg = "Permission Denial: cancelIntentSender() from pid=" @@ -6323,7 +3960,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return PackageManager.PERMISSION_GRANTED; } try { - return ActivityThread.getPackageManager() + return AppGlobals.getPackageManager() .checkUidPermission(permission, uid); } catch (RemoteException e) { // Should never happen, but if it does... deny! @@ -6431,8 +4068,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void grantUriPermissionLocked(int callingUid, - String targetPkg, Uri uri, int modeFlags, HistoryRecord activity) { + void grantUriPermissionLocked(int callingUid, + String targetPkg, Uri uri, int modeFlags, ActivityRecord activity) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (modeFlags == 0) { @@ -6442,7 +4079,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Requested grant " + targetPkg + " permission to " + uri); - final IPackageManager pm = ActivityThread.getPackageManager(); + final IPackageManager pm = AppGlobals.getPackageManager(); // If this is not a content: uri, we can't do anything with it. if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { @@ -6453,8 +4090,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String name = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr - = (ContentProviderRecord)mProvidersByName.get(name); + ContentProviderRecord cpr = mProvidersByName.get(name); if (cpr != null) { pi = cpr.info; } else { @@ -6561,8 +4197,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void grantUriPermissionFromIntentLocked(int callingUid, - String targetPkg, Intent intent, HistoryRecord activity) { + void grantUriPermissionFromIntentLocked(int callingUid, + String targetPkg, Intent intent, ActivityRecord activity) { if (intent == null) { return; } @@ -6597,7 +4233,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void removeUriPermissionIfNeededLocked(UriPermission perm) { + void removeUriPermissionIfNeededLocked(UriPermission perm) { if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) { HashMap<Uri, UriPermission> perms @@ -6613,29 +4249,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void removeActivityUriPermissionsLocked(HistoryRecord activity) { - if (activity.readUriPermissions != null) { - for (UriPermission perm : activity.readUriPermissions) { - perm.readActivities.remove(activity); - if (perm.readActivities.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - removeUriPermissionIfNeededLocked(perm); - } - } - } - if (activity.writeUriPermissions != null) { - for (UriPermission perm : activity.writeUriPermissions) { - perm.writeActivities.remove(activity); - if (perm.writeActivities.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - removeUriPermissionIfNeededLocked(perm); - } - } - } - } - private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -6647,12 +4260,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri); - final IPackageManager pm = ActivityThread.getPackageManager(); + final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr - = (ContentProviderRecord)mProvidersByName.get(authority); + ContentProviderRecord cpr = mProvidersByName.get(authority); if (cpr != null) { pi = cpr.info; } else { @@ -6742,12 +4354,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } - final IPackageManager pm = ActivityThread.getPackageManager(); + final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr - = (ContentProviderRecord)mProvidersByName.get(authority); + ContentProviderRecord cpr = mProvidersByName.get(authority); if (cpr != null) { pi = cpr.info; } else { @@ -6797,7 +4408,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen PendingThumbnailsRecord pending = null; IApplicationThread topThumbnail = null; - HistoryRecord topRecord = null; + ActivityRecord topRecord = null; synchronized(this) { if (localLOGV) Slog.v( @@ -6822,18 +4433,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen throw new SecurityException(msg); } - int pos = mHistory.size()-1; - HistoryRecord next = - pos >= 0 ? (HistoryRecord)mHistory.get(pos) : null; - HistoryRecord top = null; + int pos = mMainStack.mHistory.size()-1; + ActivityRecord next = + pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; + ActivityRecord top = null; CharSequence topDescription = null; TaskRecord curTask = null; int numActivities = 0; int numRunning = 0; while (pos >= 0 && maxNum > 0) { - final HistoryRecord r = next; + final ActivityRecord r = next; pos--; - next = pos >= 0 ? (HistoryRecord)mHistory.get(pos) : null; + next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; // Initialize state for next task if needed. if (top == null || @@ -6935,7 +4546,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res @@ -6982,12 +4593,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final int findAffinityTaskTopLocked(int startIndex, String affinity) { int j; - TaskRecord startTask = ((HistoryRecord)mHistory.get(startIndex)).task; + TaskRecord startTask = ((ActivityRecord)mMainStack.mHistory.get(startIndex)).task; TaskRecord jt = startTask; // First look backwards for (j=startIndex-1; j>=0; j--) { - HistoryRecord r = (HistoryRecord)mHistory.get(j); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); if (r.task != jt) { jt = r.task; if (affinity.equals(jt.affinity)) { @@ -6997,10 +4608,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Now look forwards - final int N = mHistory.size(); + final int N = mMainStack.mHistory.size(); jt = startTask; for (j=startIndex+1; j<N; j++) { - HistoryRecord r = (HistoryRecord)mHistory.get(j); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); if (r.task != jt) { if (affinity.equals(jt.affinity)) { return j; @@ -7010,7 +4621,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // Might it be at the top? - if (affinity.equals(((HistoryRecord)mHistory.get(N-1)).task.affinity)) { + if (affinity.equals(((ActivityRecord)mMainStack.mHistory.get(N-1)).task.affinity)) { return N-1; } @@ -7018,293 +4629,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } /** - * Perform a reset of the given task, if needed as part of launching it. - * Returns the new HistoryRecord at the top of the task. - */ - private final HistoryRecord resetTaskIfNeededLocked(HistoryRecord taskTop, - HistoryRecord newActivity) { - boolean forceReset = (newActivity.info.flags - &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { - if ((newActivity.info.flags - &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { - forceReset = true; - } - } - - final TaskRecord task = taskTop.task; - - // We are going to move through the history list so that we can look - // at each activity 'target' with 'below' either the interesting - // activity immediately below it in the stack or null. - HistoryRecord target = null; - int targetI = 0; - int taskTopI = -1; - int replyChainEnd = -1; - int lastReparentPos = -1; - for (int i=mHistory.size()-1; i>=-1; i--) { - HistoryRecord below = i >= 0 ? (HistoryRecord)mHistory.get(i) : null; - - if (below != null && below.finishing) { - continue; - } - if (target == null) { - target = below; - targetI = i; - // If we were in the middle of a reply chain before this - // task, it doesn't appear like the root of the chain wants - // anything interesting, so drop it. - replyChainEnd = -1; - continue; - } - - final int flags = target.info.flags; - - final boolean finishOnTaskLaunch = - (flags&ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; - final boolean allowTaskReparenting = - (flags&ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; - - if (target.task == task) { - // We are inside of the task being reset... we'll either - // finish this activity, push it out for another task, - // or leave it as-is. We only do this - // for activities that are not the root of the task (since - // if we finish the root, we may no longer have the task!). - if (taskTopI < 0) { - taskTopI = targetI; - } - if (below != null && below.task == task) { - final boolean clearWhenTaskReset = - (target.intent.getFlags() - &Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0; - if (!finishOnTaskLaunch && !clearWhenTaskReset && target.resultTo != null) { - // If this activity is sending a reply to a previous - // activity, we can't do anything with it now until - // we reach the start of the reply chain. - // XXX note that we are assuming the result is always - // to the previous activity, which is almost always - // the case but we really shouldn't count on. - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - } else if (!finishOnTaskLaunch && !clearWhenTaskReset && allowTaskReparenting - && target.taskAffinity != null - && !target.taskAffinity.equals(task.affinity)) { - // If this activity has an affinity for another - // task, then we need to move it out of here. We will - // move it as far out of the way as possible, to the - // bottom of the activity stack. This also keeps it - // correctly ordered with any activities we previously - // moved. - HistoryRecord p = (HistoryRecord)mHistory.get(0); - if (target.taskAffinity != null - && target.taskAffinity.equals(p.task.affinity)) { - // If the activity currently at the bottom has the - // same task affinity as the one we are moving, - // then merge it into the same task. - target.task = p.task; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target - + " out to bottom task " + p.task); - } else { - mCurTask++; - if (mCurTask <= 0) { - mCurTask = 1; - } - target.task = new TaskRecord(mCurTask, target.info, null, - (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - target.task.affinityIntent = target.intent; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target - + " out to new task " + target.task); - } - mWindowManager.setAppGroupId(target, task.taskId); - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - int dstPos = 0; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p - + " out to target's task " + target.task); - task.numActivities--; - p.task = target.task; - target.task.numActivities++; - mHistory.remove(srcPos); - mHistory.add(dstPos, p); - mWindowManager.moveAppToken(dstPos, p); - mWindowManager.setAppGroupId(p, p.task.taskId); - dstPos++; - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - i++; - } - if (taskTop == p) { - taskTop = below; - } - if (taskTopI == replyChainEnd) { - taskTopI = -1; - } - replyChainEnd = -1; - addRecentTaskLocked(target.task); - } else if (forceReset || finishOnTaskLaunch - || clearWhenTaskReset) { - // If the activity should just be removed -- either - // because it asks for it, or the task should be - // cleared -- then finish it and anything that is - // part of its reply chain. - if (clearWhenTaskReset) { - // In this case, we want to finish this activity - // and everything above it, so be sneaky and pretend - // like these are all in the reply chain. - replyChainEnd = targetI+1; - while (replyChainEnd < mHistory.size() && - ((HistoryRecord)mHistory.get( - replyChainEnd)).task == task) { - replyChainEnd++; - } - replyChainEnd--; - } else if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - HistoryRecord p = null; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset")) { - replyChainEnd--; - srcPos--; - } - } - if (taskTop == p) { - taskTop = below; - } - if (taskTopI == replyChainEnd) { - taskTopI = -1; - } - replyChainEnd = -1; - } else { - // If we were in the middle of a chain, well the - // activity that started it all doesn't want anything - // special, so leave it all as-is. - replyChainEnd = -1; - } - } else { - // Reached the bottom of the task -- any reply chain - // should be left as-is. - replyChainEnd = -1; - } - - } else if (target.resultTo != null) { - // If this activity is sending a reply to a previous - // activity, we can't do anything with it now until - // we reach the start of the reply chain. - // XXX note that we are assuming the result is always - // to the previous activity, which is almost always - // the case but we really shouldn't count on. - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - - } else if (taskTopI >= 0 && allowTaskReparenting - && task.affinity != null - && task.affinity.equals(target.taskAffinity)) { - // We are inside of another task... if this activity has - // an affinity for our task, then either remove it if we are - // clearing or move it over to our task. Note that - // we currently punt on the case where we are resetting a - // task that is not at the top but who has activities above - // with an affinity to it... this is really not a normal - // case, and we will need to later pull that task to the front - // and usually at that point we will do the reset and pick - // up those remaining activities. (This only happens if - // someone starts an activity in a new task from an activity - // in a task that is not currently on top.) - if (forceReset || finishOnTaskLaunch) { - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - HistoryRecord p = null; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset")) { - taskTopI--; - lastReparentPos--; - replyChainEnd--; - srcPos--; - } - } - replyChainEnd = -1; - } else { - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { - HistoryRecord p = (HistoryRecord)mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (lastReparentPos < 0) { - lastReparentPos = taskTopI; - taskTop = p; - } else { - lastReparentPos--; - } - mHistory.remove(srcPos); - p.task.numActivities--; - p.task = task; - mHistory.add(lastReparentPos, p); - if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p - + " in to resetting task " + task); - task.numActivities++; - mWindowManager.moveAppToken(lastReparentPos, p); - mWindowManager.setAppGroupId(p, p.task.taskId); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - } - replyChainEnd = -1; - - // Now we've moved it in to place... but what if this is - // a singleTop activity and we have put it on top of another - // instance of the same activity? Then we drop the instance - // below so it remains singleTop. - if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { - for (int j=lastReparentPos-1; j>=0; j--) { - HistoryRecord p = (HistoryRecord)mHistory.get(j); - if (p.finishing) { - continue; - } - if (p.intent.getComponent().equals(target.intent.getComponent())) { - if (finishActivityLocked(p, j, - Activity.RESULT_CANCELED, null, "replace")) { - taskTopI--; - lastReparentPos--; - } - } - } - } - } - } - - target = below; - targetI = i; - } - - return taskTop; - } - - /** * TODO: Add mController hook */ public void moveTaskToFront(int task) { @@ -7322,14 +4646,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { - moveTaskToFrontLocked(tr, null); + mMainStack.moveTaskToFrontLocked(tr, null); return; } } - for (int i=mHistory.size()-1; i>=0; i--) { - HistoryRecord hr = (HistoryRecord)mHistory.get(i); + for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); if (hr.task.taskId == task) { - moveTaskToFrontLocked(hr.task, null); + mMainStack.moveTaskToFrontLocked(hr.task, null); return; } } @@ -7339,84 +4663,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void moveTaskToFrontLocked(TaskRecord tr, HistoryRecord reason) { - if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); - - final int task = tr.taskId; - int top = mHistory.size()-1; - - if (top < 0 || ((HistoryRecord)mHistory.get(top)).task.taskId == task) { - // nothing to do! - return; - } - - ArrayList moved = new ArrayList(); - - // Applying the affinities may have removed entries from the history, - // so get the size again. - top = mHistory.size()-1; - int pos = top; - - // Shift all activities with this task up to the top - // of the stack, keeping them in the same internal order. - while (pos >= 0) { - HistoryRecord r = (HistoryRecord)mHistory.get(pos); - if (localLOGV) Slog.v( - TAG, "At " + pos + " ckp " + r.task + ": " + r); - boolean first = true; - if (r.task.taskId == task) { - if (localLOGV) Slog.v(TAG, "Removing and adding at " + top); - mHistory.remove(pos); - mHistory.add(top, r); - moved.add(0, r); - top--; - if (first) { - addRecentTaskLocked(r.task); - first = false; - } - } - pos--; - } - - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to front transition: task=" + tr); - if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - HistoryRecord r = topRunningActivityLocked(null); - if (r != null) { - mNoAnimActivities.add(r); - } - } else { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); - } - - mWindowManager.moveAppTokensToTop(moved); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - - finishTaskMoveLocked(task); - EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, task); - } - - private final void finishTaskMoveLocked(int task) { - resumeTopActivityLocked(null); - } - public void moveTaskToBack(int task) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToBack()"); synchronized(this) { - if (mResumedActivity != null && mResumedActivity.task.taskId == task) { + if (mMainStack.mResumedActivity != null + && mMainStack.mResumedActivity.task.taskId == task) { if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), Binder.getCallingUid(), "Task to back")) { return; } } final long origId = Binder.clearCallingIdentity(); - moveTaskToBackLocked(task, null); + mMainStack.moveTaskToBackLocked(task, null); Binder.restoreCallingIdentity(origId); } } @@ -7435,93 +4695,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); int taskId = getTaskForActivityLocked(token, !nonRoot); if (taskId >= 0) { - return moveTaskToBackLocked(taskId, null); + return mMainStack.moveTaskToBackLocked(taskId, null); } Binder.restoreCallingIdentity(origId); } return false; } - /** - * Worker method for rearranging history stack. Implements the function of moving all - * activities for a specific task (gathering them if disjoint) into a single group at the - * bottom of the stack. - * - * If a watcher is installed, the action is preflighted and the watcher has an opportunity - * to premeptively cancel the move. - * - * @param task The taskId to collect and move to the bottom. - * @return Returns true if the move completed, false if not. - */ - private final boolean moveTaskToBackLocked(int task, HistoryRecord reason) { - Slog.i(TAG, "moveTaskToBack: " + task); - - // If we have a watcher, preflight the move before committing to it. First check - // for *other* available tasks, but if none are available, then try again allowing the - // current task to be selected. - if (mController != null) { - HistoryRecord next = topRunningActivityLocked(null, task); - if (next == null) { - next = topRunningActivityLocked(null, 0); - } - if (next != null) { - // ask watcher if this is allowed - boolean moveOK = true; - try { - moveOK = mController.activityResuming(next.packageName); - } catch (RemoteException e) { - mController = null; - } - if (!moveOK) { - return false; - } - } - } - - ArrayList moved = new ArrayList(); - - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to back transition: task=" + task); - - final int N = mHistory.size(); - int bottom = 0; - int pos = 0; - - // Shift all activities with this task down to the bottom - // of the stack, keeping them in the same internal order. - while (pos < N) { - HistoryRecord r = (HistoryRecord)mHistory.get(pos); - if (localLOGV) Slog.v( - TAG, "At " + pos + " ckp " + r.task + ": " + r); - if (r.task.taskId == task) { - if (localLOGV) Slog.v(TAG, "Removing and adding at " + (N-1)); - mHistory.remove(pos); - mHistory.add(bottom, r); - moved.add(r); - bottom++; - } - pos++; - } - - if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); - HistoryRecord r = topRunningActivityLocked(null); - if (r != null) { - mNoAnimActivities.add(r); - } - } else { - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); - } - mWindowManager.moveAppTokensToBottom(moved); - if (VALIDATE_TOKENS) { - mWindowManager.validateAppTokens(mHistory); - } - - finishTaskMoveLocked(task); - return true; - } - public void moveTaskBackwards(int task) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskBackwards()"); @@ -7548,10 +4728,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { - final int N = mHistory.size(); + final int N = mMainStack.mHistory.size(); TaskRecord lastTask = null; for (int i=0; i<N; i++) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r == token) { if (!onlyRoot || lastTask != r.task) { return r.task.taskId; @@ -7564,89 +4744,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return -1; } - /** - * Returns the top activity in any existing task matching the given - * Intent. Returns null if no such task is found. - */ - private HistoryRecord findTaskLocked(Intent intent, ActivityInfo info) { - ComponentName cls = intent.getComponent(); - if (info.targetActivity != null) { - cls = new ComponentName(info.packageName, info.targetActivity); - } - - TaskRecord cp = null; - - final int N = mHistory.size(); - for (int i=(N-1); i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing && r.task != cp - && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - cp = r.task; - //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() - // + "/aff=" + r.task.affinity + " to new cls=" - // + intent.getComponent().flattenToShortString() + "/aff=" + taskAffinity); - if (r.task.affinity != null) { - if (r.task.affinity.equals(info.taskAffinity)) { - //Slog.i(TAG, "Found matching affinity!"); - return r; - } - } else if (r.task.intent != null - && r.task.intent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } else if (r.task.affinityIntent != null - && r.task.affinityIntent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } - } - } - - return null; - } - - /** - * Returns the first activity (starting from the top of the stack) that - * is the same as the given activity. Returns null if no such activity - * is found. - */ - private HistoryRecord findActivityLocked(Intent intent, ActivityInfo info) { - ComponentName cls = intent.getComponent(); - if (info.targetActivity != null) { - cls = new ComponentName(info.packageName, info.targetActivity); - } - - final int N = mHistory.size(); - for (int i=(N-1); i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); - if (!r.finishing) { - if (r.intent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } - } - } - - return null; - } - public void finishOtherInstances(IBinder token, ComponentName className) { synchronized(this) { final long origId = Binder.clearCallingIdentity(); - int N = mHistory.size(); + int N = mMainStack.mHistory.size(); TaskRecord lastTask = null; for (int i=0; i<N; i++) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.realActivity.equals(className) && r != token && lastTask != r.task) { - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "others")) { i--; N--; @@ -7671,7 +4779,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Binder.restoreCallingIdentity(origId); } - final void sendPendingThumbnail(HistoryRecord r, IBinder token, + final void sendPendingThumbnail(ActivityRecord r, IBinder token, Bitmap thumbnail, CharSequence description, boolean always) { TaskRecord task = null; ArrayList receivers = null; @@ -7680,11 +4788,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (r == null) { - int index = indexOfTokenLocked(token); + int index = mMainStack.indexOfTokenLocked(token); if (index < 0) { return; } - r = (HistoryRecord)mHistory.get(index); + r = (ActivityRecord)mMainStack.mHistory.get(index); } if (thumbnail == null) { thumbnail = r.thumbnail; @@ -7745,7 +4853,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final List generateApplicationProvidersLocked(ProcessRecord app) { List providers = null; try { - providers = ActivityThread.getPackageManager(). + providers = AppGlobals.getPackageManager(). queryContentProviders(app.processName, app.info.uid, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { @@ -7755,8 +4863,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); - ContentProviderRecord cpr = - (ContentProviderRecord)mProvidersByClass.get(cpi.name); + ContentProviderRecord cpr = mProvidersByClass.get(cpi.name); if (cpr == null) { cpr = new ContentProviderRecord(cpi, app.info); mProvidersByClass.put(cpi.name, cpr); @@ -7831,7 +4938,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } // First check if this content provider has been published... - cpr = (ContentProviderRecord)mProvidersByName.get(name); + cpr = mProvidersByName.get(name); if (cpr != null) { cpi = cpr.info; if (checkContentProviderPermissionLocked(cpi, r, -1) != null) { @@ -7869,8 +4976,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); } cpr.clients.add(r); - if (cpr.app != null && r.setAdj >= VISIBLE_APP_ADJ) { - // If this is a visible app accessing the provider, + if (cpr.app != null && r.setAdj <= PERCEPTIBLE_APP_ADJ) { + // If this is a perceptible app accessing the provider, // make sure to count it as being accessed and thus // back up on the LRU list. This is good because // content providers are often expensive to start. @@ -7888,7 +4995,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else { try { - cpi = ActivityThread.getPackageManager(). + cpi = AppGlobals.getPackageManager(). resolveContentProvider(name, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { @@ -7912,12 +5019,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen "Attempt to launch content provider before system ready"); } - cpr = (ContentProviderRecord)mProvidersByClass.get(cpi.name); + cpr = mProvidersByClass.get(cpi.name); final boolean firstClass = cpr == null; if (firstClass) { try { ApplicationInfo ai = - ActivityThread.getPackageManager(). + AppGlobals.getPackageManager(). getApplicationInfo( cpi.applicationInfo.packageName, STOCK_PM_FLAGS); @@ -8046,7 +5153,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen */ public void removeContentProvider(IApplicationThread caller, String name) { synchronized (this) { - ContentProviderRecord cpr = (ContentProviderRecord)mProvidersByName.get(name); + ContentProviderRecord cpr = mProvidersByName.get(name); if(cpr == null) { // remove from mProvidersByClass if (DEBUG_PROVIDER) Slog.v(TAG, name + @@ -8060,8 +5167,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen " when removing content provider " + name); } //update content provider record entry info - ContentProviderRecord localCpr = (ContentProviderRecord) - mProvidersByClass.get(cpr.info.name); + ContentProviderRecord localCpr = mProvidersByClass.get(cpr.info.name); if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by " + r.info.processName + " from process " + localCpr.appInfo.processName); @@ -8085,7 +5191,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private void removeContentProviderExternal(String name) { synchronized (this) { - ContentProviderRecord cpr = (ContentProviderRecord)mProvidersByName.get(name); + ContentProviderRecord cpr = mProvidersByName.get(name); if(cpr == null) { //remove from mProvidersByClass if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); @@ -8093,7 +5199,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } //update content provider record entry info - ContentProviderRecord localCpr = (ContentProviderRecord) mProvidersByClass.get(cpr.info.name); + ContentProviderRecord localCpr = mProvidersByClass.get(cpr.info.name); localCpr.externals--; if (localCpr.externals < 0) { Slog.e(TAG, "Externals < 0 for content provider " + localCpr); @@ -8125,8 +5231,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (src == null || src.info == null || src.provider == null) { continue; } - ContentProviderRecord dst = - (ContentProviderRecord)r.pubProviders.get(src.info.name); + ContentProviderRecord dst = r.pubProviders.get(src.info.name); if (dst != null) { mProvidersByClass.put(dst.info.name, dst); String names[] = dst.info.authority.split(";"); @@ -8219,12 +5324,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen "unhandledBack()"); synchronized(this) { - int count = mHistory.size(); + int count = mMainStack.mHistory.size(); if (DEBUG_SWITCH) Slog.d( TAG, "Performing unhandledBack(): stack size = " + count); if (count > 1) { final long origId = Binder.clearCallingIdentity(); - finishActivityLocked((HistoryRecord)mHistory.get(count-1), + mMainStack.finishActivityLocked((ActivityRecord)mMainStack.mHistory.get(count-1), count-1, Activity.RESULT_CANCELED, null, "unhandled-back"); Binder.restoreCallingIdentity(origId); } @@ -8267,8 +5372,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mSleeping = true; mWindowManager.setEventDispatching(false); - if (mResumedActivity != null) { - pauseIfSleepingLocked(); + if (mMainStack.mResumedActivity != null) { + mMainStack.pauseIfSleepingLocked(); } else { Slog.w(TAG, "goingToSleep with no resumed activity!"); } @@ -8288,10 +5393,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mShuttingDown = true; mWindowManager.setEventDispatching(false); - if (mResumedActivity != null) { - pauseIfSleepingLocked(); + if (mMainStack.mResumedActivity != null) { + mMainStack.pauseIfSleepingLocked(); final long endTime = System.currentTimeMillis() + timeout; - while (mResumedActivity != null || mPausingActivity != null) { + while (mMainStack.mResumedActivity != null + || mMainStack.mPausingActivity != null) { long delay = endTime - System.currentTimeMillis(); if (delay <= 0) { Slog.w(TAG, "Activity manager shutdown timed out"); @@ -8312,35 +5418,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return timedout; } - void pauseIfSleepingLocked() { - if (mSleeping || mShuttingDown) { - if (!mGoingToSleep.isHeld()) { - mGoingToSleep.acquire(); - if (mLaunchingActivity.isHeld()) { - mLaunchingActivity.release(); - mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); - } - } - - // If we are not currently pausing an activity, get the current - // one to pause. If we are pausing one, we will just let that stuff - // run and release the wake lock when all done. - if (mPausingActivity == null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause..."); - if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); - startPausingLocked(false, true); - } - } - } - public void wakingUp() { synchronized(this) { - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); + if (mMainStack.mGoingToSleep.isHeld()) { + mMainStack.mGoingToSleep.release(); } mWindowManager.setEventDispatching(true); mSleeping = false; - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } } @@ -8468,13 +5553,42 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public void setImmersive(IBinder token, boolean immersive) { + synchronized(this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + r.immersive = immersive; + } + } + + public boolean isImmersive(IBinder token) { + synchronized (this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + return r.immersive; + } + } + + public boolean isTopActivityImmersive() { + synchronized (this) { + ActivityRecord r = mMainStack.topRunningActivityLocked(null); + return (r != null) ? r.immersive : false; + } + } + public final void enterSafeMode() { synchronized(this) { // It only makes sense to do this before the system is ready // and started launching other packages. if (!mSystemReady) { try { - ActivityThread.getPackageManager().enterSafeMode(); + AppGlobals.getPackageManager().enterSafeMode(); } catch (RemoteException e) { } @@ -8560,123 +5674,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return killed; } - public void reportPss(IApplicationThread caller, int pss) { - Watchdog.PssRequestor req; - String name; - ProcessRecord callerApp; - synchronized (this) { - if (caller == null) { - return; - } - callerApp = getRecordForAppLocked(caller); - if (callerApp == null) { - return; - } - callerApp.lastPss = pss; - req = callerApp; - name = callerApp.processName; - } - Watchdog.getInstance().reportPss(req, name, pss); - if (!callerApp.persistent) { - removeRequestedPss(callerApp); - } - } - - public void requestPss(Runnable completeCallback) { - ArrayList<ProcessRecord> procs; - synchronized (this) { - mRequestPssCallback = completeCallback; - mRequestPssList.clear(); - for (int i=mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord proc = mLruProcesses.get(i); - if (!proc.persistent) { - mRequestPssList.add(proc); - } - } - procs = new ArrayList<ProcessRecord>(mRequestPssList); - } - - int oldPri = Process.getThreadPriority(Process.myTid()); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - for (int i=procs.size()-1; i>=0; i--) { - ProcessRecord proc = procs.get(i); - proc.lastPss = 0; - proc.requestPss(); - } - Process.setThreadPriority(oldPri); - } - - void removeRequestedPss(ProcessRecord proc) { - Runnable callback = null; - synchronized (this) { - if (mRequestPssList.remove(proc)) { - if (mRequestPssList.size() == 0) { - callback = mRequestPssCallback; - mRequestPssCallback = null; - } - } - } - - if (callback != null) { - callback.run(); - } - } - - public void collectPss(Watchdog.PssStats stats) { - stats.mEmptyPss = 0; - stats.mEmptyCount = 0; - stats.mBackgroundPss = 0; - stats.mBackgroundCount = 0; - stats.mServicePss = 0; - stats.mServiceCount = 0; - stats.mVisiblePss = 0; - stats.mVisibleCount = 0; - stats.mForegroundPss = 0; - stats.mForegroundCount = 0; - stats.mNoPssCount = 0; - synchronized (this) { - int i; - int NPD = mProcDeaths.length < stats.mProcDeaths.length - ? mProcDeaths.length : stats.mProcDeaths.length; - int aggr = 0; - for (i=0; i<NPD; i++) { - aggr += mProcDeaths[i]; - stats.mProcDeaths[i] = aggr; - } - while (i<stats.mProcDeaths.length) { - stats.mProcDeaths[i] = 0; - i++; - } - - for (i=mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord proc = mLruProcesses.get(i); - if (proc.persistent) { - continue; - } - //Slog.i(TAG, "Proc " + proc + ": pss=" + proc.lastPss); - if (proc.lastPss == 0) { - stats.mNoPssCount++; - continue; - } - if (proc.setAdj >= HIDDEN_APP_MIN_ADJ) { - if (proc.empty) { - stats.mEmptyPss += proc.lastPss; - stats.mEmptyCount++; - } else { - stats.mBackgroundPss += proc.lastPss; - stats.mBackgroundCount++; - } - } else if (proc.setAdj >= VISIBLE_APP_ADJ) { - stats.mVisiblePss += proc.lastPss; - stats.mVisibleCount++; - } else { - stats.mForegroundPss += proc.lastPss; - stats.mForegroundCount++; - } - } - } - } - public final void startRunning(String pkg, String cls, String action, String data) { synchronized(this) { @@ -8747,7 +5744,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); List<ResolveInfo> ris = null; try { - ris = ActivityThread.getPackageManager().queryIntentReceivers( + ris = AppGlobals.getPackageManager().queryIntentReceivers( intent, null, 0); } catch (RemoteException e) { } @@ -8866,7 +5863,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { - List apps = ActivityThread.getPackageManager(). + List apps = AppGlobals.getPackageManager(). getPersistentApplications(STOCK_PM_FLAGS); if (apps != null) { int N = apps.size(); @@ -8889,7 +5886,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mBooting = true; try { - if (ActivityThread.getPackageManager().hasSystemUidErrors()) { + if (AppGlobals.getPackageManager().hasSystemUidErrors()) { Message msg = Message.obtain(); msg.what = SHOW_UID_ERROR_MSG; mHandler.sendMessage(msg); @@ -8897,7 +5894,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } - resumeTopActivityLocked(null); + mMainStack.resumeTopActivityLocked(null); } } @@ -8985,12 +5982,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, app.info.processName, app.info.uid); killServicesLocked(app, false); - for (int i=mHistory.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)mHistory.get(i); + for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.app == app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "crashed"); + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "crashed"); } } if (!app.persistent) { @@ -9008,28 +6005,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return false; } } else { - HistoryRecord r = topRunningActivityLocked(null); + ActivityRecord r = mMainStack.topRunningActivityLocked(null); if (r.app == app) { // If the top running activity is from this crashing // process, then terminate it to avoid getting in a loop. Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - int index = indexOfTokenLocked(r); - finishActivityLocked(r, index, + int index = mMainStack.indexOfTokenLocked(r); + r.stack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, null, "crashed"); // Also terminate an activities below it that aren't yet // stopped, to avoid a situation where one will get // re-start our crashing activity once it gets resumed again. index--; if (index >= 0) { - r = (HistoryRecord)mHistory.get(index); + r = (ActivityRecord)mMainStack.mHistory.get(index); if (r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (!r.isHomeActivity) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - finishActivityLocked(r, index, + r.stack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, null, "crashed"); } } @@ -9041,9 +6038,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app.services.size() != 0) { // Any services running in the application need to be placed // back in the pending list. - Iterator it = app.services.iterator(); + Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { - ServiceRecord sr = (ServiceRecord)it.next(); + ServiceRecord sr = it.next(); sr.crashCount++; } } @@ -9127,6 +6124,161 @@ public final class ActivityManagerService extends ActivityManagerNative implemen crashApplication(r, crashInfo); } + public void handleApplicationStrictModeViolation( + IBinder app, + int violationMask, + StrictMode.ViolationInfo info) { + ProcessRecord r = findAppProcess(app); + + if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) { + Integer stackFingerprint = info.crashInfo.stackTrace.hashCode(); + boolean logIt = true; + synchronized (mAlreadyLoggedViolatedStacks) { + if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) { + logIt = false; + // TODO: sub-sample into EventLog for these, with + // the info.durationMillis? Then we'd get + // the relative pain numbers, without logging all + // the stack traces repeatedly. We'd want to do + // likewise in the client code, which also does + // dup suppression, before the Binder call. + } else { + if (mAlreadyLoggedViolatedStacks.size() >= MAX_DUP_SUPPRESSED_STACKS) { + mAlreadyLoggedViolatedStacks.clear(); + } + mAlreadyLoggedViolatedStacks.add(stackFingerprint); + } + } + if (logIt) { + logStrictModeViolationToDropBox(r, info); + } + } + + if ((violationMask & StrictMode.PENALTY_DIALOG) != 0) { + AppErrorResult result = new AppErrorResult(); + synchronized (this) { + final long origId = Binder.clearCallingIdentity(); + + Message msg = Message.obtain(); + msg.what = SHOW_STRICT_MODE_VIOLATION_MSG; + HashMap<String, Object> data = new HashMap<String, Object>(); + data.put("result", result); + data.put("app", r); + data.put("violationMask", violationMask); + data.put("info", info); + msg.obj = data; + mHandler.sendMessage(msg); + + Binder.restoreCallingIdentity(origId); + } + int res = result.get(); + Log.w(TAG, "handleApplicationStrictModeViolation; res=" + res); + } + } + + // Depending on the policy in effect, there could be a bunch of + // these in quick succession so we try to batch these together to + // minimize disk writes, number of dropbox entries, and maximize + // compression, by having more fewer, larger records. + private void logStrictModeViolationToDropBox( + ProcessRecord process, + StrictMode.ViolationInfo info) { + if (info == null) { + return; + } + final boolean isSystemApp = process == null || + (process.info.flags & (ApplicationInfo.FLAG_SYSTEM | + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; + final String dropboxTag = isSystemApp ? "system_app_strictmode" : "data_app_strictmode"; + final DropBoxManager dbox = (DropBoxManager) + mContext.getSystemService(Context.DROPBOX_SERVICE); + + // Exit early if the dropbox isn't configured to accept this report type. + if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; + + boolean bufferWasEmpty; + boolean needsFlush; + final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024); + synchronized (sb) { + bufferWasEmpty = sb.length() == 0; + appendDropBoxProcessHeaders(process, sb); + sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); + sb.append("System-App: ").append(isSystemApp).append("\n"); + sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n"); + if (info.violationNumThisLoop != 0) { + sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n"); + } + if (info != null && info.durationMillis != -1) { + sb.append("Duration-Millis: ").append(info.durationMillis).append("\n"); + } + sb.append("\n"); + if (info.crashInfo != null && info.crashInfo.stackTrace != null) { + sb.append(info.crashInfo.stackTrace); + } + sb.append("\n"); + + // Only buffer up to ~64k. Various logging bits truncate + // things at 128k. + needsFlush = (sb.length() > 64 * 1024); + } + + // Flush immediately if the buffer's grown too large, or this + // is a non-system app. Non-system apps are isolated with a + // different tag & policy and not batched. + // + // Batching is useful during internal testing with + // StrictMode settings turned up high. Without batching, + // thousands of separate files could be created on boot. + if (!isSystemApp || needsFlush) { + new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + String report; + synchronized (sb) { + report = sb.toString(); + sb.delete(0, sb.length()); + sb.trimToSize(); + } + if (report.length() != 0) { + dbox.addText(dropboxTag, report); + } + } + }.start(); + return; + } + + // System app batching: + if (!bufferWasEmpty) { + // An existing dropbox-writing thread is outstanding, so + // we don't need to start it up. The existing thread will + // catch the buffer appends we just did. + return; + } + + // Worker thread to both batch writes and to avoid blocking the caller on I/O. + // (After this point, we shouldn't access AMS internal data structures.) + new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + // 5 second sleep to let stacks arrive and be batched together + try { + Thread.sleep(5000); // 5 seconds + } catch (InterruptedException e) {} + + String errorReport; + synchronized (mStrictModeBuffer) { + errorReport = mStrictModeBuffer.toString(); + if (errorReport.length() == 0) { + return; + } + mStrictModeBuffer.delete(0, mStrictModeBuffer.length()); + mStrictModeBuffer.trimToSize(); + } + dbox.addText(dropboxTag, errorReport); + } + }.start(); + } + /** * Used by {@link Log} via {@link com.android.internal.os.RuntimeInit} to report serious errors. * @param app object of the crashing app, null for the system server @@ -9180,40 +6332,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } /** - * Write a description of an error (crash, WTF, ANR) to the drop box. - * @param eventType to include in the drop box tag ("crash", "wtf", etc.) - * @param process which caused the error, null means the system server - * @param activity which triggered the error, null if unknown - * @param parent activity related to the error, null if unknown - * @param subject line related to the error, null if absent - * @param report in long form describing the error, null if absent - * @param logFile to include in the report, null if none - * @param crashInfo giving an application stack trace, null if absent + * Utility function for addErrorToDropBox and handleStrictModeViolation's logging + * to append various headers to the dropbox log text. */ - public void addErrorToDropBox(String eventType, - ProcessRecord process, HistoryRecord activity, HistoryRecord parent, String subject, - final String report, final File logFile, - final ApplicationErrorReport.CrashInfo crashInfo) { - // NOTE -- this must never acquire the ActivityManagerService lock, - // otherwise the watchdog may be prevented from resetting the system. - - String prefix; - if (process == null || process.pid == MY_PID) { - prefix = "system_server_"; - } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - prefix = "system_app_"; - } else { - prefix = "data_app_"; - } - - final String dropboxTag = prefix + eventType; - final DropBoxManager dbox = (DropBoxManager) - mContext.getSystemService(Context.DROPBOX_SERVICE); - - // Exit early if the dropbox isn't configured to accept this report type. - if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; - - final StringBuilder sb = new StringBuilder(1024); + private static void appendDropBoxProcessHeaders(ProcessRecord process, StringBuilder sb) { if (process == null || process.pid == MY_PID) { sb.append("Process: system_server\n"); } else { @@ -9221,7 +6343,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (process != null) { int flags = process.info.flags; - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n"); for (String pkg : process.pkgList) { sb.append("Package: ").append(pkg); @@ -9239,6 +6361,45 @@ public final class ActivityManagerService extends ActivityManagerNative implemen sb.append("\n"); } } + } + + private static String processClass(ProcessRecord process) { + if (process == null || process.pid == MY_PID) { + return "system_server"; + } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + return "system_app"; + } else { + return "data_app"; + } + } + + /** + * Write a description of an error (crash, WTF, ANR) to the drop box. + * @param eventType to include in the drop box tag ("crash", "wtf", etc.) + * @param process which caused the error, null means the system server + * @param activity which triggered the error, null if unknown + * @param parent activity related to the error, null if unknown + * @param subject line related to the error, null if absent + * @param report in long form describing the error, null if absent + * @param logFile to include in the report, null if none + * @param crashInfo giving an application stack trace, null if absent + */ + public void addErrorToDropBox(String eventType, + ProcessRecord process, ActivityRecord activity, ActivityRecord parent, String subject, + final String report, final File logFile, + final ApplicationErrorReport.CrashInfo crashInfo) { + // NOTE -- this must never acquire the ActivityManagerService lock, + // otherwise the watchdog may be prevented from resetting the system. + + final String dropboxTag = processClass(process) + "_" + eventType; + final DropBoxManager dbox = (DropBoxManager) + mContext.getSystemService(Context.DROPBOX_SERVICE); + + // Exit early if the dropbox isn't configured to accept this report type. + if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return; + + final StringBuilder sb = new StringBuilder(1024); + appendDropBoxProcessHeaders(process, sb); if (activity != null) { sb.append("Activity: ").append(activity.shortComponentName).append("\n"); } @@ -9496,6 +6657,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); currApp.uid = app.info.uid; + if (mHeavyWeightProcess == app) { + currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HEAVY_WEIGHT; + } int adj = app.curAdj; if (adj >= EMPTY_APP_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY; @@ -9507,6 +6671,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen currApp.lru = 0; } else if (adj >= SECONDARY_SERVER_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE; + } else if (adj >= HEAVY_WEIGHT_APP_ADJ) { + currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_HEAVY_WEIGHT; + } else if (adj >= PERCEPTIBLE_APP_ADJ) { + currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE; } else if (adj >= VISIBLE_APP_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; } else { @@ -9515,8 +6683,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen currApp.importanceReasonCode = app.adjTypeCode; if (app.adjSource instanceof ProcessRecord) { currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid; - } else if (app.adjSource instanceof HistoryRecord) { - HistoryRecord r = (HistoryRecord)app.adjSource; + } else if (app.adjSource instanceof ActivityRecord) { + ActivityRecord r = (ActivityRecord)app.adjSource; if (r.app != null) currApp.importanceReasonPid = r.app.pid; } if (app.adjTarget instanceof ComponentName) { @@ -9546,7 +6714,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } } - IPackageManager pm = ActivityThread.getPackageManager(); + IPackageManager pm = AppGlobals.getPackageManager(); for (String pkg : extList) { try { ApplicationInfo info = pm.getApplicationInfo(pkg, 0); @@ -9630,7 +6798,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } return; } else if ("service".equals(cmd)) { - dumpService(fd, pw, args, opti, true); + dumpService(fd, pw, args, opti, dumpAll); return; } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { @@ -9695,31 +6863,31 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (needHeader) { pw.println(" Activity stack:"); } - dumpHistoryList(pw, mHistory, " ", "Hist", true); + dumpHistoryList(pw, mMainStack.mHistory, " ", "Hist", true); pw.println(" "); pw.println(" Running activities (most recent first):"); - dumpHistoryList(pw, mLRUActivities, " ", "Run", false); - if (mWaitingVisibleActivities.size() > 0) { + dumpHistoryList(pw, mMainStack.mLRUActivities, " ", "Run", false); + if (mMainStack.mWaitingVisibleActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting for another to become visible:"); - dumpHistoryList(pw, mWaitingVisibleActivities, " ", "Wait", false); + dumpHistoryList(pw, mMainStack.mWaitingVisibleActivities, " ", "Wait", false); } - if (mStoppingActivities.size() > 0) { + if (mMainStack.mStoppingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to stop:"); - dumpHistoryList(pw, mStoppingActivities, " ", "Stop", false); + dumpHistoryList(pw, mMainStack.mStoppingActivities, " ", "Stop", false); } - if (mFinishingActivities.size() > 0) { + if (mMainStack.mFinishingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to finish:"); - dumpHistoryList(pw, mFinishingActivities, " ", "Fin", false); + dumpHistoryList(pw, mMainStack.mFinishingActivities, " ", "Fin", false); } pw.println(" "); - pw.println(" mPausingActivity: " + mPausingActivity); - pw.println(" mResumedActivity: " + mResumedActivity); + pw.println(" mPausingActivity: " + mMainStack.mPausingActivity); + pw.println(" mResumedActivity: " + mMainStack.mResumedActivity); pw.println(" mFocusedActivity: " + mFocusedActivity); - pw.println(" mLastPausedActivity: " + mLastPausedActivity); + pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); if (dumpAll && mRecentTasks.size() > 0) { pw.println(" "); @@ -9770,7 +6938,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen needSep = true; pw.println(" Running processes (most recent first):"); dumpProcessList(pw, this, mLruProcesses, " ", - "App ", "PERS", true); + "Proc", "PERS", true); needSep = true; } @@ -9884,8 +7052,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.println(" "); pw.println(" mHomeProcess: " + mHomeProcess); + if (mHeavyWeightProcess != null) { + pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); + } pw.println(" mConfiguration: " + mConfiguration); - pw.println(" mConfigWillChange: " + mConfigWillChange); + pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange); pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown); if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { @@ -9904,8 +7075,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + " mBooting=" + mBooting + " mBooted=" + mBooted + " mFactoryTest=" + mFactoryTest); - pw.println(" mGoingToSleep=" + mGoingToSleep); - pw.println(" mLaunchingActivity=" + mLaunchingActivity); + pw.println(" mGoingToSleep=" + mMainStack.mGoingToSleep); + pw.println(" mLaunchingActivity=" + mMainStack.mLaunchingActivity); pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq); } @@ -9933,20 +7104,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen componentNameString = args[opti]; opti++; ComponentName componentName = ComponentName.unflattenFromString(componentNameString); - r = componentName != null ? mServices.get(componentName) : null; + synchronized (this) { + r = componentName != null ? mServices.get(componentName) : null; + } newArgs = new String[args.length - opti]; if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); } if (r != null) { - dumpService(fd, pw, r, newArgs); + dumpService(fd, pw, r, newArgs, dumpAll); } else { - for (ServiceRecord r1 : mServices.values()) { - if (componentNameString == null - || r1.name.flattenToString().contains(componentNameString)) { - dumpService(fd, pw, r1, newArgs); + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + synchronized (this) { + for (ServiceRecord r1 : mServices.values()) { + if (componentNameString == null + || r1.name.flattenToString().contains(componentNameString)) { + services.add(r1); + } } } + for (int i=0; i<services.size(); i++) { + dumpService(fd, pw, services.get(i), newArgs, dumpAll); + } } } @@ -9954,8 +7133,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * Invokes IApplicationThread.dumpService() on the thread of the specified service if * there is a thread associated with the service. */ - private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) { + private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args, + boolean dumpAll) { pw.println(" Service " + r.name.flattenToString()); + if (dumpAll) { + synchronized (this) { + pw.print(" * "); pw.println(r); + r.dump(pw, " "); + } + pw.println(""); + } if (r.app != null && r.app.thread != null) { try { // flush anything that is already in the PrintWriter since the thread is going @@ -9963,6 +7150,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.flush(); r.app.thread.dumpService(fd, r, args); pw.print("\n"); + pw.flush(); } catch (RemoteException e) { pw.println("got a RemoteException while dumping the service"); } @@ -10145,10 +7333,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mProvidersByClass.size() > 0) { if (needSep) pw.println(" "); pw.println(" Published content providers (by class):"); - Iterator it = mProvidersByClass.entrySet().iterator(); + Iterator<Map.Entry<String, ContentProviderRecord>> it + = mProvidersByClass.entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); - ContentProviderRecord r = (ContentProviderRecord)e.getValue(); + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); pw.print(" * "); pw.println(r); r.dump(pw, " "); } @@ -10158,10 +7347,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mProvidersByName.size() > 0) { pw.println(" "); pw.println(" Authority to provider mappings:"); - Iterator it = mProvidersByName.entrySet().iterator(); + Iterator<Map.Entry<String, ContentProviderRecord>> it + = mProvidersByName.entrySet().iterator(); while (it.hasNext()) { - Map.Entry e = (Map.Entry)it.next(); - ContentProviderRecord r = (ContentProviderRecord)e.getValue(); + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); pw.print(" "); pw.print(e.getKey()); pw.print(": "); pw.println(r); } @@ -10228,7 +7418,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String prefix, String label, boolean complete) { TaskRecord lastTask = null; for (int i=list.size()-1; i>=0; i--) { - HistoryRecord r = (HistoryRecord)list.get(i); + ActivityRecord r = (ActivityRecord)list.get(i); final boolean full = complete || !r.inHistory; if (lastTask != r.task) { lastTask = r.task; @@ -10261,7 +7451,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String prefix, String normalLabel, String persistentLabel, boolean inclOomAdj) { int numPers = 0; - for (int i=list.size()-1; i>=0; i--) { + final int N = list.size()-1; + for (int i=N; i>=0; i--) { ProcessRecord r = (ProcessRecord)list.get(i); if (false) { pw.println(prefix + (r.persistent ? persistentLabel : normalLabel) @@ -10279,6 +7470,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen oomAdj = buildOomTag("svc", " ", r.setAdj, SECONDARY_SERVER_ADJ); } else if (r.setAdj >= BACKUP_APP_ADJ) { oomAdj = buildOomTag("bckup", null, r.setAdj, BACKUP_APP_ADJ); + } else if (r.setAdj >= HEAVY_WEIGHT_APP_ADJ) { + oomAdj = buildOomTag("hvy ", null, r.setAdj, HEAVY_WEIGHT_APP_ADJ); + } else if (r.setAdj >= PERCEPTIBLE_APP_ADJ) { + oomAdj = buildOomTag("prcp ", null, r.setAdj, PERCEPTIBLE_APP_ADJ); } else if (r.setAdj >= VISIBLE_APP_ADJ) { oomAdj = buildOomTag("vis ", null, r.setAdj, VISIBLE_APP_ADJ); } else if (r.setAdj >= FOREGROUND_APP_ADJ) { @@ -10304,10 +7499,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)", prefix, (r.persistent ? persistentLabel : normalLabel), - i, oomAdj, schedGroup, r.toShortString(), r.adjType)); + N-i, oomAdj, schedGroup, r.toShortString(), r.adjType)); if (r.adjSource != null || r.adjTarget != null) { - pw.println(prefix + " " + r.adjTarget - + "<=" + r.adjSource); + pw.print(prefix); + pw.print(" "); + if (r.adjTarget instanceof ComponentName) { + pw.print(((ComponentName)r.adjTarget).flattenToShortString()); + } else if (r.adjTarget != null) { + pw.print(r.adjTarget.toString()); + } else { + pw.print("{null}"); + } + pw.print("<="); + if (r.adjSource instanceof ProcessRecord) { + pw.print("Proc{"); + pw.print(((ProcessRecord)r.adjSource).toShortString()); + pw.println("}"); + } else if (r.adjSource != null) { + pw.println(r.adjSource.toString()); + } else { + pw.println("{null}"); + } } } else { pw.println(String.format("%s%s #%2d: %s", @@ -10371,22 +7583,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return false; } - private final int indexOfTokenLocked(IBinder token) { - int count = mHistory.size(); - - // convert the token to an entry in the history. - int index = -1; - for (int i=count-1; i>=0; i--) { - Object o = mHistory.get(i); - if (o == token) { - index = i; - break; - } - } - - return index; - } - private final void killServicesLocked(ProcessRecord app, boolean allowRestart) { // Report disconnected services. @@ -10394,9 +7590,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // XXX we are letting the client link to the service for // death notifications. if (app.services.size() > 0) { - Iterator it = app.services.iterator(); + Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { - ServiceRecord r = (ServiceRecord)it.next(); + ServiceRecord r = it.next(); if (r.connections.size() > 0) { Iterator<ConnectionRecord> jt = r.connections.values().iterator(); @@ -10431,9 +7627,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app.services.size() != 0) { // Any services running in the application need to be placed // back in the pending list. - Iterator it = app.services.iterator(); + Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { - ServiceRecord sr = (ServiceRecord)it.next(); + ServiceRecord sr = it.next(); synchronized (sr.stats.getBatteryStats()) { sr.stats.stopLaunchedLocked(); } @@ -10572,9 +7768,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Remove published content providers. if (!app.pubProviders.isEmpty()) { - Iterator it = app.pubProviders.values().iterator(); + Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator(); while (it.hasNext()) { - ContentProviderRecord cpr = (ContentProviderRecord)it.next(); + ContentProviderRecord cpr = it.next(); cpr.provider = null; cpr.app = null; @@ -10666,6 +7862,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_PROCESSES) Slog.v(TAG, "Removing non-persistent process during cleanup: " + app); mProcessNames.remove(app.processName, app.info.uid); + if (mHeavyWeightProcess == app) { + mHeavyWeightProcess = null; + mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); + } } else if (!app.removed) { // This app is persistent, so we need to keep its record around. // If it is not already on the pending app list, add it there @@ -10707,8 +7907,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int NL = mLaunchingProviders.size(); boolean restart = false; for (int i=0; i<NL; i++) { - ContentProviderRecord cpr = (ContentProviderRecord) - mLaunchingProviders.get(i); + ContentProviderRecord cpr = mLaunchingProviders.get(i); if (cpr.launchingApp == app) { if (!alwaysBad && !app.bad) { restart = true; @@ -10831,7 +8030,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r == null) { try { ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveService( + AppGlobals.getPackageManager().resolveService( service, resolvedType, 0); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; @@ -10888,7 +8087,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r == null) { try { ResolveInfo rInfo = - ActivityThread.getPackageManager().resolveService( + AppGlobals.getPackageManager().resolveService( service, resolvedType, STOCK_PM_FLAGS); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; @@ -11625,7 +8824,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { boolean anyForeground = false; - for (ServiceRecord sr : (HashSet<ServiceRecord>)proc.services) { + for (ServiceRecord sr : proc.services) { if (sr.isForeground) { anyForeground = true; break; @@ -11659,14 +8858,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + ") when binding service " + service); } - HistoryRecord activity = null; + ActivityRecord activity = null; if (token != null) { - int aindex = indexOfTokenLocked(token); + int aindex = mMainStack.indexOfTokenLocked(token); if (aindex < 0) { Slog.w(TAG, "Binding with unknown activity: " + token); return 0; } - activity = (HistoryRecord)mHistory.get(aindex); + activity = (ActivityRecord)mMainStack.mHistory.get(aindex); } int clientLabel = 0; @@ -11770,8 +8969,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return 1; } - private void removeConnectionLocked( - ConnectionRecord c, ProcessRecord skipApp, HistoryRecord skipAct) { + void removeConnectionLocked( + ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) { IBinder binder = c.conn.asBinder(); AppBindRecord b = c.binding; ServiceRecord s = b.service; @@ -12452,7 +9651,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Always okay. } else if (callerApp == null || !callerApp.persistent) { try { - if (ActivityThread.getPackageManager().isProtectedBroadcast( + if (AppGlobals.getPackageManager().isProtectedBroadcast( intent.getAction())) { String msg = "Permission Denial: not allowed to send broadcast " + intent.getAction() + " from pid=" @@ -12511,7 +9710,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { if (intent.getComponent() != null) { // Broadcast is going to one specific receiver class... - ActivityInfo ai = ActivityThread.getPackageManager(). + ActivityInfo ai = AppGlobals.getPackageManager(). getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS); if (ai != null) { receivers = new ArrayList(); @@ -12524,7 +9723,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { receivers = - ActivityThread.getPackageManager().queryIntentReceivers( + AppGlobals.getPackageManager().queryIntentReceivers( intent, resolvedType, STOCK_PM_FLAGS); } registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false); @@ -13276,7 +10475,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.callingUid != Process.SYSTEM_UID && r.requiredPermission != null) { try { - perm = ActivityThread.getPackageManager(). + perm = AppGlobals.getPackageManager(). checkPermission(r.requiredPermission, info.activityInfo.applicationInfo.packageName); } catch (RemoteException e) { @@ -13533,7 +10732,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * configuration. */ public boolean updateConfigurationLocked(Configuration values, - HistoryRecord starting) { + ActivityRecord starting) { int changes = 0; boolean kept = true; @@ -13602,18 +10801,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the configuration changed, and the caller is not already // in the process of starting an activity, then find the top // activity to check if its configuration needs to change. - starting = topRunningActivityLocked(null); + starting = mMainStack.topRunningActivityLocked(null); } if (starting != null) { - kept = ensureActivityConfigurationLocked(starting, changes); + kept = mMainStack.ensureActivityConfigurationLocked(starting, changes); if (kept) { // If this didn't result in the starting activity being // destroyed, then we need to make sure at this point that all // other activities are made visible. if (DEBUG_SWITCH) Slog.i(TAG, "Config didn't destroy " + starting + ", ensuring others are correct."); - ensureActivitiesVisibleLocked(starting, changes); + mMainStack.ensureActivitiesVisibleLocked(starting, changes); } } @@ -13623,160 +10822,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return kept; } - - private final boolean relaunchActivityLocked(HistoryRecord r, - int changes, boolean andResume) { - List<ResultInfo> results = null; - List<Intent> newIntents = null; - if (andResume) { - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r - + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); - EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY - : EventLogTags.AM_RELAUNCH_ACTIVITY, System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - - r.startFreezingScreenLocked(r.app, 0); - - try { - if (DEBUG_SWITCH) Slog.i(TAG, "Switch is restarting resumed " + r); - r.app.thread.scheduleRelaunchActivity(r, results, newIntents, - changes, !andResume, mConfiguration); - // Note: don't need to call pauseIfSleepingLocked() here, because - // the caller will only pass in 'andResume' if this activity is - // currently resumed, which implies we aren't sleeping. - } catch (RemoteException e) { - return false; - } - - if (andResume) { - r.results = null; - r.newIntents = null; - reportResumedActivityLocked(r); - } - - return true; - } - - /** - * Make sure the given activity matches the current configuration. Returns - * false if the activity had to be destroyed. Returns true if the - * configuration is the same, or the activity will remain running as-is - * for whatever reason. Ensures the HistoryRecord is updated with the - * correct configuration and all other bookkeeping is handled. - */ - private final boolean ensureActivityConfigurationLocked(HistoryRecord r, - int globalChanges) { - if (mConfigWillChange) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Skipping config check (will change): " + r); - return true; - } - - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Ensuring correct configuration: " + r); - - // Short circuit: if the two configurations are the exact same - // object (the common case), then there is nothing to do. - Configuration newConfig = mConfiguration; - if (r.configuration == newConfig) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Configuration unchanged in " + r); - return true; - } - - // We don't worry about activities that are finishing. - if (r.finishing) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Configuration doesn't matter in finishing " + r); - r.stopFreezingScreenLocked(false); - return true; - } - - // Okay we now are going to make this activity have the new config. - // But then we need to figure out how it needs to deal with that. - Configuration oldConfig = r.configuration; - r.configuration = newConfig; - - // If the activity isn't currently running, just leave the new - // configuration and it will pick that up next time it starts. - if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Configuration doesn't matter not running " + r); - r.stopFreezingScreenLocked(false); - return true; - } - - // If the activity isn't persistent, there is a chance we will - // need to restart it. - if (!r.persistent) { - - // Figure out what has changed between the two configurations. - int changes = oldConfig.diff(newConfig); - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { - Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" - + Integer.toHexString(changes) + ", handles=0x" - + Integer.toHexString(r.info.configChanges) - + ", newConfig=" + newConfig); - } - if ((changes&(~r.info.configChanges)) != 0) { - // Aha, the activity isn't handling the change, so DIE DIE DIE. - r.configChangeFlags |= changes; - r.startFreezingScreenLocked(r.app, globalChanges); - if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is destroying non-running " + r); - destroyActivityLocked(r, true); - } else if (r.state == ActivityState.PAUSING) { - // A little annoying: we are waiting for this activity to - // finish pausing. Let's not do anything now, but just - // flag that it needs to be restarted when done pausing. - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is skipping already pausing " + r); - r.configDestroy = true; - return true; - } else if (r.state == ActivityState.RESUMED) { - // Try to optimize this case: the configuration is changing - // and we need to restart the top, resumed activity. - // Instead of doing the normal handshaking, just say - // "restart!". - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is restarting resumed " + r); - relaunchActivityLocked(r, r.configChangeFlags, true); - r.configChangeFlags = 0; - } else { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, - "Switch is restarting non-resumed " + r); - relaunchActivityLocked(r, r.configChangeFlags, false); - r.configChangeFlags = 0; - } - - // All done... tell the caller we weren't able to keep this - // activity around. - return false; - } - } - - // Default case: the activity can handle this new configuration, so - // hand it over. Note that we don't need to give it the new - // configuration, since we always send configuration changes to all - // process when they happen so it can just use whatever configuration - // it last got. - if (r.app != null && r.app.thread != null) { - try { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r); - r.app.thread.scheduleActivityConfigurationChanged(r); - } catch (RemoteException e) { - // If process died, whatever. - } - } - r.stopFreezingScreenLocked(false); - - return true; - } /** * Save the locale. You must be inside a synchronized (this) block. @@ -13868,15 +10913,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjType = "exec-service"; } else if (app.foregroundServices) { // The user is aware of this app, so make it visible. - adj = VISIBLE_APP_ADJ; + adj = PERCEPTIBLE_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "foreground-service"; } else if (app.forcingToForeground != null) { // The user is aware of this app, so make it visible. - adj = VISIBLE_APP_ADJ; + adj = PERCEPTIBLE_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "force-foreground"; app.adjSource = app.forcingToForeground; + } else if (app == mHeavyWeightProcess) { + // We don't want to kill the current heavy-weight process. + adj = HEAVY_WEIGHT_APP_ADJ; + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.adjType = "heavy"; } else if (app == mHomeProcess) { // This process is hosting what we currently consider to be the // home app, so we don't want to let it go into the background. @@ -13891,7 +10941,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjType = "bg-activities"; N = app.activities.size(); for (int j=0; j<N; j++) { - if (((HistoryRecord)app.activities.get(j)).visible) { + if (app.activities.get(j).visible) { // This app has a visible activity! app.hidden = false; adj = VISIBLE_APP_ADJ; @@ -13934,9 +10984,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long now = SystemClock.uptimeMillis(); // This process is more important if the top activity is // bound to the service. - Iterator jt = app.services.iterator(); + Iterator<ServiceRecord> jt = app.services.iterator(); while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) { - ServiceRecord s = (ServiceRecord)jt.next(); + ServiceRecord s = jt.next(); if (s.startRequested) { if (now < (s.lastActivity+MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within @@ -13971,7 +11021,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ProcessRecord client = cr.binding.client; int myHiddenAdj = hiddenAdj; if (myHiddenAdj > client.hiddenAdj) { - if (client.hiddenAdj > VISIBLE_APP_ADJ) { + if (client.hiddenAdj >= VISIBLE_APP_ADJ) { myHiddenAdj = client.hiddenAdj; } else { myHiddenAdj = VISIBLE_APP_ADJ; @@ -13980,7 +11030,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen int clientAdj = computeOomAdjLocked( client, myHiddenAdj, TOP_APP, true); if (adj > clientAdj) { - adj = clientAdj > VISIBLE_APP_ADJ + adj = clientAdj >= VISIBLE_APP_ADJ ? clientAdj : VISIBLE_APP_ADJ; if (!client.hidden) { app.hidden = false; @@ -13989,7 +11039,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE; app.adjSource = cr.binding.client; - app.adjTarget = s.serviceInfo.name; + app.adjTarget = s.name; } if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { @@ -13997,7 +11047,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } } - HistoryRecord a = cr.activity; + ActivityRecord a = cr.activity; //if (a != null) { // Slog.i(TAG, "Connection to " + a ": state=" + a.state); //} @@ -14011,7 +11061,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE; app.adjSource = a; - app.adjTarget = s.serviceInfo.name; + app.adjTarget = s.name; } } } @@ -14031,10 +11081,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (app.pubProviders.size() != 0 && (adj > FOREGROUND_APP_ADJ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - Iterator jt = app.pubProviders.values().iterator(); + Iterator<ContentProviderRecord> jt = app.pubProviders.values().iterator(); while (jt.hasNext() && (adj > FOREGROUND_APP_ADJ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - ContentProviderRecord cpr = (ContentProviderRecord)jt.next(); + ContentProviderRecord cpr = jt.next(); if (cpr.clients.size() != 0) { Iterator<ProcessRecord> kt = cpr.clients.iterator(); while (kt.hasNext() && adj > FOREGROUND_APP_ADJ) { @@ -14063,7 +11113,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_PROVIDER_IN_USE; app.adjSource = client; - app.adjTarget = cpr.info.name; + app.adjTarget = cpr.name; } if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { schedGroup = Process.THREAD_GROUP_DEFAULT; @@ -14079,7 +11129,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen schedGroup = Process.THREAD_GROUP_DEFAULT; app.hidden = false; app.adjType = "provider"; - app.adjTarget = cpr.info.name; + app.adjTarget = cpr.name; } } } @@ -14091,7 +11141,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj); if (adj > app.maxAdj) { adj = app.maxAdj; - if (app.maxAdj <= VISIBLE_APP_ADJ) { + if (app.maxAdj <= PERCEPTIBLE_APP_ADJ) { schedGroup = Process.THREAD_GROUP_DEFAULT; } } @@ -14127,8 +11177,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final boolean canGcNowLocked() { return mParallelBroadcasts.size() == 0 && mOrderedBroadcasts.size() == 0 - && (mSleeping || (mResumedActivity != null && - mResumedActivity.idle)); + && (mSleeping || (mMainStack.mResumedActivity != null && + mMainStack.mResumedActivity.idle)); } /** @@ -14143,7 +11193,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (canGcNowLocked()) { while (mProcessesToGc.size() > 0) { ProcessRecord proc = mProcessesToGc.remove(0); - if (proc.curRawAdj > VISIBLE_APP_ADJ || proc.reportLowMemory) { + if (proc.curRawAdj > PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) { if ((proc.lastRequestedGc+GC_MIN_INTERVAL) <= SystemClock.uptimeMillis()) { // To avoid spamming the system, we will GC processes one @@ -14298,19 +11348,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return true; } - private final HistoryRecord resumedAppLocked() { - HistoryRecord resumedActivity = mResumedActivity; + private final ActivityRecord resumedAppLocked() { + ActivityRecord resumedActivity = mMainStack.mResumedActivity; if (resumedActivity == null || resumedActivity.app == null) { - resumedActivity = mPausingActivity; + resumedActivity = mMainStack.mPausingActivity; if (resumedActivity == null || resumedActivity.app == null) { - resumedActivity = topRunningActivityLocked(null); + resumedActivity = mMainStack.topRunningActivityLocked(null); } } return resumedActivity; } private final boolean updateOomAdjLocked(ProcessRecord app) { - final HistoryRecord TOP_ACT = resumedAppLocked(); + final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; int curAdj = app.curAdj; final boolean wasHidden = app.curAdj >= HIDDEN_APP_MIN_ADJ @@ -14331,9 +11381,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return res; } - private final boolean updateOomAdjLocked() { + final boolean updateOomAdjLocked() { boolean didOomAdj = true; - final HistoryRecord TOP_ACT = resumedAppLocked(); + final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; if (false) { @@ -14395,7 +11445,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return ENFORCE_PROCESS_LIMIT || mProcessLimit > 0 ? false : didOomAdj; } - private final void trimApplications() { + final void trimApplications() { synchronized (this) { int i; @@ -14515,7 +11565,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (Config.LOGV) Slog.v( TAG, "Looking to quit " + app.processName); for (j=0; j<NUMA && canQuit; j++) { - HistoryRecord r = (HistoryRecord)app.activities.get(j); + ActivityRecord r = app.activities.get(j); if (Config.LOGV) Slog.v( TAG, " " + r.intent.getComponent().flattenToShortString() + ": frozen=" + r.haveState + ", visible=" + r.visible); @@ -14525,9 +11575,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (canQuit) { // Finish all of the activities, and then the app itself. for (j=0; j<NUMA; j++) { - HistoryRecord r = (HistoryRecord)app.activities.get(j); + ActivityRecord r = app.activities.get(j); if (!r.finishing) { - destroyActivityLocked(r, false); + r.stack.destroyActivityLocked(r, false); } r.resultTo = null; } @@ -14564,25 +11614,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. for ( i=0; - i<mLRUActivities.size() - && mLRUActivities.size() > curMaxActivities; + i<mMainStack.mLRUActivities.size() + && mMainStack.mLRUActivities.size() > curMaxActivities; i++) { - final HistoryRecord r - = (HistoryRecord)mLRUActivities.get(i); + final ActivityRecord r + = (ActivityRecord)mMainStack.mLRUActivities.get(i); // We can finish this one if we have its icicle saved and // it is not persistent. if ((r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped && !r.persistent && !r.finishing) { - final int origSize = mLRUActivities.size(); - destroyActivityLocked(r, true); + final int origSize = mMainStack.mLRUActivities.size(); + r.stack.destroyActivityLocked(r, true); // This will remove it from the LRU list, so keep // our index at the same value. Note that this check to // see if the size changes is just paranoia -- if // something unexpected happens, we don't want to end up // in an infinite loop. - if (origSize > mLRUActivities.size()) { + if (origSize > mMainStack.mLRUActivities.size()) { i--; } } diff --git a/services/java/com/android/server/am/HistoryRecord.java b/services/java/com/android/server/am/ActivityRecord.java index dca7a99..79756a7 100644 --- a/services/java/com/android/server/am/HistoryRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -17,7 +17,7 @@ package com.android.server.am; import com.android.server.AttributeCache; -import com.android.server.am.ActivityManagerService.ActivityState; +import com.android.server.am.ActivityStack.ActivityState; import android.app.Activity; import android.content.ComponentName; @@ -32,6 +32,7 @@ import android.os.Process; import android.os.SystemClock; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import android.view.IApplicationToken; import java.io.PrintWriter; @@ -42,8 +43,9 @@ import java.util.HashSet; /** * An entry in the history stack, representing an activity. */ -class HistoryRecord extends IApplicationToken.Stub { +class ActivityRecord extends IApplicationToken.Stub { final ActivityManagerService service; // owner + final ActivityStack stack; // owner final ActivityInfo info; // all about me final int launchedFromUid; // always the uid who started the activity. final Intent intent; // the original intent that generated us @@ -68,7 +70,7 @@ class HistoryRecord extends IApplicationToken.Stub { long startTime; // when we starting launching this activity long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity Configuration configuration; // configuration activity was last running in - HistoryRecord resultTo; // who started this entry, so will get our reply + ActivityRecord resultTo; // who started this entry, so will get our reply final String resultWho; // additional identifier for use by resultTo. final int requestCode; // code given by requester (resultTo) ArrayList results; // pending ActivityResult objs we have received @@ -80,7 +82,7 @@ class HistoryRecord extends IApplicationToken.Stub { ProcessRecord app; // if non-null, hosting application Bitmap thumbnail; // icon representation of paused screen CharSequence description; // textual description of paused screen - ActivityManagerService.ActivityState state; // current state we are in + ActivityState state; // current state we are in Bundle icicle; // last saved activity state boolean frontOfTask; // is this the root activity of its task? boolean launchFailed; // set if a launched failed, to abort on 2nd try @@ -101,6 +103,7 @@ class HistoryRecord extends IApplicationToken.Stub { boolean idle; // has the activity gone idle? boolean hasBeenLaunched;// has this activity ever been launched? boolean frozenBeforeDestroy;// has been frozen but not yet destroyed. + boolean immersive; // immersive mode (don't interrupt if possible) String stringName; // for caching of toString(). @@ -153,6 +156,7 @@ class HistoryRecord extends IApplicationToken.Stub { pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused); pw.print(" inHistory="); pw.print(inHistory); pw.print(" persistent="); pw.print(persistent); + pw.print(" immersive="); pw.print(immersive); pw.print(" launchMode="); pw.println(launchMode); pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen); pw.print(" visible="); pw.print(visible); @@ -173,12 +177,13 @@ class HistoryRecord extends IApplicationToken.Stub { } } - HistoryRecord(ActivityManagerService _service, ProcessRecord _caller, + ActivityRecord(ActivityManagerService _service, ActivityStack _stack, ProcessRecord _caller, int _launchedFromUid, Intent _intent, String _resolvedType, ActivityInfo aInfo, Configuration _configuration, - HistoryRecord _resultTo, String _resultWho, int _reqCode, + ActivityRecord _resultTo, String _resultWho, int _reqCode, boolean _componentSpecified) { service = _service; + stack = _stack; info = aInfo; launchedFromUid = _launchedFromUid; intent = _intent; @@ -189,7 +194,7 @@ class HistoryRecord extends IApplicationToken.Stub { resultTo = _resultTo; resultWho = _resultWho; requestCode = _reqCode; - state = ActivityManagerService.ActivityState.INITIALIZING; + state = ActivityState.INITIALIZING; frontOfTask = false; launchFailed = false; haveState = false; @@ -278,6 +283,8 @@ class HistoryRecord extends IApplicationToken.Stub { } else { isHomeActivity = false; } + + immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0; } else { realActivity = null; taskAffinity = null; @@ -289,10 +296,11 @@ class HistoryRecord extends IApplicationToken.Stub { packageName = null; fullscreen = true; isHomeActivity = false; + immersive = false; } } - void addResultLocked(HistoryRecord from, String resultWho, + void addResultLocked(ActivityRecord from, String resultWho, int requestCode, int resultCode, Intent resultData) { ActivityResult r = new ActivityResult(from, resultWho, @@ -303,7 +311,7 @@ class HistoryRecord extends IApplicationToken.Stub { results.add(r); } - void removeResultsLocked(HistoryRecord from, String resultWho, + void removeResultsLocked(ActivityRecord from, String resultWho, int requestCode) { if (results != null) { for (int i=results.size()-1; i>=0; i--) { @@ -327,6 +335,52 @@ class HistoryRecord extends IApplicationToken.Stub { } newIntents.add(intent); } + + /** + * Deliver a new Intent to an existing activity, so that its onNewIntent() + * method will be called at the proper time. + */ + final void deliverNewIntentLocked(Intent intent) { + boolean sent = false; + if (state == ActivityState.RESUMED + && app != null && app.thread != null) { + try { + ArrayList<Intent> ar = new ArrayList<Intent>(); + ar.add(new Intent(intent)); + app.thread.scheduleNewIntent(ar, this); + sent = true; + } catch (Exception e) { + Slog.w(ActivityManagerService.TAG, + "Exception thrown sending new intent to " + this, e); + } + } + if (!sent) { + addNewIntentLocked(new Intent(intent)); + } + } + + void removeUriPermissionsLocked() { + if (readUriPermissions != null) { + for (UriPermission perm : readUriPermissions) { + perm.readActivities.remove(this); + if (perm.readActivities.size() == 0 && (perm.globalModeFlags + &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { + perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + service.removeUriPermissionIfNeededLocked(perm); + } + } + } + if (writeUriPermissions != null) { + for (UriPermission perm : writeUriPermissions) { + perm.writeActivities.remove(this); + if (perm.writeActivities.size() == 0 && (perm.globalModeFlags + &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { + perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + service.removeUriPermissionIfNeededLocked(perm); + } + } + } + } void pauseKeyDispatchingLocked() { if (!keysPaused) { @@ -370,8 +424,8 @@ class HistoryRecord extends IApplicationToken.Stub { if (startTime != 0) { final long curTime = SystemClock.uptimeMillis(); final long thisTime = curTime - startTime; - final long totalTime = service.mInitialStartTime != 0 - ? (curTime - service.mInitialStartTime) : thisTime; + final long totalTime = stack.mInitialStartTime != 0 + ? (curTime - stack.mInitialStartTime) : thisTime; if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { EventLog.writeEvent(EventLogTags.ACTIVITY_LAUNCH_TIME, System.identityHashCode(this), shortComponentName, @@ -387,14 +441,14 @@ class HistoryRecord extends IApplicationToken.Stub { sb.append(" ms)"); Log.i(ActivityManagerService.TAG, sb.toString()); } - service.reportActivityLaunchedLocked(false, this, thisTime, totalTime); + stack.reportActivityLaunchedLocked(false, this, thisTime, totalTime); if (totalTime > 0) { service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime); } startTime = 0; - service.mInitialStartTime = 0; + stack.mInitialStartTime = 0; } - service.reportActivityVisibleLocked(this); + stack.reportActivityVisibleLocked(this); if (ActivityManagerService.DEBUG_SWITCH) Log.v( ActivityManagerService.TAG, "windowsVisible(): " + this); if (!nowVisible) { @@ -403,27 +457,27 @@ class HistoryRecord extends IApplicationToken.Stub { // Instead of doing the full stop routine here, let's just // hide any activities we now can, and let them stop when // the normal idle happens. - service.processStoppingActivitiesLocked(false); + stack.processStoppingActivitiesLocked(false); } else { // If this activity was already idle, then we now need to // make sure we perform the full stop of any activities // that are waiting to do so. This is because we won't // do that while they are still waiting for this one to // become visible. - final int N = service.mWaitingVisibleActivities.size(); + final int N = stack.mWaitingVisibleActivities.size(); if (N > 0) { for (int i=0; i<N; i++) { - HistoryRecord r = (HistoryRecord) - service.mWaitingVisibleActivities.get(i); + ActivityRecord r = (ActivityRecord) + stack.mWaitingVisibleActivities.get(i); r.waitingVisible = false; if (ActivityManagerService.DEBUG_SWITCH) Log.v( ActivityManagerService.TAG, "Was waiting for visible: " + r); } - service.mWaitingVisibleActivities.clear(); + stack.mWaitingVisibleActivities.clear(); Message msg = Message.obtain(); - msg.what = ActivityManagerService.IDLE_NOW_MSG; - service.mHandler.sendMessage(msg); + msg.what = ActivityStack.IDLE_NOW_MSG; + stack.mHandler.sendMessage(msg); } } service.scheduleAppGcsLocked(); @@ -437,16 +491,16 @@ class HistoryRecord extends IApplicationToken.Stub { nowVisible = false; } - private HistoryRecord getWaitingHistoryRecordLocked() { + private ActivityRecord getWaitingHistoryRecordLocked() { // First find the real culprit... if we are waiting // for another app to start, then we have paused dispatching // for this activity. - HistoryRecord r = this; + ActivityRecord r = this; if (r.waitingVisible) { // Hmmm, who might we be waiting for? - r = service.mResumedActivity; + r = stack.mResumedActivity; if (r == null) { - r = service.mPausingActivity; + r = stack.mPausingActivity; } // Both of those null? Fall back to 'this' again if (r == null) { @@ -458,7 +512,7 @@ class HistoryRecord extends IApplicationToken.Stub { } public boolean keyDispatchingTimedOut() { - HistoryRecord r; + ActivityRecord r; ProcessRecord anrApp = null; synchronized(service) { r = getWaitingHistoryRecordLocked(); @@ -496,7 +550,7 @@ class HistoryRecord extends IApplicationToken.Stub { /** Returns the key dispatching timeout for this application token. */ public long getKeyDispatchingTimeout() { synchronized(service) { - HistoryRecord r = getWaitingHistoryRecordLocked(); + ActivityRecord r = getWaitingHistoryRecordLocked(); if (r == null || r.app == null || r.app.instrumentationClass == null) { return ActivityManagerService.KEY_DISPATCHING_TIMEOUT; diff --git a/services/java/com/android/server/am/ActivityResult.java b/services/java/com/android/server/am/ActivityResult.java index 3cc2725..12eba34 100644 --- a/services/java/com/android/server/am/ActivityResult.java +++ b/services/java/com/android/server/am/ActivityResult.java @@ -24,9 +24,9 @@ import android.os.Bundle; * Pending result information to send back to an activity. */ class ActivityResult extends ResultInfo { - final HistoryRecord mFrom; + final ActivityRecord mFrom; - public ActivityResult(HistoryRecord from, String resultWho, + public ActivityResult(ActivityRecord from, String resultWho, int requestCode, int resultCode, Intent data) { super(resultWho, requestCode, resultCode, data); mFrom = from; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java new file mode 100644 index 0000000..de7b15c --- /dev/null +++ b/services/java/com/android/server/am/ActivityStack.java @@ -0,0 +1,3521 @@ +/* + * 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.am; + +import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.am.ActivityManagerService.PendingActivityLaunch; + +import android.app.Activity; +import android.app.AppGlobals; +import android.app.IActivityManager; +import static android.app.IActivityManager.START_CLASS_NOT_FOUND; +import static android.app.IActivityManager.START_DELIVERED_TO_TOP; +import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; +import static android.app.IActivityManager.START_INTENT_NOT_RESOLVED; +import static android.app.IActivityManager.START_PERMISSION_DENIED; +import static android.app.IActivityManager.START_RETURN_INTENT_TO_CALLER; +import static android.app.IActivityManager.START_SUCCESS; +import static android.app.IActivityManager.START_SWITCHES_CANCELED; +import static android.app.IActivityManager.START_TASK_TO_FRONT; +import android.app.IApplicationThread; +import android.app.PendingIntent; +import android.app.ResultInfo; +import android.app.IActivityManager.WaitResult; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +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.RemoteException; +import android.os.SystemClock; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.view.WindowManagerPolicy; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * State and management of a single stack of activities. + */ +public class ActivityStack { + static final String TAG = ActivityManagerService.TAG; + static final boolean localLOGV = ActivityManagerService.localLOGV; + static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH; + static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE; + static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY; + static final boolean DEBUG_USER_LEAVING = ActivityManagerService.DEBUG_USER_LEAVING; + static final boolean DEBUG_TRANSITION = ActivityManagerService.DEBUG_TRANSITION; + static final boolean DEBUG_RESULTS = ActivityManagerService.DEBUG_RESULTS; + static final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION; + static final boolean DEBUG_TASKS = ActivityManagerService.DEBUG_TASKS; + + static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS; + + // How long we wait until giving up on the last activity telling us it + // is idle. + static final int IDLE_TIMEOUT = 10*1000; + + // How long we wait until giving up on the last activity to pause. This + // is short because it directly impacts the responsiveness of starting the + // next activity. + static final int PAUSE_TIMEOUT = 500; + + // How long we can hold the launch wake lock before giving up. + static final int LAUNCH_TIMEOUT = 10*1000; + + // How long we wait until giving up on an activity telling us it has + // finished destroying itself. + static final int DESTROY_TIMEOUT = 10*1000; + + // How long until we reset a task when the user returns to it. Currently + // 30 minutes. + static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30; + + // Set to false to disable the preview that is shown while a new activity + // is being started. + static final boolean SHOW_APP_STARTING_PREVIEW = true; + + enum ActivityState { + INITIALIZING, + RESUMED, + PAUSING, + PAUSED, + STOPPING, + STOPPED, + FINISHING, + DESTROYING, + DESTROYED + } + + final ActivityManagerService mService; + final boolean mMainStack; + + final Context mContext; + + /** + * The back history of all previous (and possibly still + * running) activities. It contains HistoryRecord objects. + */ + final ArrayList mHistory = new ArrayList(); + + /** + * List of running activities, sorted by recent usage. + * The first entry in the list is the least recently used. + * It contains HistoryRecord objects. + */ + final ArrayList mLRUActivities = new ArrayList(); + + /** + * List of activities that are waiting for a new activity + * to become visible before completing whatever operation they are + * supposed to do. + */ + final ArrayList<ActivityRecord> mWaitingVisibleActivities + = new ArrayList<ActivityRecord>(); + + /** + * List of activities that are ready to be stopped, but waiting + * for the next activity to settle down before doing so. It contains + * HistoryRecord objects. + */ + final ArrayList<ActivityRecord> mStoppingActivities + = new ArrayList<ActivityRecord>(); + + /** + * Animations that for the current transition have requested not to + * be considered for the transition animation. + */ + final ArrayList<ActivityRecord> mNoAnimActivities + = new ArrayList<ActivityRecord>(); + + /** + * List of activities that are ready to be finished, but waiting + * for the previous activity to settle down before doing so. It contains + * HistoryRecord objects. + */ + final ArrayList<ActivityRecord> mFinishingActivities + = new ArrayList<ActivityRecord>(); + + /** + * List of people waiting to find out about the next launched activity. + */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched + = new ArrayList<IActivityManager.WaitResult>(); + + /** + * List of people waiting to find out about the next visible activity. + */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible + = new ArrayList<IActivityManager.WaitResult>(); + + /** + * Set when the system is going to sleep, until we have + * successfully paused the current activity and released our wake lock. + * At that point the system is allowed to actually sleep. + */ + final PowerManager.WakeLock mGoingToSleep; + + /** + * We don't want to allow the device to go to sleep while in the process + * of launching an activity. This is primarily to allow alarm intent + * receivers to launch an activity and get that to run before the device + * goes back to sleep. + */ + final PowerManager.WakeLock mLaunchingActivity; + + /** + * When we are in the process of pausing an activity, before starting the + * next one, this variable holds the activity that is currently being paused. + */ + ActivityRecord mPausingActivity = null; + + /** + * This is the last activity that we put into the paused state. This is + * used to determine if we need to do an activity transition while sleeping, + * when we normally hold the top activity paused. + */ + ActivityRecord mLastPausedActivity = null; + + /** + * Current activity that is resumed, or null if there is none. + */ + ActivityRecord mResumedActivity = null; + + /** + * Set when we know we are going to be calling updateConfiguration() + * soon, so want to skip intermediate config checks. + */ + boolean mConfigWillChange; + + /** + * Set to indicate whether to issue an onUserLeaving callback when a + * newly launched activity is being brought in front of us. + */ + boolean mUserLeaving = false; + + long mInitialStartTime = 0; + + static final int PAUSE_TIMEOUT_MSG = 9; + static final int IDLE_TIMEOUT_MSG = 10; + static final int IDLE_NOW_MSG = 11; + static final int LAUNCH_TIMEOUT_MSG = 16; + static final int DESTROY_TIMEOUT_MSG = 17; + static final int RESUME_TOP_ACTIVITY_MSG = 19; + + final Handler mHandler = new Handler() { + //public Handler() { + // if (localLOGV) Slog.v(TAG, "Handler started!"); + //} + + public void handleMessage(Message msg) { + switch (msg.what) { + case PAUSE_TIMEOUT_MSG: { + IBinder token = (IBinder)msg.obj; + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + Slog.w(TAG, "Activity pause timeout for " + token); + activityPaused(token, null, true); + } break; + case IDLE_TIMEOUT_MSG: { + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); + return; + } + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + IBinder token = (IBinder)msg.obj; + Slog.w(TAG, "Activity idle timeout for " + token); + activityIdleInternal(token, true, null); + } break; + case DESTROY_TIMEOUT_MSG: { + IBinder token = (IBinder)msg.obj; + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + Slog.w(TAG, "Activity destroy timeout for " + token); + activityDestroyed(token); + } break; + case IDLE_NOW_MSG: { + IBinder token = (IBinder)msg.obj; + activityIdleInternal(token, false, null); + } break; + case LAUNCH_TIMEOUT_MSG: { + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); + mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT); + return; + } + synchronized (mService) { + if (mLaunchingActivity.isHeld()) { + Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); + mLaunchingActivity.release(); + } + } + } break; + case RESUME_TOP_ACTIVITY_MSG: { + synchronized (mService) { + resumeTopActivityLocked(null); + } + } break; + } + } + }; + + ActivityStack(ActivityManagerService service, Context context, boolean mainStack) { + mService = service; + mContext = context; + mMainStack = mainStack; + PowerManager pm = + (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); + mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); + mLaunchingActivity.setReferenceCounted(false); + } + + final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { + int i = mHistory.size()-1; + while (i >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing && r != notTop) { + return r; + } + i--; + } + return null; + } + + final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { + int i = mHistory.size()-1; + while (i >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing && !r.delayedResume && r != notTop) { + return r; + } + i--; + } + return null; + } + + /** + * This is a simplified version of topRunningActivityLocked that provides a number of + * optional skip-over modes. It is intended for use with the ActivityController hook only. + * + * @param token If non-null, any history records matching this token will be skipped. + * @param taskId If non-zero, we'll attempt to skip over records with the same task ID. + * + * @return Returns the HistoryRecord of the next activity on the stack. + */ + final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { + int i = mHistory.size()-1; + while (i >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + // Note: the taskId check depends on real taskId fields being non-zero + if (!r.finishing && (token != r) && (taskId != r.task.taskId)) { + return r; + } + i--; + } + return null; + } + + final int indexOfTokenLocked(IBinder token) { + int count = mHistory.size(); + + // convert the token to an entry in the history. + int index = -1; + for (int i=count-1; i>=0; i--) { + Object o = mHistory.get(i); + if (o == token) { + index = i; + break; + } + } + + return index; + } + + private final boolean updateLRUListLocked(ActivityRecord r) { + final boolean hadit = mLRUActivities.remove(r); + mLRUActivities.add(r); + return hadit; + } + + /** + * Returns the top activity in any existing task matching the given + * Intent. Returns null if no such task is found. + */ + private ActivityRecord findTaskLocked(Intent intent, ActivityInfo info) { + ComponentName cls = intent.getComponent(); + if (info.targetActivity != null) { + cls = new ComponentName(info.packageName, info.targetActivity); + } + + TaskRecord cp = null; + + final int N = mHistory.size(); + for (int i=(N-1); i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing && r.task != cp + && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + cp = r.task; + //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() + // + "/aff=" + r.task.affinity + " to new cls=" + // + intent.getComponent().flattenToShortString() + "/aff=" + taskAffinity); + if (r.task.affinity != null) { + if (r.task.affinity.equals(info.taskAffinity)) { + //Slog.i(TAG, "Found matching affinity!"); + return r; + } + } else if (r.task.intent != null + && r.task.intent.getComponent().equals(cls)) { + //Slog.i(TAG, "Found matching class!"); + //dump(); + //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + return r; + } else if (r.task.affinityIntent != null + && r.task.affinityIntent.getComponent().equals(cls)) { + //Slog.i(TAG, "Found matching class!"); + //dump(); + //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + return r; + } + } + } + + return null; + } + + /** + * Returns the first activity (starting from the top of the stack) that + * is the same as the given activity. Returns null if no such activity + * is found. + */ + private ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { + ComponentName cls = intent.getComponent(); + if (info.targetActivity != null) { + cls = new ComponentName(info.packageName, info.targetActivity); + } + + final int N = mHistory.size(); + for (int i=(N-1); i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (!r.finishing) { + if (r.intent.getComponent().equals(cls)) { + //Slog.i(TAG, "Found matching class!"); + //dump(); + //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + return r; + } + } + } + + return null; + } + + final boolean realStartActivityLocked(ActivityRecord r, + ProcessRecord app, boolean andResume, boolean checkConfig) + throws RemoteException { + + r.startFreezingScreenLocked(app, 0); + mService.mWindowManager.setAppVisibility(r, true); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. Note that + // as a result of this, it can call back into the activity + // manager with a new orientation. We don't care about that, + // because the activity is not currently running so we are + // just restarting it anyway. + if (checkConfig) { + Configuration config = mService.mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + r.mayFreezeScreenLocked(app) ? r : null); + mService.updateConfigurationLocked(config, r); + } + + r.app = app; + + if (localLOGV) Slog.v(TAG, "Launching: " + r); + + int idx = app.activities.indexOf(r); + if (idx < 0) { + app.activities.add(r); + } + mService.updateLruProcessLocked(app, true, true); + + try { + if (app.thread == null) { + throw new RemoteException(); + } + List<ResultInfo> results = null; + List<Intent> newIntents = null; + if (andResume) { + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r + + " icicle=" + r.icicle + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + if (andResume) { + EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, + System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + } + if (r.isHomeActivity) { + mService.mHomeProcess = app; + } + mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); + app.thread.scheduleLaunchActivity(new Intent(r.intent), r, + System.identityHashCode(r), + r.info, r.icicle, results, newIntents, !andResume, + mService.isNextTransitionForward()); + + if ((app.info.flags&ApplicationInfo.FLAG_HEAVY_WEIGHT) != 0) { + // This may be a heavy-weight process! Note that the package + // manager will ensure that only activity can run in the main + // process of the .apk, which is the only thing that will be + // considered heavy-weight. + if (app.processName.equals(app.info.packageName)) { + if (mService.mHeavyWeightProcess != null + && mService.mHeavyWeightProcess != app) { + Log.w(TAG, "Starting new heavy weight process " + app + + " when already running " + + mService.mHeavyWeightProcess); + } + mService.mHeavyWeightProcess = app; + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); + msg.obj = r; + mService.mHandler.sendMessage(msg); + } + } + + } catch (RemoteException e) { + if (r.launchFailed) { + // This is the second time we failed -- finish activity + // and give up. + Slog.e(TAG, "Second failure launching " + + r.intent.getComponent().flattenToShortString() + + ", giving up", e); + mService.appDiedLocked(app, app.pid, app.thread); + requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, + "2nd-crash"); + return false; + } + + // This is the first time we failed -- restart process and + // retry. + app.activities.remove(r); + throw e; + } + + r.launchFailed = false; + if (updateLRUListLocked(r)) { + Slog.w(TAG, "Activity " + r + + " being launched, but already in LRU list"); + } + + if (andResume) { + // As part of the process of launching, ActivityThread also performs + // a resume. + r.state = ActivityState.RESUMED; + r.icicle = null; + r.haveState = false; + r.stopped = false; + mResumedActivity = r; + r.task.touchActiveTime(); + completeResumeLocked(r); + pauseIfSleepingLocked(); + } else { + // This activity is not starting in the resumed state... which + // should look like we asked it to pause+stop (but remain visible), + // and it has done so and reported back the current icicle and + // other state. + r.state = ActivityState.STOPPED; + r.stopped = true; + } + + // Launch the new version setup screen if needed. We do this -after- + // launching the initial activity (that is, home), so that it can have + // a chance to initialize itself while in the background, making the + // switch back to it faster and look better. + if (mMainStack) { + mService.startSetupActivityLocked(); + } + + return true; + } + + private final void startSpecificActivityLocked(ActivityRecord r, + boolean andResume, boolean checkConfig) { + // Is this activity's application already running? + ProcessRecord app = mService.getProcessRecordLocked(r.processName, + r.info.applicationInfo.uid); + + if (r.startTime == 0) { + r.startTime = SystemClock.uptimeMillis(); + if (mInitialStartTime == 0) { + mInitialStartTime = r.startTime; + } + } else if (mInitialStartTime == 0) { + mInitialStartTime = SystemClock.uptimeMillis(); + } + + if (app != null && app.thread != null) { + try { + realStartActivityLocked(r, app, andResume, checkConfig); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting activity " + + r.intent.getComponent().flattenToShortString(), e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, + "activity", r.intent.getComponent(), false); + } + + void pauseIfSleepingLocked() { + if (mService.mSleeping || mService.mShuttingDown) { + if (!mGoingToSleep.isHeld()) { + mGoingToSleep.acquire(); + if (mLaunchingActivity.isHeld()) { + mLaunchingActivity.release(); + mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + } + } + + // If we are not currently pausing an activity, get the current + // one to pause. If we are pausing one, we will just let that stuff + // run and release the wake lock when all done. + if (mPausingActivity == null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause..."); + if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); + startPausingLocked(false, true); + } + } + } + + private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { + if (mPausingActivity != null) { + RuntimeException e = new RuntimeException(); + Slog.e(TAG, "Trying to pause when pause is already pending for " + + mPausingActivity, e); + } + ActivityRecord prev = mResumedActivity; + if (prev == null) { + RuntimeException e = new RuntimeException(); + Slog.e(TAG, "Trying to pause when nothing is resumed", e); + resumeTopActivityLocked(null); + return; + } + if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev); + mResumedActivity = null; + mPausingActivity = prev; + mLastPausedActivity = prev; + prev.state = ActivityState.PAUSING; + prev.task.touchActiveTime(); + + mService.updateCpuStats(); + + if (prev.app != null && prev.app.thread != null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev); + try { + EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, + System.identityHashCode(prev), + prev.shortComponentName); + prev.app.thread.schedulePauseActivity(prev, prev.finishing, userLeaving, + prev.configChangeFlags); + if (mMainStack) { + mService.updateUsageStats(prev, false); + } + } catch (Exception e) { + // Ignore exception, if process died other code will cleanup. + Slog.w(TAG, "Exception thrown during pause", e); + mPausingActivity = null; + mLastPausedActivity = null; + } + } else { + mPausingActivity = null; + mLastPausedActivity = null; + } + + // If we are not going to sleep, we want to ensure the device is + // awake until the next activity is started. + if (!mService.mSleeping && !mService.mShuttingDown) { + mLaunchingActivity.acquire(); + if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { + // To be safe, don't allow the wake lock to be held for too long. + Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); + mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT); + } + } + + + if (mPausingActivity != null) { + // Have the window manager pause its key dispatching until the new + // activity has started. If we're pausing the activity just because + // the screen is being turned off and the UI is sleeping, don't interrupt + // key dispatch; the same activity will pick it up again on wakeup. + if (!uiSleeping) { + prev.pauseKeyDispatchingLocked(); + } else { + if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off"); + } + + // Schedule a pause timeout in case the app doesn't respond. + // We don't give it much time because this directly impacts the + // responsiveness seen by the user. + Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); + msg.obj = prev; + mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); + if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); + } else { + // This activity failed to schedule the + // pause, so just treat it as being paused now. + if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next."); + resumeTopActivityLocked(null); + } + } + + final void activityPaused(IBinder token, Bundle icicle, boolean timeout) { + if (DEBUG_PAUSE) Slog.v( + TAG, "Activity paused: token=" + token + ", icicle=" + icicle + + ", timeout=" + timeout); + + ActivityRecord r = null; + + synchronized (mService) { + int index = indexOfTokenLocked(token); + if (index >= 0) { + r = (ActivityRecord)mHistory.get(index); + if (!timeout) { + r.icicle = icicle; + r.haveState = true; + } + mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); + if (mPausingActivity == r) { + r.state = ActivityState.PAUSED; + completePauseLocked(); + } else { + EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, + System.identityHashCode(r), r.shortComponentName, + mPausingActivity != null + ? mPausingActivity.shortComponentName : "(none)"); + } + } + } + } + + private final void completePauseLocked() { + ActivityRecord prev = mPausingActivity; + if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); + + if (prev != null) { + if (prev.finishing) { + if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); + prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE); + } else if (prev.app != null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); + if (prev.waitingVisible) { + prev.waitingVisible = false; + mWaitingVisibleActivities.remove(prev); + if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v( + TAG, "Complete pause, no longer waiting: " + prev); + } + if (prev.configDestroy) { + // The previous is being paused because the configuration + // is changing, which means it is actually stopping... + // To juggle the fact that we are also starting a new + // instance right now, we need to first completely stop + // the current instance before starting the new one. + if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev); + destroyActivityLocked(prev, true); + } else { + mStoppingActivities.add(prev); + if (mStoppingActivities.size() > 3) { + // If we already have a few activities waiting to stop, + // then give up on things going idle and start clearing + // them out. + if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle"); + Message msg = Message.obtain(); + msg.what = IDLE_NOW_MSG; + mHandler.sendMessage(msg); + } + } + } else { + if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev); + prev = null; + } + mPausingActivity = null; + } + + if (!mService.mSleeping && !mService.mShuttingDown) { + resumeTopActivityLocked(prev); + } else { + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + if (mService.mShuttingDown) { + mService.notifyAll(); + } + } + + if (prev != null) { + prev.resumeKeyDispatchingLocked(); + } + + if (prev.app != null && prev.cpuTimeAtResume > 0 + && mService.mBatteryStatsService.isOnBattery()) { + long diff = 0; + synchronized (mService.mProcessStatsThread) { + diff = mService.mProcessStats.getCpuTimeForPid(prev.app.pid) + - prev.cpuTimeAtResume; + } + if (diff > 0) { + BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics(); + synchronized (bsi) { + BatteryStatsImpl.Uid.Proc ps = + bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, + prev.info.packageName); + if (ps != null) { + ps.addForegroundTimeLocked(diff); + } + } + } + } + prev.cpuTimeAtResume = 0; // reset it + } + + /** + * Once we know that we have asked an application to put an activity in + * the resumed state (either by launching it or explicitly telling it), + * this function updates the rest of our state to match that fact. + */ + private final void completeResumeLocked(ActivityRecord next) { + next.idle = false; + next.results = null; + next.newIntents = null; + + // schedule an idle timeout in case the app doesn't do it for us. + Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + msg.obj = next; + mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); + + if (false) { + // The activity was never told to pause, so just keep + // things going as-is. To maintain our own state, + // we need to emulate it coming back and saying it is + // idle. + msg = mHandler.obtainMessage(IDLE_NOW_MSG); + msg.obj = next; + mHandler.sendMessage(msg); + } + + if (mMainStack) { + mService.reportResumedActivityLocked(next); + } + + next.thumbnail = null; + if (mMainStack) { + mService.setFocusedActivityLocked(next); + } + next.resumeKeyDispatchingLocked(); + ensureActivitiesVisibleLocked(null, 0); + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + + // Mark the point when the activity is resuming + // TODO: To be more accurate, the mark should be before the onCreate, + // not after the onResume. But for subsequent starts, onResume is fine. + if (next.app != null) { + synchronized (mService.mProcessStatsThread) { + next.cpuTimeAtResume = mService.mProcessStats.getCpuTimeForPid(next.app.pid); + } + } else { + next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process + } + } + + /** + * Make sure that all activities that need to be visible (that is, they + * currently can be seen by the user) actually are. + */ + final void ensureActivitiesVisibleLocked(ActivityRecord top, + ActivityRecord starting, String onlyThisProcess, int configChanges) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "ensureActivitiesVisible behind " + top + + " configChanges=0x" + Integer.toHexString(configChanges)); + + // If the top activity is not fullscreen, then we need to + // make sure any activities under it are now visible. + final int count = mHistory.size(); + int i = count-1; + while (mHistory.get(i) != top) { + i--; + } + ActivityRecord r; + boolean behindFullscreen = false; + for (; i>=0; i--) { + r = (ActivityRecord)mHistory.get(i); + if (DEBUG_VISBILITY) Slog.v( + TAG, "Make visible? " + r + " finishing=" + r.finishing + + " state=" + r.state); + if (r.finishing) { + continue; + } + + final boolean doThisProcess = onlyThisProcess == null + || onlyThisProcess.equals(r.processName); + + // First: if this is not the current activity being started, make + // sure it matches the current configuration. + if (r != starting && doThisProcess) { + ensureActivityConfigurationLocked(r, 0); + } + + if (r.app == null || r.app.thread == null) { + if (onlyThisProcess == null + || onlyThisProcess.equals(r.processName)) { + // This activity needs to be visible, but isn't even + // running... get it started, but don't resume it + // at this point. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Start and freeze screen for " + r); + if (r != starting) { + r.startFreezingScreenLocked(r.app, configChanges); + } + if (!r.visible) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Starting and making visible: " + r); + mService.mWindowManager.setAppVisibility(r, true); + } + if (r != starting) { + startSpecificActivityLocked(r, false, false); + } + } + + } else if (r.visible) { + // If this activity is already visible, then there is nothing + // else to do here. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Skipping: already visible at " + r); + r.stopFreezingScreenLocked(false); + + } else if (onlyThisProcess == null) { + // This activity is not currently visible, but is running. + // Tell it to become visible. + r.visible = true; + if (r.state != ActivityState.RESUMED && r != starting) { + // If this activity is paused, tell it + // to now show its window. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Making visible and scheduling visibility: " + r); + try { + mService.mWindowManager.setAppVisibility(r, true); + r.app.thread.scheduleWindowVisibility(r, true); + r.stopFreezingScreenLocked(false); + } catch (Exception e) { + // Just skip on any failure; we'll make it + // visible when it next restarts. + Slog.w(TAG, "Exception thrown making visibile: " + + r.intent.getComponent(), e); + } + } + } + + // Aggregate current change flags. + configChanges |= r.configChangeFlags; + + if (r.fullscreen) { + // At this point, nothing else needs to be shown + if (DEBUG_VISBILITY) Slog.v( + TAG, "Stopping: fullscreen at " + r); + behindFullscreen = true; + i--; + break; + } + } + + // Now for any activities that aren't visible to the user, make + // sure they no longer are keeping the screen frozen. + while (i >= 0) { + r = (ActivityRecord)mHistory.get(i); + if (DEBUG_VISBILITY) Slog.v( + TAG, "Make invisible? " + r + " finishing=" + r.finishing + + " state=" + r.state + + " behindFullscreen=" + behindFullscreen); + if (!r.finishing) { + if (behindFullscreen) { + if (r.visible) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Making invisible: " + r); + r.visible = false; + try { + mService.mWindowManager.setAppVisibility(r, false); + if ((r.state == ActivityState.STOPPING + || r.state == ActivityState.STOPPED) + && r.app != null && r.app.thread != null) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Scheduling invisibility: " + r); + r.app.thread.scheduleWindowVisibility(r, false); + } + } catch (Exception e) { + // Just skip on any failure; we'll make it + // visible when it next restarts. + Slog.w(TAG, "Exception thrown making hidden: " + + r.intent.getComponent(), e); + } + } else { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Already invisible: " + r); + } + } else if (r.fullscreen) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Now behindFullscreen: " + r); + behindFullscreen = true; + } + } + i--; + } + } + + /** + * Version of ensureActivitiesVisible that can easily be called anywhere. + */ + final void ensureActivitiesVisibleLocked(ActivityRecord starting, + int configChanges) { + ActivityRecord r = topRunningActivityLocked(null); + if (r != null) { + ensureActivitiesVisibleLocked(r, starting, null, configChanges); + } + } + + /** + * Ensure that the top activity in the stack is resumed. + * + * @param prev The previously resumed activity, for when in the process + * of pausing; can be null to call from elsewhere. + * + * @return Returns true if something is being resumed, or false if + * nothing happened. + */ + final boolean resumeTopActivityLocked(ActivityRecord prev) { + // Find the first activity that is not finishing. + ActivityRecord next = topRunningActivityLocked(null); + + // Remember how we'll process this pause/resume situation, and ensure + // that the state is reset however we wind up proceeding. + final boolean userLeaving = mUserLeaving; + mUserLeaving = false; + + if (next == null) { + // There are no more activities! Let's just start up the + // Launcher... + if (mMainStack) { + return mService.startHomeActivityLocked(); + } + } + + next.delayedResume = false; + + // If the top activity is the resumed one, nothing to do. + if (mResumedActivity == next && next.state == ActivityState.RESUMED) { + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + return false; + } + + // If we are sleeping, and there is no resumed activity, and the top + // activity is paused, well that is the state we want. + if ((mService.mSleeping || mService.mShuttingDown) + && mLastPausedActivity == next && next.state == ActivityState.PAUSED) { + // Make sure we have executed any pending transitions, since there + // should be nothing left to do at this point. + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + return false; + } + + // The activity may be waiting for stop, but that is no longer + // appropriate for it. + mStoppingActivities.remove(next); + mWaitingVisibleActivities.remove(next); + + if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); + + // If we are currently pausing an activity, then don't do anything + // until that is done. + if (mPausingActivity != null) { + if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: pausing=" + mPausingActivity); + return false; + } + + // We need to start pausing the current activity so the top one + // can be resumed... + if (mResumedActivity != null) { + if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to start pausing"); + startPausingLocked(userLeaving, false); + return true; + } + + if (prev != null && prev != next) { + if (!prev.waitingVisible && next != null && !next.nowVisible) { + prev.waitingVisible = true; + mWaitingVisibleActivities.add(prev); + if (DEBUG_SWITCH) Slog.v( + TAG, "Resuming top, waiting visible to hide: " + prev); + } else { + // The next activity is already visible, so hide the previous + // activity's windows right now so we can show the new one ASAP. + // We only do this if the previous is finishing, which should mean + // it is on top of the one being resumed so hiding it quickly + // is good. Otherwise, we want to do the normal route of allowing + // the resumed activity to be shown so we can decide if the + // previous should actually be hidden depending on whether the + // new one is found to be full-screen or not. + if (prev.finishing) { + mService.mWindowManager.setAppVisibility(prev, false); + if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: " + + prev + ", waitingVisible=" + + (prev != null ? prev.waitingVisible : null) + + ", nowVisible=" + next.nowVisible); + } else { + if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: " + + prev + ", waitingVisible=" + + (prev != null ? prev.waitingVisible : null) + + ", nowVisible=" + next.nowVisible); + } + } + } + + // We are starting up the next activity, so tell the window manager + // that the previous one will be hidden soon. This way it can know + // to ignore it when computing the desired screen orientation. + if (prev != null) { + if (prev.finishing) { + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare close transition: prev=" + prev); + if (mNoAnimActivities.contains(prev)) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mService.mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE + : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + } + mService.mWindowManager.setAppWillBeHidden(prev); + mService.mWindowManager.setAppVisibility(prev, false); + } else { + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare open transition: prev=" + prev); + if (mNoAnimActivities.contains(next)) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mService.mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + : WindowManagerPolicy.TRANSIT_TASK_OPEN); + } + } + if (false) { + mService.mWindowManager.setAppWillBeHidden(prev); + mService.mWindowManager.setAppVisibility(prev, false); + } + } else if (mHistory.size() > 1) { + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare open transition: no previous"); + if (mNoAnimActivities.contains(next)) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + } + } + + if (next.app != null && next.app.thread != null) { + if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); + + // This activity is now becoming visible. + mService.mWindowManager.setAppVisibility(next, true); + + ActivityRecord lastResumedActivity = mResumedActivity; + ActivityState lastState = next.state; + + mService.updateCpuStats(); + + next.state = ActivityState.RESUMED; + mResumedActivity = next; + next.task.touchActiveTime(); + mService.updateLruProcessLocked(next.app, true, true); + updateLRUListLocked(next); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. + boolean updated = false; + if (mMainStack) { + synchronized (mService) { + Configuration config = mService.mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + next.mayFreezeScreenLocked(next.app) ? next : null); + if (config != null) { + next.frozenBeforeDestroy = true; + } + updated = mService.updateConfigurationLocked(config, next); + } + } + if (!updated) { + // The configuration update wasn't able to keep the existing + // instance of the activity, and instead started a new one. + // We should be all done, but let's just make sure our activity + // is still at the top and schedule another run if something + // weird happened. + ActivityRecord nextNext = topRunningActivityLocked(null); + if (DEBUG_SWITCH) Slog.i(TAG, + "Activity config changed during resume: " + next + + ", new next: " + nextNext); + if (nextNext != next) { + // Do over! + mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); + } + if (mMainStack) { + mService.setFocusedActivityLocked(next); + } + ensureActivitiesVisibleLocked(null, 0); + mService.mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); + return true; + } + + try { + // Deliver all pending results. + ArrayList a = next.results; + if (a != null) { + final int N = a.size(); + if (!next.finishing && N > 0) { + if (DEBUG_RESULTS) Slog.v( + TAG, "Delivering results to " + next + + ": " + a); + next.app.thread.scheduleSendResult(next, a); + } + } + + if (next.newIntents != null) { + next.app.thread.scheduleNewIntent(next.newIntents, next); + } + + EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, + System.identityHashCode(next), + next.task.taskId, next.shortComponentName); + + next.app.thread.scheduleResumeActivity(next, + mService.isNextTransitionForward()); + + pauseIfSleepingLocked(); + + } catch (Exception e) { + // Whoops, need to restart this activity! + next.state = lastState; + mResumedActivity = lastResumedActivity; + Slog.i(TAG, "Restarting because process died: " + next); + if (!next.hasBeenLaunched) { + next.hasBeenLaunched = true; + } else { + if (SHOW_APP_STARTING_PREVIEW && mMainStack) { + mService.mWindowManager.setAppStartingWindow( + next, next.packageName, next.theme, + next.nonLocalizedLabel, + next.labelRes, next.icon, null, true); + } + } + startSpecificActivityLocked(next, true, false); + return true; + } + + // From this point on, if something goes wrong there is no way + // to recover the activity. + try { + next.visible = true; + completeResumeLocked(next); + } catch (Exception e) { + // If any exception gets thrown, toss away this + // activity and try the next one. + Slog.w(TAG, "Exception thrown during resume of " + next, e); + requestFinishActivityLocked(next, Activity.RESULT_CANCELED, null, + "resume-exception"); + return true; + } + + // Didn't need to use the icicle, and it is now out of date. + next.icicle = null; + next.haveState = false; + next.stopped = false; + + } else { + // Whoops, need to restart this activity! + if (!next.hasBeenLaunched) { + next.hasBeenLaunched = true; + } else { + if (SHOW_APP_STARTING_PREVIEW) { + mService.mWindowManager.setAppStartingWindow( + next, next.packageName, next.theme, + next.nonLocalizedLabel, + next.labelRes, next.icon, null, true); + } + if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); + } + startSpecificActivityLocked(next, true, true); + } + + return true; + } + + private final void startActivityLocked(ActivityRecord r, boolean newTask, + boolean doResume) { + final int NH = mHistory.size(); + + int addPos = -1; + + if (!newTask) { + // If starting in an existing task, find where that is... + ActivityRecord next = null; + boolean startIt = true; + for (int i = NH-1; i >= 0; i--) { + ActivityRecord p = (ActivityRecord)mHistory.get(i); + if (p.finishing) { + continue; + } + if (p.task == r.task) { + // Here it is! Now, if this is not yet visible to the + // user, then just add it without starting; it will + // get started when the user navigates back to it. + addPos = i+1; + if (!startIt) { + mHistory.add(addPos, r); + r.inHistory = true; + r.task.numActivities++; + mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, + r.info.screenOrientation, r.fullscreen); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + return; + } + break; + } + if (p.fullscreen) { + startIt = false; + } + next = p; + } + } + + // Place a new activity at top of stack, so it is next to interact + // with the user. + if (addPos < 0) { + addPos = mHistory.size(); + } + + // If we are not placing the new activity frontmost, we do not want + // to deliver the onUserLeaving callback to the actual frontmost + // activity + if (addPos < NH) { + mUserLeaving = false; + if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() behind front, mUserLeaving=false"); + } + + // Slot the activity into the history stack and proceed + mHistory.add(addPos, r); + r.inHistory = true; + r.frontOfTask = newTask; + r.task.numActivities++; + if (NH > 0) { + // We want to show the starting preview window if we are + // switching to a new task, or the next activity's process is + // not currently running. + boolean showStartingIcon = newTask; + ProcessRecord proc = r.app; + if (proc == null) { + proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid); + } + if (proc == null || proc.thread == null) { + showStartingIcon = true; + } + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare open transition: starting " + r); + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mNoAnimActivities.add(r); + } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + mService.mWindowManager.prepareAppTransition( + WindowManagerPolicy.TRANSIT_TASK_OPEN); + mNoAnimActivities.remove(r); + } else { + mService.mWindowManager.prepareAppTransition(newTask + ? WindowManagerPolicy.TRANSIT_TASK_OPEN + : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + mNoAnimActivities.remove(r); + } + mService.mWindowManager.addAppToken( + addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); + boolean doShow = true; + if (newTask) { + // Even though this activity is starting fresh, we still need + // to reset it to make sure we apply affinities to move any + // existing activities from other tasks in to it. + // If the caller has requested that the target task be + // reset, then do so. + if ((r.intent.getFlags() + &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + resetTaskIfNeededLocked(r, r); + doShow = topRunningNonDelayedActivityLocked(null) == r; + } + } + if (SHOW_APP_STARTING_PREVIEW && doShow) { + // Figure out if we are transitioning from another activity that is + // "has the same starting icon" as the next one. This allows the + // window manager to keep the previous window it had previously + // created, if it still had one. + ActivityRecord prev = mResumedActivity; + if (prev != null) { + // We don't want to reuse the previous starting preview if: + // (1) The current activity is in a different task. + if (prev.task != r.task) prev = null; + // (2) The current activity is already displayed. + else if (prev.nowVisible) prev = null; + } + mService.mWindowManager.setAppStartingWindow( + r, r.packageName, r.theme, r.nonLocalizedLabel, + r.labelRes, r.icon, prev, showStartingIcon); + } + } else { + // If this is the first activity, don't do any fancy animations, + // because there is nothing for it to animate on top of. + mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, + r.info.screenOrientation, r.fullscreen); + } + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + + if (doResume) { + resumeTopActivityLocked(null); + } + } + + /** + * Perform a reset of the given task, if needed as part of launching it. + * Returns the new HistoryRecord at the top of the task. + */ + private final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, + ActivityRecord newActivity) { + boolean forceReset = (newActivity.info.flags + &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; + if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if ((newActivity.info.flags + &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { + forceReset = true; + } + } + + final TaskRecord task = taskTop.task; + + // We are going to move through the history list so that we can look + // at each activity 'target' with 'below' either the interesting + // activity immediately below it in the stack or null. + ActivityRecord target = null; + int targetI = 0; + int taskTopI = -1; + int replyChainEnd = -1; + int lastReparentPos = -1; + for (int i=mHistory.size()-1; i>=-1; i--) { + ActivityRecord below = i >= 0 ? (ActivityRecord)mHistory.get(i) : null; + + if (below != null && below.finishing) { + continue; + } + if (target == null) { + target = below; + targetI = i; + // If we were in the middle of a reply chain before this + // task, it doesn't appear like the root of the chain wants + // anything interesting, so drop it. + replyChainEnd = -1; + continue; + } + + final int flags = target.info.flags; + + final boolean finishOnTaskLaunch = + (flags&ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; + final boolean allowTaskReparenting = + (flags&ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; + + if (target.task == task) { + // We are inside of the task being reset... we'll either + // finish this activity, push it out for another task, + // or leave it as-is. We only do this + // for activities that are not the root of the task (since + // if we finish the root, we may no longer have the task!). + if (taskTopI < 0) { + taskTopI = targetI; + } + if (below != null && below.task == task) { + final boolean clearWhenTaskReset = + (target.intent.getFlags() + &Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0; + if (!finishOnTaskLaunch && !clearWhenTaskReset && target.resultTo != null) { + // If this activity is sending a reply to a previous + // activity, we can't do anything with it now until + // we reach the start of the reply chain. + // XXX note that we are assuming the result is always + // to the previous activity, which is almost always + // the case but we really shouldn't count on. + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + } else if (!finishOnTaskLaunch && !clearWhenTaskReset && allowTaskReparenting + && target.taskAffinity != null + && !target.taskAffinity.equals(task.affinity)) { + // If this activity has an affinity for another + // task, then we need to move it out of here. We will + // move it as far out of the way as possible, to the + // bottom of the activity stack. This also keeps it + // correctly ordered with any activities we previously + // moved. + ActivityRecord p = (ActivityRecord)mHistory.get(0); + if (target.taskAffinity != null + && target.taskAffinity.equals(p.task.affinity)) { + // If the activity currently at the bottom has the + // same task affinity as the one we are moving, + // then merge it into the same task. + target.task = p.task; + if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + + " out to bottom task " + p.task); + } else { + mService.mCurTask++; + if (mService.mCurTask <= 0) { + mService.mCurTask = 1; + } + target.task = new TaskRecord(mService.mCurTask, target.info, null, + (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + target.task.affinityIntent = target.intent; + if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + + " out to new task " + target.task); + } + mService.mWindowManager.setAppGroupId(target, task.taskId); + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + int dstPos = 0; + for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { + p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + + " out to target's task " + target.task); + task.numActivities--; + p.task = target.task; + target.task.numActivities++; + mHistory.remove(srcPos); + mHistory.add(dstPos, p); + mService.mWindowManager.moveAppToken(dstPos, p); + mService.mWindowManager.setAppGroupId(p, p.task.taskId); + dstPos++; + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + i++; + } + if (taskTop == p) { + taskTop = below; + } + if (taskTopI == replyChainEnd) { + taskTopI = -1; + } + replyChainEnd = -1; + if (mMainStack) { + mService.addRecentTaskLocked(target.task); + } + } else if (forceReset || finishOnTaskLaunch + || clearWhenTaskReset) { + // If the activity should just be removed -- either + // because it asks for it, or the task should be + // cleared -- then finish it and anything that is + // part of its reply chain. + if (clearWhenTaskReset) { + // In this case, we want to finish this activity + // and everything above it, so be sneaky and pretend + // like these are all in the reply chain. + replyChainEnd = targetI+1; + while (replyChainEnd < mHistory.size() && + ((ActivityRecord)mHistory.get( + replyChainEnd)).task == task) { + replyChainEnd++; + } + replyChainEnd--; + } else if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + ActivityRecord p = null; + for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { + p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (finishActivityLocked(p, srcPos, + Activity.RESULT_CANCELED, null, "reset")) { + replyChainEnd--; + srcPos--; + } + } + if (taskTop == p) { + taskTop = below; + } + if (taskTopI == replyChainEnd) { + taskTopI = -1; + } + replyChainEnd = -1; + } else { + // If we were in the middle of a chain, well the + // activity that started it all doesn't want anything + // special, so leave it all as-is. + replyChainEnd = -1; + } + } else { + // Reached the bottom of the task -- any reply chain + // should be left as-is. + replyChainEnd = -1; + } + + } else if (target.resultTo != null) { + // If this activity is sending a reply to a previous + // activity, we can't do anything with it now until + // we reach the start of the reply chain. + // XXX note that we are assuming the result is always + // to the previous activity, which is almost always + // the case but we really shouldn't count on. + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + + } else if (taskTopI >= 0 && allowTaskReparenting + && task.affinity != null + && task.affinity.equals(target.taskAffinity)) { + // We are inside of another task... if this activity has + // an affinity for our task, then either remove it if we are + // clearing or move it over to our task. Note that + // we currently punt on the case where we are resetting a + // task that is not at the top but who has activities above + // with an affinity to it... this is really not a normal + // case, and we will need to later pull that task to the front + // and usually at that point we will do the reset and pick + // up those remaining activities. (This only happens if + // someone starts an activity in a new task from an activity + // in a task that is not currently on top.) + if (forceReset || finishOnTaskLaunch) { + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + ActivityRecord p = null; + for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { + p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (finishActivityLocked(p, srcPos, + Activity.RESULT_CANCELED, null, "reset")) { + taskTopI--; + lastReparentPos--; + replyChainEnd--; + srcPos--; + } + } + replyChainEnd = -1; + } else { + if (replyChainEnd < 0) { + replyChainEnd = targetI; + } + for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { + ActivityRecord p = (ActivityRecord)mHistory.get(srcPos); + if (p.finishing) { + continue; + } + if (lastReparentPos < 0) { + lastReparentPos = taskTopI; + taskTop = p; + } else { + lastReparentPos--; + } + mHistory.remove(srcPos); + p.task.numActivities--; + p.task = task; + mHistory.add(lastReparentPos, p); + if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + + " in to resetting task " + task); + task.numActivities++; + mService.mWindowManager.moveAppToken(lastReparentPos, p); + mService.mWindowManager.setAppGroupId(p, p.task.taskId); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + } + replyChainEnd = -1; + + // Now we've moved it in to place... but what if this is + // a singleTop activity and we have put it on top of another + // instance of the same activity? Then we drop the instance + // below so it remains singleTop. + if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { + for (int j=lastReparentPos-1; j>=0; j--) { + ActivityRecord p = (ActivityRecord)mHistory.get(j); + if (p.finishing) { + continue; + } + if (p.intent.getComponent().equals(target.intent.getComponent())) { + if (finishActivityLocked(p, j, + Activity.RESULT_CANCELED, null, "replace")) { + taskTopI--; + lastReparentPos--; + } + } + } + } + } + } + + target = below; + targetI = i; + } + + return taskTop; + } + + /** + * Perform clear operation as requested by + * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the + * stack to the given task, then look for + * an instance of that activity in the stack and, if found, finish all + * activities on top of it and return the instance. + * + * @param newR Description of the new activity being started. + * @return Returns the old activity that should be continue to be used, + * or null if none was found. + */ + private final ActivityRecord performClearTaskLocked(int taskId, + ActivityRecord newR, int launchFlags, boolean doClear) { + int i = mHistory.size(); + + // First find the requested task. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId == taskId) { + i++; + break; + } + } + + // Now clear it. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.finishing) { + continue; + } + if (r.task.taskId != taskId) { + return null; + } + if (r.realActivity.equals(newR.realActivity)) { + // Here it is! Now finish everything in front... + ActivityRecord ret = r; + if (doClear) { + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; + } + } + } + + // Finally, if this is a normal launch mode (that is, not + // expecting onNewIntent()), then we will finish the current + // instance of the activity so a new fresh one can be started. + if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE + && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { + if (!ret.finishing) { + int index = indexOfTokenLocked(ret); + if (index >= 0) { + finishActivityLocked(ret, index, Activity.RESULT_CANCELED, + null, "clear"); + } + return null; + } + } + + return ret; + } + } + + return null; + } + + /** + * Find the activity in the history stack within the given task. Returns + * the index within the history at which it's found, or < 0 if not found. + */ + private final int findActivityInHistoryLocked(ActivityRecord r, int task) { + int i = mHistory.size(); + while (i > 0) { + i--; + ActivityRecord candidate = (ActivityRecord)mHistory.get(i); + if (candidate.task.taskId != task) { + break; + } + if (candidate.realActivity.equals(r.realActivity)) { + return i; + } + } + + return -1; + } + + /** + * Reorder the history stack so that the activity at the given index is + * brought to the front. + */ + private final ActivityRecord moveActivityToFrontLocked(int where) { + ActivityRecord newTop = (ActivityRecord)mHistory.remove(where); + int top = mHistory.size(); + ActivityRecord oldTop = (ActivityRecord)mHistory.get(top-1); + mHistory.add(top, newTop); + oldTop.frontOfTask = false; + newTop.frontOfTask = true; + return newTop; + } + + final int startActivityLocked(IApplicationThread caller, + Intent intent, String resolvedType, + Uri[] grantedUriPermissions, + int grantedMode, ActivityInfo aInfo, IBinder resultTo, + String resultWho, int requestCode, + int callingPid, int callingUid, boolean onlyIfNeeded, + boolean componentSpecified) { + Slog.i(TAG, "Starting activity: " + intent); + + ActivityRecord sourceRecord = null; + ActivityRecord resultRecord = null; + if (resultTo != null) { + int index = indexOfTokenLocked(resultTo); + if (DEBUG_RESULTS) Slog.v( + TAG, "Sending result to " + resultTo + " (index " + index + ")"); + if (index >= 0) { + sourceRecord = (ActivityRecord)mHistory.get(index); + if (requestCode >= 0 && !sourceRecord.finishing) { + resultRecord = sourceRecord; + } + } + } + + int launchFlags = intent.getFlags(); + + if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 + && sourceRecord != null) { + // Transfer the result target from the source activity to the new + // one being started, including any failures. + if (requestCode >= 0) { + return START_FORWARD_AND_REQUEST_CONFLICT; + } + resultRecord = sourceRecord.resultTo; + resultWho = sourceRecord.resultWho; + requestCode = sourceRecord.requestCode; + sourceRecord.resultTo = null; + if (resultRecord != null) { + resultRecord.removeResultsLocked( + sourceRecord, resultWho, requestCode); + } + } + + int err = START_SUCCESS; + + if (intent.getComponent() == null) { + // We couldn't find a class that can handle the given Intent. + // That's the end of that! + err = START_INTENT_NOT_RESOLVED; + } + + if (err == START_SUCCESS && aInfo == null) { + // We couldn't find the specific class specified in the Intent. + // Also the end of the line. + err = START_CLASS_NOT_FOUND; + } + + ProcessRecord callerApp = null; + if (err == START_SUCCESS && caller != null) { + callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + callingPid = callerApp.pid; + callingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = START_PERMISSION_DENIED; + } + } + + if (err != START_SUCCESS) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + return err; + } + + final int perm = mService.checkComponentPermission(aInfo.permission, callingPid, + callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid); + if (perm != PackageManager.PERMISSION_GRANTED) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + String msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " requires " + aInfo.permission; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + if (mMainStack) { + if (mService.mController != null) { + boolean abort = false; + try { + // The Intent we give to the watcher has the extra data + // stripped off, since it can contain private information. + Intent watchIntent = intent.cloneFilter(); + abort = !mService.mController.activityStarting(watchIntent, + aInfo.applicationInfo.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + + if (abort) { + if (resultRecord != null) { + sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + return START_SUCCESS; + } + } + } + + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, + intent, resolvedType, aInfo, mService.mConfiguration, + resultRecord, resultWho, requestCode, componentSpecified); + + if (mMainStack) { + if (mResumedActivity == null + || mResumedActivity.info.applicationInfo.uid != callingUid) { + if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { + PendingActivityLaunch pal = new PendingActivityLaunch(); + pal.r = r; + pal.sourceRecord = sourceRecord; + pal.grantedUriPermissions = grantedUriPermissions; + pal.grantedMode = grantedMode; + pal.onlyIfNeeded = onlyIfNeeded; + mService.mPendingActivityLaunches.add(pal); + return START_SWITCHES_CANCELED; + } + } + + if (mService.mDidAppSwitch) { + // This is the second allowed switch since we stopped switches, + // so now just generally allow switches. Use case: user presses + // home (switches disabled, switch to home, mDidAppSwitch now true); + // user taps a home icon (coming from home so allowed, we hit here + // and now allow anyone to switch again). + mService.mAppSwitchesAllowedTime = 0; + } else { + mService.mDidAppSwitch = true; + } + + mService.doPendingActivityLaunchesLocked(false); + } + + return startActivityUncheckedLocked(r, sourceRecord, + grantedUriPermissions, grantedMode, onlyIfNeeded, true); + } + + final int startActivityUncheckedLocked(ActivityRecord r, + ActivityRecord sourceRecord, Uri[] grantedUriPermissions, + int grantedMode, boolean onlyIfNeeded, boolean doResume) { + final Intent intent = r.intent; + final int callingUid = r.launchedFromUid; + + int launchFlags = intent.getFlags(); + + // We'll invoke onUserLeaving before onPause only if the launching + // activity did not explicitly state that this is an automated launch. + mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + if (DEBUG_USER_LEAVING) Slog.v(TAG, + "startActivity() => mUserLeaving=" + mUserLeaving); + + // If the caller has asked not to resume at this point, we make note + // of this in the record so that we can skip it when trying to find + // the top running activity. + if (!doResume) { + r.delayedResume = true; + } + + ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) + != 0 ? r : null; + + // If the onlyIfNeeded flag is set, then we can do this if the activity + // being launched is the same as the one making the call... or, as + // a special case, if we do not know the caller then we count the + // current top activity as the caller. + if (onlyIfNeeded) { + ActivityRecord checkedCaller = sourceRecord; + if (checkedCaller == null) { + checkedCaller = topRunningNonDelayedActivityLocked(notTop); + } + if (!checkedCaller.realActivity.equals(r.realActivity)) { + // Caller is not the same as launcher, so always needed. + onlyIfNeeded = false; + } + } + + if (grantedUriPermissions != null && callingUid > 0) { + for (int i=0; i<grantedUriPermissions.length; i++) { + mService.grantUriPermissionLocked(callingUid, r.packageName, + grantedUriPermissions[i], grantedMode, r); + } + } + + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + intent, r); + + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: " + + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + + if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + // For whatever reason this activity is being launched into a new + // task... yet the caller has requested a result back. Well, that + // is pretty messed up, so instead immediately send back a cancel + // and let the new task continue launched as normal without a + // dependency on its originator. + Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); + sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + r.resultTo = null; + } + + boolean addingToTask = false; + if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && + (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // If bring to front is requested, and no result is requested, and + // we can find a task that was started with this same + // component, then instead of launching bring that one to the front. + if (r.resultTo == null) { + // See if there is a task to bring to the front. If this is + // a SINGLE_INSTANCE activity, there can be one and only one + // instance of it in the history, and it is always in its own + // unique task, so we do a special search. + ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE + ? findTaskLocked(intent, r.info) + : findActivityLocked(intent, r.info); + if (taskTop != null) { + if (taskTop.task.intent == null) { + // This task was started because of movement of + // the activity based on affinity... now that we + // are actually launching it, we can assign the + // base intent. + taskTop.task.setIntent(intent, r.info); + } + // If the target task is not in the front, then we need + // to bring it to the front... except... well, with + // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like + // to have the same behavior as if a new instance was + // being started, which means not bringing it to the front + // if the caller is not itself in the front. + ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop); + if (curTop.task != taskTop.task) { + r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + boolean callerAtFront = sourceRecord == null + || curTop.task == sourceRecord.task; + if (callerAtFront) { + // We really do want to push this one into the + // user's face, right now. + moveTaskToFrontLocked(taskTop.task, r); + } + } + // If the caller has requested that the target task be + // reset, then do so. + if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + taskTop = resetTaskIfNeededLocked(taskTop, r); + } + if (onlyIfNeeded) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + return START_RETURN_INTENT_TO_CALLER; + } + if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // In this situation we want to remove all activities + // from the task up to the one being started. In most + // cases this means we are resetting the task to its + // initial state. + ActivityRecord top = performClearTaskLocked( + taskTop.task.taskId, r, launchFlags, true); + if (top != null) { + if (top.frontOfTask) { + // Activity aliases may mean we use different + // intents for the top activity, so make sure + // the task now has the identity of the new + // intent. + top.task.setIntent(r.intent, r.info); + } + logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(r.intent); + } else { + // A special case: we need to + // start the activity because it is not currently + // running, and the caller has asked to clear the + // current task to have this activity at the top. + addingToTask = true; + // Now pretend like this activity is being started + // by the top of its task, so it is put in the + // right place. + sourceRecord = taskTop; + } + } else if (r.realActivity.equals(taskTop.task.realActivity)) { + // In this case the top activity on the task is the + // same as the one being launched, so we take that + // as a request to bring the task to the foreground. + // If the top activity in the task is the root + // activity, deliver this new intent to it if it + // desires. + if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + && taskTop.realActivity.equals(r.realActivity)) { + logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task); + if (taskTop.frontOfTask) { + taskTop.task.setIntent(r.intent, r.info); + } + taskTop.deliverNewIntentLocked(r.intent); + } else if (!r.intent.filterEquals(taskTop.task.intent)) { + // In this case we are launching the root activity + // of the task, but with a different intent. We + // should start a new instance on top. + addingToTask = true; + sourceRecord = taskTop; + } + } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + // In this case an activity is being launched in to an + // existing task, without resetting that task. This + // is typically the situation of launching an activity + // from a notification or shortcut. We want to place + // the new activity on top of the current task. + addingToTask = true; + sourceRecord = taskTop; + } else if (!taskTop.task.rootWasReset) { + // In this case we are launching in to an existing task + // that has not yet been started from its front door. + // The current task has been brought to the front. + // Ideally, we'd probably like to place this new task + // at the bottom of its stack, but that's a little hard + // to do with the current organization of the code so + // for now we'll just drop it. + taskTop.task.setIntent(r.intent, r.info); + } + if (!addingToTask) { + // We didn't do anything... but it was needed (a.k.a., client + // don't use that intent!) And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + return START_TASK_TO_FRONT; + } + } + } + } + + //String uri = r.intent.toURI(); + //Intent intent2 = new Intent(uri); + //Slog.i(TAG, "Given intent: " + r.intent); + //Slog.i(TAG, "URI is: " + uri); + //Slog.i(TAG, "To intent: " + intent2); + + if (r.packageName != null) { + // If the activity being launched is the same as the one currently + // at the top, then we need to check if it should only be launched + // once. + ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); + if (top != null && r.resultTo == null) { + if (top.realActivity.equals(r.realActivity)) { + if (top.app != null && top.app.thread != null) { + if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); + // For paranoia, make sure we have correctly + // resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + if (onlyIfNeeded) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + return START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(r.intent); + return START_DELIVERED_TO_TOP; + } + } + } + } + + } else { + if (r.resultTo != null) { + sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + } + return START_CLASS_NOT_FOUND; + } + + boolean newTask = false; + + // Should this be considered a new task? + if (r.resultTo == null && !addingToTask + && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + // todo: should do better management of integers. + mService.mCurTask++; + if (mService.mCurTask <= 0) { + mService.mCurTask = 1; + } + r.task = new TaskRecord(mService.mCurTask, r.info, intent, + (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new task " + r.task); + newTask = true; + if (mMainStack) { + mService.addRecentTaskLocked(r.task); + } + + } else if (sourceRecord != null) { + if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + // In this case, we are adding the activity to an existing + // task, but the caller has asked to clear that task if the + // activity is already running. + ActivityRecord top = performClearTaskLocked( + sourceRecord.task.taskId, r, launchFlags, true); + if (top != null) { + logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(r.intent); + // For paranoia, make sure we have correctly + // resumed the top activity. + if (doResume) { + resumeTopActivityLocked(null); + } + return START_DELIVERED_TO_TOP; + } + } else if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { + // In this case, we are launching an activity in our own task + // that may already be running somewhere in the history, and + // we want to shuffle it to the front of the stack if so. + int where = findActivityInHistoryLocked(r, sourceRecord.task.taskId); + if (where >= 0) { + ActivityRecord top = moveActivityToFrontLocked(where); + logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(r.intent); + if (doResume) { + resumeTopActivityLocked(null); + } + return START_DELIVERED_TO_TOP; + } + } + // An existing activity is starting this new activity, so we want + // to keep the new one in the same task as the one that is starting + // it. + r.task = sourceRecord.task; + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in existing task " + r.task); + + } else { + // This not being started from an existing activity, and not part + // of a new task... just put it in the top task, though these days + // this case should never happen. + final int N = mHistory.size(); + ActivityRecord prev = + N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; + r.task = prev != null + ? prev.task + : new TaskRecord(mService.mCurTask, r.info, intent, + (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new guessed " + r.task); + } + if (newTask) { + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); + } + logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); + startActivityLocked(r, newTask, doResume); + return START_SUCCESS; + } + + final int startActivityMayWait(IApplicationThread caller, + Intent intent, String resolvedType, Uri[] grantedUriPermissions, + int grantedMode, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded, + boolean debug, WaitResult outResult, Configuration config) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, resolvedType, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS); + aInfo = rInfo != null ? rInfo.activityInfo : null; + } catch (RemoteException e) { + aInfo = null; + } + + if (aInfo != null) { + // Store the found target back into the intent, because now that + // we have it we never want to do this again. For example, if the + // user navigates back to this point in the history, we should + // always restart the exact same activity. + intent.setComponent(new ComponentName( + aInfo.applicationInfo.packageName, aInfo.name)); + + // Don't debug things in the system process + if (debug) { + if (!aInfo.processName.equals("system")) { + mService.setDebugApp(aInfo.processName, true, false); + } + } + } + + synchronized (mService) { + int callingPid; + int callingUid; + if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + + mConfigWillChange = config != null + && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Starting activity when config will change = " + mConfigWillChange); + + final long origId = Binder.clearCallingIdentity(); + + if (mMainStack && aInfo != null && + (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_HEAVY_WEIGHT) != 0) { + // This may be a heavy-weight process! Check to see if we already + // have another, different heavy-weight process running. + if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { + if (mService.mHeavyWeightProcess != null && + (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || + !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { + int realCallingPid = callingPid; + int realCallingUid = callingUid; + if (caller != null) { + ProcessRecord callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + realCallingPid = callerApp.pid; + realCallingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + realCallingPid + ") when starting: " + + intent.toString()); + return START_PERMISSION_DENIED; + } + } + + IIntentSender target = mService.getIntentSenderLocked( + IActivityManager.INTENT_SENDER_ACTIVITY, "android", + realCallingUid, null, null, 0, intent, + resolvedType, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT); + + Intent newIntent = new Intent(); + if (requestCode >= 0) { + // Caller is requesting a result. + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, + new IntentSender(target)); + if (mService.mHeavyWeightProcess.activities.size() > 0) { + ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, + hist.packageName); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, + hist.task.taskId); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, + aInfo.packageName); + newIntent.setFlags(intent.getFlags()); + newIntent.setClassName("android", + HeavyWeightSwitcherActivity.class.getName()); + intent = newIntent; + resolvedType = null; + caller = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); + componentSpecified = true; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, null, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS); + aInfo = rInfo != null ? rInfo.activityInfo : null; + } catch (RemoteException e) { + aInfo = null; + } + } + } + } + + int res = startActivityLocked(caller, intent, resolvedType, + grantedUriPermissions, grantedMode, aInfo, + resultTo, resultWho, requestCode, callingPid, callingUid, + onlyIfNeeded, componentSpecified); + + if (mConfigWillChange && mMainStack) { + // If the caller also wants to switch to a new configuration, + // do so now. This allows a clean switch, as we are waiting + // for the current activity to pause (so we will not destroy + // it), and have not yet started the next activity. + mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, + "updateConfiguration()"); + mConfigWillChange = false; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Updating to new configuration after starting activity."); + mService.updateConfigurationLocked(config, null); + } + + Binder.restoreCallingIdentity(origId); + + if (outResult != null) { + outResult.result = res; + if (res == IActivityManager.START_SUCCESS) { + mWaitingActivityLaunched.add(outResult); + do { + try { + wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } else if (res == IActivityManager.START_TASK_TO_FRONT) { + ActivityRecord r = this.topRunningActivityLocked(null); + if (r.nowVisible) { + outResult.timeout = false; + outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.totalTime = 0; + outResult.thisTime = 0; + } else { + outResult.thisTime = SystemClock.uptimeMillis(); + mWaitingActivityVisible.add(outResult); + do { + try { + wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } + } + } + + return res; + } + } + + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, + long thisTime, long totalTime) { + for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { + WaitResult w = mWaitingActivityLaunched.get(i); + w.timeout = timeout; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.thisTime = thisTime; + w.totalTime = totalTime; + } + mService.notifyAll(); + } + + void reportActivityVisibleLocked(ActivityRecord r) { + for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) { + WaitResult w = mWaitingActivityVisible.get(i); + w.timeout = false; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.totalTime = SystemClock.uptimeMillis() - w.thisTime; + w.thisTime = w.totalTime; + } + mService.notifyAll(); + } + + void sendActivityResultLocked(int callingUid, ActivityRecord r, + String resultWho, int requestCode, int resultCode, Intent data) { + + if (callingUid > 0) { + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + data, r); + } + + if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r + + " : who=" + resultWho + " req=" + requestCode + + " res=" + resultCode + " data=" + data); + if (mResumedActivity == r && r.app != null && r.app.thread != null) { + try { + ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); + list.add(new ResultInfo(resultWho, requestCode, + resultCode, data)); + r.app.thread.scheduleSendResult(r, list); + return; + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending result to " + r, e); + } + } + + r.addResultLocked(null, resultWho, requestCode, resultCode, data); + } + + private final void stopActivityLocked(ActivityRecord r) { + if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 + || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { + if (!r.finishing) { + requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null, + "no-history"); + } + } else if (r.app != null && r.app.thread != null) { + if (mMainStack) { + if (mService.mFocusedActivity == r) { + mService.setFocusedActivityLocked(topRunningActivityLocked(null)); + } + } + r.resumeKeyDispatchingLocked(); + try { + r.stopped = false; + r.state = ActivityState.STOPPING; + if (DEBUG_VISBILITY) Slog.v( + TAG, "Stopping visible=" + r.visible + " for " + r); + if (!r.visible) { + mService.mWindowManager.setAppVisibility(r, false); + } + r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags); + } catch (Exception e) { + // Maybe just ignore exceptions here... if the process + // has crashed, our death notification will clean things + // up. + Slog.w(TAG, "Exception thrown during pause", e); + // Just in case, assume it to be stopped. + r.stopped = true; + r.state = ActivityState.STOPPED; + if (r.configDestroy) { + destroyActivityLocked(r, true); + } + } + } + } + + final ArrayList<ActivityRecord> processStoppingActivitiesLocked( + boolean remove) { + int N = mStoppingActivities.size(); + if (N <= 0) return null; + + ArrayList<ActivityRecord> stops = null; + + final boolean nowVisible = mResumedActivity != null + && mResumedActivity.nowVisible + && !mResumedActivity.waitingVisible; + for (int i=0; i<N; i++) { + ActivityRecord s = mStoppingActivities.get(i); + if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + + nowVisible + " waitingVisible=" + s.waitingVisible + + " finishing=" + s.finishing); + if (s.waitingVisible && nowVisible) { + mWaitingVisibleActivities.remove(s); + s.waitingVisible = false; + if (s.finishing) { + // If this activity is finishing, it is sitting on top of + // everyone else but we now know it is no longer needed... + // so get rid of it. Otherwise, we need to go through the + // normal flow and hide it once we determine that it is + // hidden by the activities in front of it. + if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); + mService.mWindowManager.setAppVisibility(s, false); + } + } + if (!s.waitingVisible && remove) { + if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); + if (stops == null) { + stops = new ArrayList<ActivityRecord>(); + } + stops.add(s); + mStoppingActivities.remove(i); + N--; + i--; + } + } + + return stops; + } + + final void activityIdleInternal(IBinder token, boolean fromTimeout, + Configuration config) { + if (localLOGV) Slog.v(TAG, "Activity idle: " + token); + + ArrayList<ActivityRecord> stops = null; + ArrayList<ActivityRecord> finishes = null; + ArrayList<ActivityRecord> thumbnails = null; + int NS = 0; + int NF = 0; + int NT = 0; + IApplicationThread sendThumbnail = null; + boolean booting = false; + boolean enableScreen = false; + + synchronized (mService) { + if (token != null) { + mHandler.removeMessages(IDLE_TIMEOUT_MSG, token); + } + + // Get the activity record. + int index = indexOfTokenLocked(token); + if (index >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(index); + + if (fromTimeout) { + reportActivityLaunchedLocked(fromTimeout, r, -1, -1); + } + + // This is a hack to semi-deal with a race condition + // in the client where it can be constructed with a + // newer configuration from when we asked it to launch. + // We'll update with whatever configuration it now says + // it used to launch. + if (config != null) { + r.configuration = config; + } + + // No longer need to keep the device awake. + if (mResumedActivity == r && mLaunchingActivity.isHeld()) { + mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + mLaunchingActivity.release(); + } + + // We are now idle. If someone is waiting for a thumbnail from + // us, we can now deliver. + r.idle = true; + mService.scheduleAppGcsLocked(); + if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { + sendThumbnail = r.app.thread; + r.thumbnailNeeded = false; + } + + // If this activity is fullscreen, set up to hide those under it. + + if (DEBUG_VISBILITY) Slog.v(TAG, "Idle activity for " + r); + ensureActivitiesVisibleLocked(null, 0); + + //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); + if (mMainStack) { + if (!mService.mBooted && !fromTimeout) { + mService.mBooted = true; + enableScreen = true; + } + } + + } else if (fromTimeout) { + reportActivityLaunchedLocked(fromTimeout, null, -1, -1); + } + + // Atomically retrieve all of the other things to do. + stops = processStoppingActivitiesLocked(true); + NS = stops != null ? stops.size() : 0; + if ((NF=mFinishingActivities.size()) > 0) { + finishes = new ArrayList<ActivityRecord>(mFinishingActivities); + mFinishingActivities.clear(); + } + if ((NT=mService.mCancelledThumbnails.size()) > 0) { + thumbnails = new ArrayList<ActivityRecord>(mService.mCancelledThumbnails); + mService.mCancelledThumbnails.clear(); + } + + if (mMainStack) { + booting = mService.mBooting; + mService.mBooting = false; + } + } + + int i; + + // Send thumbnail if requested. + if (sendThumbnail != null) { + try { + sendThumbnail.requestThumbnail(token); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown when requesting thumbnail", e); + mService.sendPendingThumbnail(null, token, null, null, true); + } + } + + // Stop any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (i=0; i<NS; i++) { + ActivityRecord r = (ActivityRecord)stops.get(i); + synchronized (mService) { + if (r.finishing) { + finishCurrentActivityLocked(r, FINISH_IMMEDIATELY); + } else { + stopActivityLocked(r); + } + } + } + + // Finish any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (i=0; i<NF; i++) { + ActivityRecord r = (ActivityRecord)finishes.get(i); + synchronized (mService) { + destroyActivityLocked(r, true); + } + } + + // Report back to any thumbnail receivers. + for (i=0; i<NT; i++) { + ActivityRecord r = (ActivityRecord)thumbnails.get(i); + mService.sendPendingThumbnail(r, null, null, null, true); + } + + if (booting) { + mService.finishBooting(); + } + + mService.trimApplications(); + //dump(); + //mWindowManager.dump(); + + if (enableScreen) { + mService.enableScreenAfterBoot(); + } + } + + /** + * @return Returns true if the activity is being finished, false if for + * some reason it is being left as-is. + */ + final boolean requestFinishActivityLocked(IBinder token, int resultCode, + Intent resultData, String reason) { + if (DEBUG_RESULTS) Slog.v( + TAG, "Finishing activity: token=" + token + + ", result=" + resultCode + ", data=" + resultData); + + int index = indexOfTokenLocked(token); + if (index < 0) { + return false; + } + ActivityRecord r = (ActivityRecord)mHistory.get(index); + + // Is this the last activity left? + boolean lastActivity = true; + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord p = (ActivityRecord)mHistory.get(i); + if (!p.finishing && p != r) { + lastActivity = false; + break; + } + } + + // If this is the last activity, but it is the home activity, then + // just don't finish it. + if (lastActivity) { + if (r.intent.hasCategory(Intent.CATEGORY_HOME)) { + return false; + } + } + + finishActivityLocked(r, index, resultCode, resultData, reason); + return true; + } + + /** + * @return Returns true if this activity has been removed from the history + * list, or false if it is still in the list and will be removed later. + */ + final boolean finishActivityLocked(ActivityRecord r, int index, + int resultCode, Intent resultData, String reason) { + if (r.finishing) { + Slog.w(TAG, "Duplicate finish request for " + r); + return false; + } + + r.finishing = true; + EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, + System.identityHashCode(r), + r.task.taskId, r.shortComponentName, reason); + r.task.numActivities--; + if (index < (mHistory.size()-1)) { + ActivityRecord next = (ActivityRecord)mHistory.get(index+1); + if (next.task == r.task) { + if (r.frontOfTask) { + // The next activity is now the front of the task. + next.frontOfTask = true; + } + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // If the caller asked that this activity (and all above it) + // be cleared when the task is reset, don't lose that information, + // but propagate it up to the next activity. + next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + } + } + } + + r.pauseKeyDispatchingLocked(); + if (mMainStack) { + if (mService.mFocusedActivity == r) { + mService.setFocusedActivityLocked(topRunningActivityLocked(null)); + } + } + + // send the result + ActivityRecord resultTo = r.resultTo; + if (resultTo != null) { + if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo + + " who=" + r.resultWho + " req=" + r.requestCode + + " res=" + resultCode + " data=" + resultData); + if (r.info.applicationInfo.uid > 0) { + mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid, + r.packageName, resultData, r); + } + resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode, + resultData); + r.resultTo = null; + } + else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r); + + // Make sure this HistoryRecord is not holding on to other resources, + // because clients have remote IPC references to this object so we + // can't assume that will go away and want to avoid circular IPC refs. + r.results = null; + r.pendingResults = null; + r.newIntents = null; + r.icicle = null; + + if (mService.mPendingThumbnails.size() > 0) { + // There are clients waiting to receive thumbnails so, in case + // this is an activity that someone is waiting for, add it + // to the pending list so we can correctly update the clients. + mService.mCancelledThumbnails.add(r); + } + + if (mResumedActivity == r) { + boolean endTask = index <= 0 + || ((ActivityRecord)mHistory.get(index-1)).task != r.task; + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare close transition: finishing " + r); + mService.mWindowManager.prepareAppTransition(endTask + ? WindowManagerPolicy.TRANSIT_TASK_CLOSE + : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE); + + // Tell window manager to prepare for this one to be removed. + mService.mWindowManager.setAppVisibility(r, false); + + if (mPausingActivity == null) { + if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); + if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); + startPausingLocked(false, false); + } + + } else if (r.state != ActivityState.PAUSING) { + // If the activity is PAUSING, we will complete the finish once + // it is done pausing; else we can just directly finish it here. + if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r); + return finishCurrentActivityLocked(r, index, + FINISH_AFTER_PAUSE) == null; + } else { + if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); + } + + return false; + } + + private static final int FINISH_IMMEDIATELY = 0; + private static final int FINISH_AFTER_PAUSE = 1; + private static final int FINISH_AFTER_VISIBLE = 2; + + private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, + int mode) { + final int index = indexOfTokenLocked(r); + if (index < 0) { + return null; + } + + return finishCurrentActivityLocked(r, index, mode); + } + + private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, + int index, int mode) { + // First things first: if this activity is currently visible, + // and the resumed activity is not yet visible, then hold off on + // finishing until the resumed one becomes visible. + if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) { + if (!mStoppingActivities.contains(r)) { + mStoppingActivities.add(r); + if (mStoppingActivities.size() > 3) { + // If we already have a few activities waiting to stop, + // then give up on things going idle and start clearing + // them out. + Message msg = Message.obtain(); + msg.what = IDLE_NOW_MSG; + mHandler.sendMessage(msg); + } + } + r.state = ActivityState.STOPPING; + mService.updateOomAdjLocked(); + return r; + } + + // make sure the record is cleaned out of other places. + mStoppingActivities.remove(r); + mWaitingVisibleActivities.remove(r); + if (mResumedActivity == r) { + mResumedActivity = null; + } + final ActivityState prevState = r.state; + r.state = ActivityState.FINISHING; + + if (mode == FINISH_IMMEDIATELY + || prevState == ActivityState.STOPPED + || prevState == ActivityState.INITIALIZING) { + // If this activity is already stopped, we can just finish + // it right now. + return destroyActivityLocked(r, true) ? null : r; + } else { + // Need to go through the full pause cycle to get this + // activity into the stopped state and then finish it. + if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r); + mFinishingActivities.add(r); + resumeTopActivityLocked(null); + } + return r; + } + + /** + * Perform the common clean-up of an activity record. This is called both + * as part of destroyActivityLocked() (when destroying the client-side + * representation) and cleaning things up as a result of its hosting + * processing going away, in which case there is no remaining client-side + * state to destroy so only the cleanup here is needed. + */ + final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices) { + if (mResumedActivity == r) { + mResumedActivity = null; + } + if (mService.mFocusedActivity == r) { + mService.mFocusedActivity = null; + } + + r.configDestroy = false; + r.frozenBeforeDestroy = false; + + // Make sure this record is no longer in the pending finishes list. + // This could happen, for example, if we are trimming activities + // down to the max limit while they are still waiting to finish. + mFinishingActivities.remove(r); + mWaitingVisibleActivities.remove(r); + + // Remove any pending results. + if (r.finishing && r.pendingResults != null) { + for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { + PendingIntentRecord rec = apr.get(); + if (rec != null) { + mService.cancelIntentSenderLocked(rec, false); + } + } + r.pendingResults = null; + } + + if (cleanServices) { + cleanUpActivityServicesLocked(r); + } + + if (mService.mPendingThumbnails.size() > 0) { + // There are clients waiting to receive thumbnails so, in case + // this is an activity that someone is waiting for, add it + // to the pending list so we can correctly update the clients. + mService.mCancelledThumbnails.add(r); + } + + // Get rid of any pending idle timeouts. + mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); + mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + } + + private final void removeActivityFromHistoryLocked(ActivityRecord r) { + if (r.state != ActivityState.DESTROYED) { + mHistory.remove(r); + r.inHistory = false; + r.state = ActivityState.DESTROYED; + mService.mWindowManager.removeAppToken(r); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + cleanUpActivityServicesLocked(r); + r.removeUriPermissionsLocked(); + } + } + + /** + * Perform clean-up of service connections in an activity record. + */ + final void cleanUpActivityServicesLocked(ActivityRecord r) { + // Throw away any services that have been bound by this activity. + if (r.connections != null) { + Iterator<ConnectionRecord> it = r.connections.iterator(); + while (it.hasNext()) { + ConnectionRecord c = it.next(); + mService.removeConnectionLocked(c, null, r); + } + r.connections = null; + } + } + + /** + * Destroy the current CLIENT SIDE instance of an activity. This may be + * called both when actually finishing an activity, or when performing + * a configuration switch where we destroy the current client-side object + * but then create a new client-side object for this same HistoryRecord. + */ + final boolean destroyActivityLocked(ActivityRecord r, + boolean removeFromApp) { + if (DEBUG_SWITCH) Slog.v( + TAG, "Removing activity: token=" + r + + ", app=" + (r.app != null ? r.app.processName : "(null)")); + EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, + System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + + boolean removedFromHistory = false; + + cleanUpActivityLocked(r, false); + + final boolean hadApp = r.app != null; + + if (hadApp) { + if (removeFromApp) { + int idx = r.app.activities.indexOf(r); + if (idx >= 0) { + r.app.activities.remove(idx); + } + if (mService.mHeavyWeightProcess == r.app && r.app.activities.size() <= 0) { + mService.mHeavyWeightProcess = null; + mService.mHandler.sendEmptyMessage( + ActivityManagerService.CANCEL_HEAVY_NOTIFICATION_MSG); + } + if (r.persistent) { + mService.decPersistentCountLocked(r.app); + } + if (r.app.activities.size() == 0) { + // No longer have activities, so update location in + // LRU list. + mService.updateLruProcessLocked(r.app, true, false); + } + } + + boolean skipDestroy = false; + + try { + if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r); + r.app.thread.scheduleDestroyActivity(r, r.finishing, + r.configChangeFlags); + } catch (Exception e) { + // We can just ignore exceptions here... if the process + // has crashed, our death notification will clean things + // up. + //Slog.w(TAG, "Exception thrown during finish", e); + if (r.finishing) { + removeActivityFromHistoryLocked(r); + removedFromHistory = true; + skipDestroy = true; + } + } + + r.app = null; + r.nowVisible = false; + + if (r.finishing && !skipDestroy) { + r.state = ActivityState.DESTROYING; + Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG); + msg.obj = r; + mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); + } else { + r.state = ActivityState.DESTROYED; + } + } else { + // remove this record from the history. + if (r.finishing) { + removeActivityFromHistoryLocked(r); + removedFromHistory = true; + } else { + r.state = ActivityState.DESTROYED; + } + } + + r.configChangeFlags = 0; + + if (!mLRUActivities.remove(r) && hadApp) { + Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); + } + + return removedFromHistory; + } + + final void activityDestroyed(IBinder token) { + synchronized (mService) { + mHandler.removeMessages(DESTROY_TIMEOUT_MSG, token); + + int index = indexOfTokenLocked(token); + if (index >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(index); + if (r.state == ActivityState.DESTROYING) { + final long origId = Binder.clearCallingIdentity(); + removeActivityFromHistoryLocked(r); + Binder.restoreCallingIdentity(origId); + } + } + } + } + + private static void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app) { + int i = list.size(); + if (localLOGV) Slog.v( + TAG, "Removing app " + app + " from list " + list + + " with " + i + " entries"); + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)list.get(i); + if (localLOGV) Slog.v( + TAG, "Record #" + i + " " + r + ": app=" + r.app); + if (r.app == app) { + if (localLOGV) Slog.v(TAG, "Removing this entry!"); + list.remove(i); + } + } + } + + void removeHistoryRecordsForAppLocked(ProcessRecord app) { + removeHistoryRecordsForAppLocked(mLRUActivities, app); + removeHistoryRecordsForAppLocked(mStoppingActivities, app); + removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app); + removeHistoryRecordsForAppLocked(mFinishingActivities, app); + } + + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { + if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); + + final int task = tr.taskId; + int top = mHistory.size()-1; + + if (top < 0 || ((ActivityRecord)mHistory.get(top)).task.taskId == task) { + // nothing to do! + return; + } + + ArrayList moved = new ArrayList(); + + // Applying the affinities may have removed entries from the history, + // so get the size again. + top = mHistory.size()-1; + int pos = top; + + // Shift all activities with this task up to the top + // of the stack, keeping them in the same internal order. + while (pos >= 0) { + ActivityRecord r = (ActivityRecord)mHistory.get(pos); + if (localLOGV) Slog.v( + TAG, "At " + pos + " ckp " + r.task + ": " + r); + boolean first = true; + if (r.task.taskId == task) { + if (localLOGV) Slog.v(TAG, "Removing and adding at " + top); + mHistory.remove(pos); + mHistory.add(top, r); + moved.add(0, r); + top--; + if (first && mMainStack) { + mService.addRecentTaskLocked(r.task); + first = false; + } + } + pos--; + } + + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare to front transition: task=" + tr); + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + ActivityRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); + } + + mService.mWindowManager.moveAppTokensToTop(moved); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + + finishTaskMoveLocked(task); + EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, task); + } + + private final void finishTaskMoveLocked(int task) { + resumeTopActivityLocked(null); + } + + /** + * Worker method for rearranging history stack. Implements the function of moving all + * activities for a specific task (gathering them if disjoint) into a single group at the + * bottom of the stack. + * + * If a watcher is installed, the action is preflighted and the watcher has an opportunity + * to premeptively cancel the move. + * + * @param task The taskId to collect and move to the bottom. + * @return Returns true if the move completed, false if not. + */ + final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { + Slog.i(TAG, "moveTaskToBack: " + task); + + // If we have a watcher, preflight the move before committing to it. First check + // for *other* available tasks, but if none are available, then try again allowing the + // current task to be selected. + if (mMainStack && mService.mController != null) { + ActivityRecord next = topRunningActivityLocked(null, task); + if (next == null) { + next = topRunningActivityLocked(null, 0); + } + if (next != null) { + // ask watcher if this is allowed + boolean moveOK = true; + try { + moveOK = mService.mController.activityResuming(next.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + if (!moveOK) { + return false; + } + } + } + + ArrayList moved = new ArrayList(); + + if (DEBUG_TRANSITION) Slog.v(TAG, + "Prepare to back transition: task=" + task); + + final int N = mHistory.size(); + int bottom = 0; + int pos = 0; + + // Shift all activities with this task down to the bottom + // of the stack, keeping them in the same internal order. + while (pos < N) { + ActivityRecord r = (ActivityRecord)mHistory.get(pos); + if (localLOGV) Slog.v( + TAG, "At " + pos + " ckp " + r.task + ": " + r); + if (r.task.taskId == task) { + if (localLOGV) Slog.v(TAG, "Removing and adding at " + (N-1)); + mHistory.remove(pos); + mHistory.add(bottom, r); + moved.add(r); + bottom++; + } + pos++; + } + + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + ActivityRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); + } + mService.mWindowManager.moveAppTokensToBottom(moved); + if (VALIDATE_TOKENS) { + mService.mWindowManager.validateAppTokens(mHistory); + } + + finishTaskMoveLocked(task); + return true; + } + + private final void logStartActivity(int tag, ActivityRecord r, + TaskRecord task) { + EventLog.writeEvent(tag, + System.identityHashCode(r), task.taskId, + r.shortComponentName, r.intent.getAction(), + r.intent.getType(), r.intent.getDataString(), + r.intent.getFlags()); + } + + /** + * Make sure the given activity matches the current configuration. Returns + * false if the activity had to be destroyed. Returns true if the + * configuration is the same, or the activity will remain running as-is + * for whatever reason. Ensures the HistoryRecord is updated with the + * correct configuration and all other bookkeeping is handled. + */ + final boolean ensureActivityConfigurationLocked(ActivityRecord r, + int globalChanges) { + if (mConfigWillChange) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Skipping config check (will change): " + r); + return true; + } + + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Ensuring correct configuration: " + r); + + // Short circuit: if the two configurations are the exact same + // object (the common case), then there is nothing to do. + Configuration newConfig = mService.mConfiguration; + if (r.configuration == newConfig) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Configuration unchanged in " + r); + return true; + } + + // We don't worry about activities that are finishing. + if (r.finishing) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Configuration doesn't matter in finishing " + r); + r.stopFreezingScreenLocked(false); + return true; + } + + // Okay we now are going to make this activity have the new config. + // But then we need to figure out how it needs to deal with that. + Configuration oldConfig = r.configuration; + r.configuration = newConfig; + + // If the activity isn't currently running, just leave the new + // configuration and it will pick that up next time it starts. + if (r.app == null || r.app.thread == null) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Configuration doesn't matter not running " + r); + r.stopFreezingScreenLocked(false); + return true; + } + + // If the activity isn't persistent, there is a chance we will + // need to restart it. + if (!r.persistent) { + + // Figure out what has changed between the two configurations. + int changes = oldConfig.diff(newConfig); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { + Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" + + Integer.toHexString(changes) + ", handles=0x" + + Integer.toHexString(r.info.configChanges) + + ", newConfig=" + newConfig); + } + if ((changes&(~r.info.configChanges)) != 0) { + // Aha, the activity isn't handling the change, so DIE DIE DIE. + r.configChangeFlags |= changes; + r.startFreezingScreenLocked(r.app, globalChanges); + if (r.app == null || r.app.thread == null) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is destroying non-running " + r); + destroyActivityLocked(r, true); + } else if (r.state == ActivityState.PAUSING) { + // A little annoying: we are waiting for this activity to + // finish pausing. Let's not do anything now, but just + // flag that it needs to be restarted when done pausing. + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is skipping already pausing " + r); + r.configDestroy = true; + return true; + } else if (r.state == ActivityState.RESUMED) { + // Try to optimize this case: the configuration is changing + // and we need to restart the top, resumed activity. + // Instead of doing the normal handshaking, just say + // "restart!". + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is restarting resumed " + r); + relaunchActivityLocked(r, r.configChangeFlags, true); + r.configChangeFlags = 0; + } else { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + "Switch is restarting non-resumed " + r); + relaunchActivityLocked(r, r.configChangeFlags, false); + r.configChangeFlags = 0; + } + + // All done... tell the caller we weren't able to keep this + // activity around. + return false; + } + } + + // Default case: the activity can handle this new configuration, so + // hand it over. Note that we don't need to give it the new + // configuration, since we always send configuration changes to all + // process when they happen so it can just use whatever configuration + // it last got. + if (r.app != null && r.app.thread != null) { + try { + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r); + r.app.thread.scheduleActivityConfigurationChanged(r); + } catch (RemoteException e) { + // If process died, whatever. + } + } + r.stopFreezingScreenLocked(false); + + return true; + } + + private final boolean relaunchActivityLocked(ActivityRecord r, + int changes, boolean andResume) { + List<ResultInfo> results = null; + List<Intent> newIntents = null; + if (andResume) { + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY + : EventLogTags.AM_RELAUNCH_ACTIVITY, System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + + r.startFreezingScreenLocked(r.app, 0); + + try { + if (DEBUG_SWITCH) Slog.i(TAG, "Switch is restarting resumed " + r); + r.app.thread.scheduleRelaunchActivity(r, results, newIntents, + changes, !andResume, mService.mConfiguration); + // Note: don't need to call pauseIfSleepingLocked() here, because + // the caller will only pass in 'andResume' if this activity is + // currently resumed, which implies we aren't sleeping. + } catch (RemoteException e) { + return false; + } + + if (andResume) { + r.results = null; + r.newIntents = null; + if (mMainStack) { + mService.reportResumedActivityLocked(r); + } + } + + return true; + } +} diff --git a/services/java/com/android/server/am/AppErrorDialog.java b/services/java/com/android/server/am/AppErrorDialog.java index 3a1aad6..a769c05 100644 --- a/services/java/com/android/server/am/AppErrorDialog.java +++ b/services/java/com/android/server/am/AppErrorDialog.java @@ -80,9 +80,6 @@ class AppErrorDialog extends BaseErrorDialog { DISMISS_TIMEOUT); } - public void onStop() { - } - private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { synchronized (mProc) { diff --git a/services/java/com/android/server/am/AppNotRespondingDialog.java b/services/java/com/android/server/am/AppNotRespondingDialog.java index 9702f91..b2737dc 100644 --- a/services/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/java/com/android/server/am/AppNotRespondingDialog.java @@ -40,7 +40,7 @@ class AppNotRespondingDialog extends BaseErrorDialog { private final ProcessRecord mProc; public AppNotRespondingDialog(ActivityManagerService service, Context context, - ProcessRecord app, HistoryRecord activity) { + ProcessRecord app, ActivityRecord activity) { super(context); mService = service; diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 33bbc13..37da6f7 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -59,7 +59,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void shutdown() { Slog.w("BatteryStats", "Writing battery stats before shutdown..."); synchronized (mStats) { - mStats.writeLocked(); + mStats.shutdownLocked(); } } @@ -124,14 +124,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void noteStartGps(int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteStartGps(uid); + mStats.noteStartGpsLocked(uid); } } public void noteStopGps(int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteStopGps(uid); + mStats.noteStopGpsLocked(uid); } } @@ -321,14 +321,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { return mStats.isOnBattery(); } - public void setOnBattery(boolean onBattery, int level) { + public void setBatteryState(int status, int health, int plugType, int level, + int temp, int volt) { enforceCallingPermission(); - mStats.setOnBattery(onBattery, level); - } - - public void recordCurrentLevel(int level) { - enforceCallingPermission(); - mStats.recordCurrentLevel(level); + mStats.setBatteryState(status, health, plugType, level, temp, volt); } public long getAwakeTimeBattery() { @@ -359,7 +355,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub { for (String arg : args) { if ("--checkin".equals(arg)) { isCheckin = true; - break; + } else if ("--reset".equals(arg)) { + mStats.resetAllStatsLocked(); } } } diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index f613b00..22acda9 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -26,7 +26,7 @@ import java.io.PrintWriter; */ class ConnectionRecord { final AppBindRecord binding; // The application/service binding. - final HistoryRecord activity; // If non-null, the owning activity. + final ActivityRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. final int clientLabel; // String resource labeling this client. @@ -42,7 +42,7 @@ class ConnectionRecord { + " flags=0x" + Integer.toHexString(flags)); } - ConnectionRecord(AppBindRecord _binding, HistoryRecord _activity, + ConnectionRecord(AppBindRecord _binding, ActivityRecord _activity, IServiceConnection _conn, int _flags, int _clientLabel, PendingIntent _clientIntent) { binding = _binding; diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index c764635..44c9742 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.app.IActivityManager.ContentProviderHolder; +import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.ProviderInfo; import android.os.Process; @@ -29,6 +30,7 @@ class ContentProviderRecord extends ContentProviderHolder { final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>(); final int uid; final ApplicationInfo appInfo; + final ComponentName name; int externals; // number of non-framework processes supported by this provider ProcessRecord app; // if non-null, hosting application ProcessRecord launchingApp; // if non-null, waiting for this app to be launched. @@ -38,6 +40,7 @@ class ContentProviderRecord extends ContentProviderHolder { super(_info); uid = ai.uid; appInfo = ai; + name = new ComponentName(_info.packageName, _info.name); noReleaseNeeded = uid == 0 || uid == Process.SYSTEM_UID; } @@ -45,6 +48,7 @@ class ContentProviderRecord extends ContentProviderHolder { super(cpr.info); uid = cpr.uid; appInfo = cpr.appInfo; + name = cpr.name; noReleaseNeeded = cpr.noReleaseNeeded; } diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 847e91b..7a85eb8 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -42,7 +42,7 @@ class PendingIntentRecord extends IIntentSender.Stub { final static class Key { final int type; final String packageName; - final HistoryRecord activity; + final ActivityRecord activity; final String who; final int requestCode; final Intent requestIntent; @@ -52,7 +52,7 @@ class PendingIntentRecord extends IIntentSender.Stub { private static final int ODD_PRIME_NUMBER = 37; - Key(int _t, String _p, HistoryRecord _a, String _w, + Key(int _t, String _p, ActivityRecord _a, String _w, int _r, Intent _i, String _it, int _f) { type = _t; packageName = _p; @@ -218,7 +218,7 @@ class PendingIntentRecord extends IIntentSender.Stub { } break; case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT: - owner.sendActivityResultLocked(-1, key.activity, + key.activity.stack.sendActivityResultLocked(-1, key.activity, key.who, key.requestCode, code, finalIntent); break; case IActivityManager.INTENT_SENDER_BROADCAST: diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index f49a182..18b1acb 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -28,7 +28,6 @@ import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.util.PrintWriterPrinter; import java.io.PrintWriter; @@ -40,12 +39,12 @@ import java.util.HashSet; * Full information about a particular process that * is currently running. */ -class ProcessRecord implements Watchdog.PssRequestor { +class ProcessRecord { final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics final ApplicationInfo info; // all about the first app in the process final String processName; // name of the process // List of packages running in the process - final HashSet<String> pkgList = new HashSet(); + final HashSet<String> pkgList = new HashSet<String>(); IApplicationThread thread; // the actual proc... may be null only if // 'persistent' is true (in which case we // are in the process of launching the app) @@ -87,9 +86,9 @@ class ProcessRecord implements Watchdog.PssRequestor { Object adjTarget; // Debugging: target component impacting oom_adj. // contains HistoryRecord objects - final ArrayList activities = new ArrayList(); + final ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); // all ServiceRecord running in this process - final HashSet services = new HashSet(); + final HashSet<ServiceRecord> services = new HashSet<ServiceRecord>(); // services that are currently executing code (need to remain foreground). final HashSet<ServiceRecord> executingServices = new HashSet<ServiceRecord>(); @@ -99,7 +98,8 @@ class ProcessRecord implements Watchdog.PssRequestor { // all IIntentReceivers that are registered from this process. final HashSet<ReceiverList> receivers = new HashSet<ReceiverList>(); // class (String) -> ContentProviderRecord - final HashMap pubProviders = new HashMap(); + final HashMap<String, ContentProviderRecord> pubProviders + = new HashMap<String, ContentProviderRecord>(); // All ContentProviderRecord process is using final HashMap<ContentProviderRecord, Integer> conProviders = new HashMap<ContentProviderRecord, Integer>(); @@ -128,7 +128,6 @@ class ProcessRecord implements Watchdog.PssRequestor { ComponentName errorReportReceiver; void dump(PrintWriter pw, String prefix) { - long now = SystemClock.uptimeMillis(); if (info.className != null) { pw.print(prefix); pw.print("class="); pw.println(info.className); } @@ -249,7 +248,7 @@ class ProcessRecord implements Watchdog.PssRequestor { public boolean isInterestingToUserLocked() { final int size = activities.size(); for (int i = 0 ; i < size ; i++) { - HistoryRecord r = (HistoryRecord) activities.get(i); + ActivityRecord r = activities.get(i); if (r.isInterestingToUserLocked()) { return true; } @@ -261,17 +260,7 @@ class ProcessRecord implements Watchdog.PssRequestor { int i = activities.size(); while (i > 0) { i--; - ((HistoryRecord)activities.get(i)).stopFreezingScreenLocked(true); - } - } - - public void requestPss() { - IApplicationThread localThread = thread; - if (localThread != null) { - try { - localThread.requestPss(); - } catch (RemoteException e) { - } + activities.get(i).stopFreezingScreenLocked(true); } } diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 0542497..75365ad 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; +import com.android.server.NotificationManagerService; import android.app.INotificationManager; import android.app.Notification; @@ -252,6 +253,8 @@ class ServiceRecord extends Binder { } public void postNotification() { + final int appUid = appInfo.uid; + final int appPid = app.pid; if (foregroundId != 0 && foregroundNoti != null) { // Do asynchronous communication with notification manager to // avoid deadlocks. @@ -260,14 +263,15 @@ class ServiceRecord extends Binder { final Notification localForegroundNoti = foregroundNoti; ams.mHandler.post(new Runnable() { public void run() { - INotificationManager inm = NotificationManager.getService(); - if (inm == null) { + NotificationManagerService nm = + (NotificationManagerService) NotificationManager.getService(); + if (nm == null) { return; } try { int[] outId = new int[1]; - inm.enqueueNotification(localPackageName, localForegroundId, - localForegroundNoti, outId); + nm.enqueueNotificationInternal(localPackageName, appUid, appPid, + null, localForegroundId, localForegroundNoti, outId); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error showing notification for service", e); @@ -275,7 +279,6 @@ class ServiceRecord extends Binder { // get to be foreground. ams.setServiceForeground(name, ServiceRecord.this, localForegroundId, null, true); - } catch (RemoteException e) { } } }); diff --git a/services/java/com/android/server/am/StrictModeViolationDialog.java b/services/java/com/android/server/am/StrictModeViolationDialog.java new file mode 100644 index 0000000..fe76d18 --- /dev/null +++ b/services/java/com/android/server/am/StrictModeViolationDialog.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2006 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.am; + +import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Message; +import android.util.Slog; + +class StrictModeViolationDialog extends BaseErrorDialog { + private final static String TAG = "StrictModeViolationDialog"; + + private final AppErrorResult mResult; + private final ProcessRecord mProc; + + // Event 'what' codes + static final int ACTION_OK = 0; + static final int ACTION_OK_AND_REPORT = 1; + + // 1-minute timeout, then we automatically dismiss the violation + // dialog + static final long DISMISS_TIMEOUT = 1000 * 60 * 1; + + public StrictModeViolationDialog(Context context, AppErrorResult result, ProcessRecord app) { + super(context); + + Resources res = context.getResources(); + + mProc = app; + mResult = result; + CharSequence name; + if ((app.pkgList.size() == 1) && + (name=context.getPackageManager().getApplicationLabel(app.info)) != null) { + setMessage(res.getString( + com.android.internal.R.string.smv_application, + name.toString(), app.info.processName)); + } else { + name = app.processName; + setMessage(res.getString( + com.android.internal.R.string.smv_process, + name.toString())); + } + + setCancelable(false); + + setButton(DialogInterface.BUTTON_POSITIVE, + res.getText(com.android.internal.R.string.dlg_ok), + mHandler.obtainMessage(ACTION_OK)); + + if (app.errorReportReceiver != null) { + setButton(DialogInterface.BUTTON_NEGATIVE, + res.getText(com.android.internal.R.string.report), + mHandler.obtainMessage(ACTION_OK_AND_REPORT)); + } + + setTitle(res.getText(com.android.internal.R.string.aerr_title)); + getWindow().addFlags(FLAG_SYSTEM_ERROR); + getWindow().setTitle("Strict Mode Violation: " + app.info.processName); + + // After the timeout, pretend the user clicked the quit button + mHandler.sendMessageDelayed( + mHandler.obtainMessage(ACTION_OK), + DISMISS_TIMEOUT); + } + + private final Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + synchronized (mProc) { + if (mProc != null && mProc.crashDialog == StrictModeViolationDialog.this) { + mProc.crashDialog = null; + } + } + mResult.set(msg.what); + + // If this is a timeout we won't be automatically closed, so go + // ahead and explicitly dismiss ourselves just in case. + dismiss(); + } + }; +} diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java index ffa8a2a..81450c5 100644 --- a/services/java/com/android/server/am/UriPermission.java +++ b/services/java/com/android/server/am/UriPermission.java @@ -27,8 +27,8 @@ class UriPermission { final Uri uri; int modeFlags = 0; int globalModeFlags = 0; - final HashSet<HistoryRecord> readActivities = new HashSet<HistoryRecord>(); - final HashSet<HistoryRecord> writeActivities = new HashSet<HistoryRecord>(); + final HashSet<ActivityRecord> readActivities = new HashSet<ActivityRecord>(); + final HashSet<ActivityRecord> writeActivities = new HashSet<ActivityRecord>(); String stringName; @@ -42,7 +42,7 @@ class UriPermission { globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; if (readActivities.size() > 0) { - for (HistoryRecord r : readActivities) { + for (ActivityRecord r : readActivities) { r.readUriPermissions.remove(this); if (r.readUriPermissions.size() == 0) { r.readUriPermissions = null; @@ -55,7 +55,7 @@ class UriPermission { globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; if (readActivities.size() > 0) { - for (HistoryRecord r : readActivities) { + for (ActivityRecord r : readActivities) { r.writeUriPermissions.remove(this); if (r.writeUriPermissions.size() == 0) { r.writeUriPermissions = null; diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 1b9e1c7..3f15d0a 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -44,6 +44,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; /** * This service collects the statistics associated with usage @@ -88,11 +91,13 @@ public final class UsageStatsService extends IUsageStats.Stub { private boolean mIsResumed; private File mFile; private String mFileLeaf; - //private File mBackupFile; - private long mLastWriteElapsedTime; private File mDir; - private Calendar mCal; - private int mLastWriteDay; + + private Calendar mCal; // guarded by itself + + private final AtomicInteger mLastWriteDay = new AtomicInteger(-1); + private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0); + private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false); static class TimeStats { int count; @@ -241,31 +246,33 @@ public final class UsageStatsService extends IUsageStats.Stub { mFileLeaf = getCurrentDateStr(FILE_PREFIX); mFile = new File(mDir, mFileLeaf); readStatsFromFile(); - mLastWriteElapsedTime = SystemClock.elapsedRealtime(); + mLastWriteElapsedTime.set(SystemClock.elapsedRealtime()); // mCal was set by getCurrentDateStr(), want to use that same time. - mLastWriteDay = mCal.get(Calendar.DAY_OF_YEAR); + mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR)); } /* * Utility method to convert date into string. */ private String getCurrentDateStr(String prefix) { - mCal.setTimeInMillis(System.currentTimeMillis()); StringBuilder sb = new StringBuilder(); - if (prefix != null) { - sb.append(prefix); - } - sb.append(mCal.get(Calendar.YEAR)); - int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; - if (mm < 10) { - sb.append("0"); - } - sb.append(mm); - int dd = mCal.get(Calendar.DAY_OF_MONTH); - if (dd < 10) { - sb.append("0"); + synchronized (mCal) { + mCal.setTimeInMillis(System.currentTimeMillis()); + if (prefix != null) { + sb.append(prefix); + } + sb.append(mCal.get(Calendar.YEAR)); + int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1; + if (mm < 10) { + sb.append("0"); + } + sb.append(mm); + int dd = mCal.get(Calendar.DAY_OF_MONTH); + if (dd < 10) { + sb.append("0"); + } + sb.append(dd); } - sb.append(dd); return sb.toString(); } @@ -360,23 +367,56 @@ public final class UsageStatsService extends IUsageStats.Stub { file.delete(); } } - - private void writeStatsToFile(boolean force) { - synchronized (mFileLock) { + + /** + * Conditionally start up a disk write if it's been awhile, or the + * day has rolled over. + * + * This is called indirectly from user-facing actions (when + * 'force' is false) so it tries to be quick, without writing to + * disk directly or acquiring heavy locks. + * + * @params force do an unconditional, synchronous stats flush + * to disk on the current thread. + */ + private void writeStatsToFile(final boolean force) { + int curDay; + synchronized (mCal) { mCal.setTimeInMillis(System.currentTimeMillis()); - final int curDay = mCal.get(Calendar.DAY_OF_YEAR); - // Determine if the day changed... note that this will be wrong - // if the year has changed but we are in the same day of year... - // we can probably live with this. - final boolean dayChanged = curDay != mLastWriteDay; - long currElapsedTime = SystemClock.elapsedRealtime(); - if (!force) { - if (((currElapsedTime-mLastWriteElapsedTime) < FILE_WRITE_INTERVAL) && - (!dayChanged)) { - // wait till the next update - return; - } + curDay = mCal.get(Calendar.DAY_OF_YEAR); + } + final boolean dayChanged = curDay != mLastWriteDay.get(); + + // Determine if the day changed... note that this will be wrong + // if the year has changed but we are in the same day of year... + // we can probably live with this. + final long currElapsedTime = SystemClock.elapsedRealtime(); + + // Fast common path, without taking the often-contentious + // mFileLock. + if (!force) { + if (!dayChanged && + (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) { + // wait till the next update + return; } + if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) { + new Thread("UsageStatsService_DiskWriter") { + public void run() { + try { + Slog.d(TAG, "Disk writer thread starting."); + writeStatsToFile(true); + } finally { + mUnforcedDiskWriteRunning.set(false); + Slog.d(TAG, "Disk writer thread ending."); + } + } + }.start(); + } + return; + } + + synchronized (mFileLock) { // Get the most recent file mFileLeaf = getCurrentDateStr(FILE_PREFIX); // Copy current file to back up @@ -395,10 +435,10 @@ public final class UsageStatsService extends IUsageStats.Stub { try { // Write mStats to file - writeStatsFLOCK(); - mLastWriteElapsedTime = currElapsedTime; + writeStatsFLOCK(mFile); + mLastWriteElapsedTime.set(currElapsedTime); if (dayChanged) { - mLastWriteDay = curDay; + mLastWriteDay.set(curDay); // clear stats synchronized (mStats) { mStats.clear(); @@ -418,10 +458,11 @@ public final class UsageStatsService extends IUsageStats.Stub { } } } + Slog.d(TAG, "Dumped usage stats."); } - private void writeStatsFLOCK() throws IOException { - FileOutputStream stream = new FileOutputStream(mFile); + private void writeStatsFLOCK(File file) throws IOException { + FileOutputStream stream = new FileOutputStream(file); try { Parcel out = Parcel.obtain(); writeStatsToParcelFLOCK(out); diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index b29f875..eb0a8a9 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -26,13 +26,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.Usb; import android.net.ConnectivityManager; import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.NetworkInfo; import android.net.NetworkUtils; -import android.os.BatteryManager; import android.os.Binder; import android.os.Environment; import android.os.HandlerThread; @@ -135,7 +135,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(Usb.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(mStateReceiver, filter); @@ -424,10 +424,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); - if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - mUsbConnected = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) - == BatteryManager.BATTERY_PLUGGED_USB); - Tethering.this.updateUsbStatus(); + if (action.equals(Usb.ACTION_USB_STATE)) { + mUsbConnected = intent.getExtras().getBoolean(Usb.USB_CONNECTED); + updateUsbStatus(); } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { mUsbMassStorageOff = false; updateUsbStatus(); diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java new file mode 100644 index 0000000..3c05da2 --- /dev/null +++ b/services/java/com/android/server/location/GeocoderProxy.java @@ -0,0 +1,105 @@ +/* + * 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.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Address; +import android.location.GeocoderParams; +import android.location.IGeocodeProvider; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import java.util.List; + +/** + * A class for proxying IGeocodeProvider implementations. + * + * {@hide} + */ +public class GeocoderProxy { + + private static final String TAG = "GeocoderProxy"; + + private final Context mContext; + private final Intent mIntent; + private final Connection mServiceConnection = new Connection(); + private IGeocodeProvider mProvider; + + public GeocoderProxy(Context context, String serviceName) { + mContext = context; + mIntent = new Intent(serviceName); + mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE); + } + + private class Connection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "onServiceConnected " + className); + synchronized (this) { + mProvider = IGeocodeProvider.Stub.asInterface(service); + } + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "onServiceDisconnected " + className); + synchronized (this) { + mProvider = null; + } + } + } + + public String getFromLocation(double latitude, double longitude, int maxResults, + GeocoderParams params, List<Address> addrs) { + IGeocodeProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getFromLocation(latitude, longitude, maxResults, + params, addrs); + } catch (RemoteException e) { + Log.e(TAG, "getFromLocation failed", e); + } + } + return "Service not Available"; + } + + public String getFromLocationName(String locationName, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, + GeocoderParams params, List<Address> addrs) { + IGeocodeProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getFromLocationName(locationName, lowerLeftLatitude, + lowerLeftLongitude, upperRightLatitude, upperRightLongitude, + maxResults, params, addrs); + } catch (RemoteException e) { + Log.e(TAG, "getFromLocationName failed", e); + } + } + return "Service not Available"; + } +} diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java new file mode 100755 index 0000000..ea6aa94 --- /dev/null +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -0,0 +1,1469 @@ +/* + * 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.location; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Criteria; +import android.location.IGpsStatusListener; +import android.location.IGpsStatusProvider; +import android.location.ILocationManager; +import android.location.INetInitiatedListener; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.SntpClient; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.Phone; +import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.StringBufferInputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Properties; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; + +/** + * A GPS implementation of LocationProvider used by LocationManager. + * + * {@hide} + */ +public class GpsLocationProvider implements LocationProviderInterface { + + private static final String TAG = "GpsLocationProvider"; + + private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; + + // these need to match GpsPositionMode enum in gps.h + private static final int GPS_POSITION_MODE_STANDALONE = 0; + private static final int GPS_POSITION_MODE_MS_BASED = 1; + private static final int GPS_POSITION_MODE_MS_ASSISTED = 2; + + // these need to match GpsPositionRecurrence enum in gps.h + private static final int GPS_POSITION_RECURRENCE_PERIODIC = 0; + private static final int GPS_POSITION_RECURRENCE_SINGLE = 1; + + // these need to match GpsStatusValue defines in gps.h + private static final int GPS_STATUS_NONE = 0; + private static final int GPS_STATUS_SESSION_BEGIN = 1; + private static final int GPS_STATUS_SESSION_END = 2; + private static final int GPS_STATUS_ENGINE_ON = 3; + private static final int GPS_STATUS_ENGINE_OFF = 4; + + // these need to match GpsApgsStatusValue defines in gps.h + /** AGPS status event values. */ + private static final int GPS_REQUEST_AGPS_DATA_CONN = 1; + private static final int GPS_RELEASE_AGPS_DATA_CONN = 2; + private static final int GPS_AGPS_DATA_CONNECTED = 3; + private static final int GPS_AGPS_DATA_CONN_DONE = 4; + private static final int GPS_AGPS_DATA_CONN_FAILED = 5; + + // these need to match GpsLocationFlags enum in gps.h + private static final int LOCATION_INVALID = 0; + private static final int LOCATION_HAS_LAT_LONG = 1; + private static final int LOCATION_HAS_ALTITUDE = 2; + private static final int LOCATION_HAS_SPEED = 4; + private static final int LOCATION_HAS_BEARING = 8; + private static final int LOCATION_HAS_ACCURACY = 16; + +// IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h + private static final int GPS_DELETE_EPHEMERIS = 0x0001; + private static final int GPS_DELETE_ALMANAC = 0x0002; + private static final int GPS_DELETE_POSITION = 0x0004; + private static final int GPS_DELETE_TIME = 0x0008; + private static final int GPS_DELETE_IONO = 0x0010; + private static final int GPS_DELETE_UTC = 0x0020; + private static final int GPS_DELETE_HEALTH = 0x0040; + private static final int GPS_DELETE_SVDIR = 0x0080; + private static final int GPS_DELETE_SVSTEER = 0x0100; + private static final int GPS_DELETE_SADATA = 0x0200; + private static final int GPS_DELETE_RTI = 0x0400; + private static final int GPS_DELETE_CELLDB_INFO = 0x8000; + private static final int GPS_DELETE_ALL = 0xFFFF; + + // The GPS_CAPABILITY_* flags must match the values in gps.h + private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001; + private static final int GPS_CAPABILITY_MSB = 0x0000002; + private static final int GPS_CAPABILITY_MSA = 0x0000004; + private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008; + + + // these need to match AGpsType enum in gps.h + private static final int AGPS_TYPE_SUPL = 1; + private static final int AGPS_TYPE_C2K = 2; + + // for mAGpsDataConnectionState + private static final int AGPS_DATA_CONNECTION_CLOSED = 0; + private static final int AGPS_DATA_CONNECTION_OPENING = 1; + private static final int AGPS_DATA_CONNECTION_OPEN = 2; + + // Handler messages + private static final int CHECK_LOCATION = 1; + private static final int ENABLE = 2; + private static final int ENABLE_TRACKING = 3; + private static final int UPDATE_NETWORK_STATE = 4; + private static final int INJECT_NTP_TIME = 5; + private static final int DOWNLOAD_XTRA_DATA = 6; + private static final int UPDATE_LOCATION = 7; + private static final int ADD_LISTENER = 8; + private static final int REMOVE_LISTENER = 9; + private static final int REQUEST_SINGLE_SHOT = 10; + + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + + private int mLocationFlags = LOCATION_INVALID; + + // current status + private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE; + + // time for last status update + private long mStatusUpdateTime = SystemClock.elapsedRealtime(); + + // turn off GPS fix icon if we haven't received a fix in 10 seconds + private static final long RECENT_FIX_TIMEOUT = 10 * 1000; + + // stop trying if we do not receive a fix within 60 seconds + private static final int NO_FIX_TIMEOUT = 60 * 1000; + + // true if we are enabled + private volatile boolean mEnabled; + + // true if we have network connectivity + private boolean mNetworkAvailable; + + // flags to trigger NTP or XTRA data download when network becomes available + // initialized to true so we do NTP and XTRA when the network comes up after booting + private boolean mInjectNtpTimePending = true; + private boolean mDownloadXtraDataPending = false; + + // true if GPS is navigating + private boolean mNavigating; + + // true if GPS engine is on + private boolean mEngineOn; + + // requested frequency of fixes, in milliseconds + private int mFixInterval = 1000; + + // true if we started navigation + private boolean mStarted; + + // true if single shot request is in progress + private boolean mSingleShot; + + // capabilities of the GPS engine + private int mEngineCapabilities; + + // for calculating time to first fix + private long mFixRequestTime = 0; + // time to first fix for most recent session + private int mTTFF = 0; + // time we received our last fix + private long mLastFixTime; + + private int mPositionMode; + + // properties loaded from PROPERTIES_FILE + private Properties mProperties; + private String mNtpServer; + private String mSuplServerHost; + private int mSuplServerPort; + private String mC2KServerHost; + private int mC2KServerPort; + + private final Context mContext; + private final ILocationManager mLocationManager; + private Location mLocation = new Location(LocationManager.GPS_PROVIDER); + private Bundle mLocationExtras = new Bundle(); + private ArrayList<Listener> mListeners = new ArrayList<Listener>(); + + // GpsLocationProvider's handler thread + private final Thread mThread; + // Handler for processing events in mThread. + private Handler mHandler; + // Used to signal when our main thread has initialized everything + private final CountDownLatch mInitializedLatch = new CountDownLatch(1); + + private String mAGpsApn; + private int mAGpsDataConnectionState; + private final ConnectivityManager mConnMgr; + private final GpsNetInitiatedHandler mNIHandler; + + // Wakelocks + private final static String WAKELOCK_KEY = "GpsLocationProvider"; + private final PowerManager.WakeLock mWakeLock; + // bitfield of pending messages to our Handler + // used only for messages that cannot have multiple instances queued + private int mPendingMessageBits; + // separate counter for ADD_LISTENER and REMOVE_LISTENER messages, + // which might have multiple instances queued + private int mPendingListenerMessages; + + // Alarms + private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; + private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT"; + private final AlarmManager mAlarmManager; + private final PendingIntent mWakeupIntent; + private final PendingIntent mTimeoutIntent; + + private final IBatteryStats mBatteryStats; + private final SparseIntArray mClientUids = new SparseIntArray(); + + // how often to request NTP time, in milliseconds + // current setting 4 hours + private static final long NTP_INTERVAL = 4*60*60*1000; + // how long to wait if we have a network error in NTP or XTRA downloading + // current setting - 5 minutes + private static final long RETRY_INTERVAL = 5*60*1000; + + // to avoid injecting bad NTP time, we reject any time fixes that differ from system time + // by more than 5 minutes. + private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000; + + private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { + public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { + if (listener == null) { + throw new NullPointerException("listener is null in addGpsStatusListener"); + } + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + // listener already added + return; + } + } + + Listener l = new Listener(listener); + binder.linkToDeath(l, 0); + mListeners.add(l); + } + } + + public void removeGpsStatusListener(IGpsStatusListener listener) { + if (listener == null) { + throw new NullPointerException("listener is null in addGpsStatusListener"); + } + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + Listener l = null; + int size = mListeners.size(); + for (int i = 0; i < size && l == null; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + l = test; + } + } + + if (l != null) { + mListeners.remove(l); + binder.unlinkToDeath(l, 0); + } + } + } + }; + + public IGpsStatusProvider getGpsStatusProvider() { + return mGpsStatusProvider; + } + + private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(ALARM_WAKEUP)) { + if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); + startNavigating(false); + } else if (action.equals(ALARM_TIMEOUT)) { + if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); + hibernate(); + } + } + }; + + public static boolean isSupported() { + return native_is_supported(); + } + + public GpsLocationProvider(Context context, ILocationManager locationManager) { + mContext = context; + mLocationManager = locationManager; + mNIHandler = new GpsNetInitiatedHandler(context); + + mLocation.setExtras(mLocationExtras); + + // Create a wake lock + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mWakeLock.setReferenceCounted(false); + + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); + mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); + + mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + + // Battery statistics service to be notified when GPS turns on or off + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); + + mProperties = new Properties(); + try { + File file = new File(PROPERTIES_FILE); + FileInputStream stream = new FileInputStream(file); + mProperties.load(stream); + stream.close(); + mNtpServer = mProperties.getProperty("NTP_SERVER", null); + + mSuplServerHost = mProperties.getProperty("SUPL_HOST"); + String portString = mProperties.getProperty("SUPL_PORT"); + if (mSuplServerHost != null && portString != null) { + try { + mSuplServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse SUPL_PORT: " + portString); + } + } + + mC2KServerHost = mProperties.getProperty("C2K_HOST"); + portString = mProperties.getProperty("C2K_PORT"); + if (mC2KServerHost != null && portString != null) { + try { + mC2KServerPort = Integer.parseInt(portString); + } catch (NumberFormatException e) { + Log.e(TAG, "unable to parse C2K_PORT: " + portString); + } + } + } catch (IOException e) { + Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + } + + // wait until we are fully initialized before returning + mThread = new GpsLocationProviderThread(); + mThread.start(); + while (true) { + try { + mInitializedLatch.await(); + break; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private void initialize() { + // register our receiver on our thread rather than the main thread + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ALARM_WAKEUP); + intentFilter.addAction(ALARM_TIMEOUT); + mContext.registerReceiver(mBroadcastReciever, intentFilter); + } + + /** + * Returns the name of this provider. + */ + public String getName() { + return LocationManager.GPS_PROVIDER; + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public boolean requiresNetwork() { + return true; + } + + public void updateNetworkState(int state, NetworkInfo info) { + sendMessage(UPDATE_NETWORK_STATE, state, info); + } + + private void handleUpdateNetworkState(int state, NetworkInfo info) { + mNetworkAvailable = (state == LocationProvider.AVAILABLE); + + if (DEBUG) { + Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable") + + " info: " + info); + } + + if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL + && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { + String apnName = info.getExtraInfo(); + if (mNetworkAvailable && apnName != null && apnName.length() > 0) { + mAGpsApn = apnName; + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open"); + native_agps_data_conn_open(apnName); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed"); + mAGpsApn = null; + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + native_agps_data_conn_failed(); + } + } + + if (mNetworkAvailable) { + if (mInjectNtpTimePending) { + sendMessage(INJECT_NTP_TIME, 0, null); + } + if (mDownloadXtraDataPending) { + sendMessage(DOWNLOAD_XTRA_DATA, 0, null); + } + } + } + + private void handleInjectNtpTime() { + if (!mNetworkAvailable) { + // try again when network is up + mInjectNtpTimePending = true; + return; + } + mInjectNtpTimePending = false; + + SntpClient client = new SntpClient(); + long delay; + + if (client.requestTime(mNtpServer, 10000)) { + long time = client.getNtpTime(); + long timeReference = client.getNtpTimeReference(); + int certainty = (int)(client.getRoundTripTime()/2); + long now = System.currentTimeMillis(); + + Log.d(TAG, "NTP server returned: " + + time + " (" + new Date(time) + + ") reference: " + timeReference + + " certainty: " + certainty + + " system time offset: " + (time - now)); + + native_inject_time(time, timeReference, certainty); + delay = NTP_INTERVAL; + } else { + if (DEBUG) Log.d(TAG, "requestTime failed"); + delay = RETRY_INTERVAL; + } + + // send delayed message for next NTP injection + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.removeMessages(INJECT_NTP_TIME); + mHandler.sendMessageDelayed(Message.obtain(mHandler, INJECT_NTP_TIME), delay); + } + + private void handleDownloadXtraData() { + if (!mNetworkAvailable) { + // try again when network is up + mDownloadXtraDataPending = true; + return; + } + mDownloadXtraDataPending = false; + + + GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (DEBUG) { + Log.d(TAG, "calling native_inject_xtra_data"); + } + native_inject_xtra_data(data, data.length); + } else { + // try again later + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.removeMessages(DOWNLOAD_XTRA_DATA); + mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL); + } + } + + /** + * This is called to inform us when another location provider returns a location. + * Someday we might use this for network location injection to aid the GPS + */ + public void updateLocation(Location location) { + sendMessage(UPDATE_LOCATION, 0, location); + } + + private void handleUpdateLocation(Location location) { + if (location.hasAccuracy()) { + native_inject_location(location.getLatitude(), location.getLongitude(), + location.getAccuracy()); + } + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + public boolean requiresSatellite() { + return true; + } + + /** + * Returns true if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + public boolean requiresCell() { + return false; + } + + /** + * Returns true if the use of this provider may result in a + * monetary charge to the user, false if use is free. It is up to + * each provider to give accurate information. + */ + public boolean hasMonetaryCost() { + return false; + } + + /** + * Returns true if the provider is able to provide altitude + * information, false otherwise. A provider that reports altitude + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsAltitude() { + return true; + } + + /** + * Returns true if the provider is able to provide speed + * information, false otherwise. A provider that reports speed + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsSpeed() { + return true; + } + + /** + * Returns true if the provider is able to provide bearing + * information, false otherwise. A provider that reports bearing + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsBearing() { + return true; + } + + /** + * Returns the power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_REQUIREMENT_*. + */ + public int getPowerRequirement() { + return Criteria.POWER_HIGH; + } + + /** + * Returns true if this provider meets the given criteria, + * false otherwise. + */ + public boolean meetsCriteria(Criteria criteria) { + return (criteria.getPowerRequirement() != Criteria.POWER_LOW); + } + + /** + * Returns the horizontal accuracy of this provider + * + * @return the accuracy of location from this provider, as one + * of the constants Criteria.ACCURACY_*. + */ + public int getAccuracy() { + return Criteria.ACCURACY_FINE; + } + + /** + * Enables this provider. When enabled, calls to getStatus() + * must be handled. Hardware may be started up + * when the provider is enabled. + */ + public void enable() { + synchronized (mHandler) { + sendMessage(ENABLE, 1, null); + } + } + + private void handleEnable() { + if (DEBUG) Log.d(TAG, "handleEnable"); + if (mEnabled) return; + mEnabled = native_init(); + + if (mEnabled) { + if (mSuplServerHost != null) { + native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); + } + if (mC2KServerHost != null) { + native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); + } + } else { + Log.w(TAG, "Failed to enable location provider"); + } + } + + /** + * Disables this provider. When disabled, calls to getStatus() + * need not be handled. Hardware may be shut + * down while the provider is disabled. + */ + public void disable() { + synchronized (mHandler) { + sendMessage(ENABLE, 0, null); + } + } + + private void handleDisable() { + if (DEBUG) Log.d(TAG, "handleDisable"); + if (!mEnabled) return; + + mEnabled = false; + stopNavigating(); + + // do this before releasing wakelock + native_cleanup(); + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + if (extras != null) { + extras.putInt("satellites", mSvCount); + } + return mStatus; + } + + private void updateStatus(int status, int svCount) { + if (status != mStatus || svCount != mSvCount) { + mStatus = status; + mSvCount = svCount; + mLocationExtras.putInt("satellites", svCount); + mStatusUpdateTime = SystemClock.elapsedRealtime(); + } + } + + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + public void enableLocationTracking(boolean enable) { + // FIXME - should set a flag here to avoid race conditions with single shot request + synchronized (mHandler) { + sendMessage(ENABLE_TRACKING, (enable ? 1 : 0), null); + } + } + + private void handleEnableLocationTracking(boolean enable) { + if (enable) { + mTTFF = 0; + mLastFixTime = 0; + startNavigating(false); + } else { + if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); + } + stopNavigating(); + } + } + + public boolean requestSingleShotFix() { + if (mStarted) { + // cannot do single shot if already navigating + return false; + } + synchronized (mHandler) { + mHandler.removeMessages(REQUEST_SINGLE_SHOT); + Message m = Message.obtain(mHandler, REQUEST_SINGLE_SHOT); + mHandler.sendMessage(m); + } + return true; + } + + private void handleRequestSingleShot() { + mTTFF = 0; + mLastFixTime = 0; + startNavigating(true); + } + + public void setMinTime(long minTime) { + if (DEBUG) Log.d(TAG, "setMinTime " + minTime); + + if (minTime >= 0) { + mFixInterval = (int)minTime; + + if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + mFixInterval, 0, 0)) { + Log.e(TAG, "set_position_mode failed in setMinTime()"); + } + } + } + } + + public String getInternalState() { + return native_get_internal_state(); + } + + private final class Listener implements IBinder.DeathRecipient { + final IGpsStatusListener mListener; + + int mSensors = 0; + + Listener(IGpsStatusListener listener) { + mListener = listener; + } + + public void binderDied() { + if (DEBUG) Log.d(TAG, "GPS status listener died"); + + synchronized(mListeners) { + mListeners.remove(this); + } + if (mListener != null) { + mListener.asBinder().unlinkToDeath(this, 0); + } + } + } + + public void addListener(int uid) { + synchronized (mWakeLock) { + mPendingListenerMessages++; + mWakeLock.acquire(); + Message m = Message.obtain(mHandler, ADD_LISTENER); + m.arg1 = uid; + mHandler.sendMessage(m); + } + } + + private void handleAddListener(int uid) { + synchronized(mListeners) { + if (mClientUids.indexOfKey(uid) >= 0) { + // Shouldn't be here -- already have this uid. + Log.w(TAG, "Duplicate add listener for uid " + uid); + return; + } + mClientUids.put(uid, 0); + if (mNavigating) { + try { + mBatteryStats.noteStartGps(uid); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in addListener"); + } + } + } + } + + public void removeListener(int uid) { + synchronized (mWakeLock) { + mPendingListenerMessages++; + mWakeLock.acquire(); + Message m = Message.obtain(mHandler, REMOVE_LISTENER); + m.arg1 = uid; + mHandler.sendMessage(m); + } + } + + private void handleRemoveListener(int uid) { + synchronized(mListeners) { + if (mClientUids.indexOfKey(uid) < 0) { + // Shouldn't be here -- don't have this uid. + Log.w(TAG, "Unneeded remove listener for uid " + uid); + return; + } + mClientUids.delete(uid); + if (mNavigating) { + try { + mBatteryStats.noteStopGps(uid); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in removeListener"); + } + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + + long identity = Binder.clearCallingIdentity(); + boolean result = false; + + if ("delete_aiding_data".equals(command)) { + result = deleteAidingData(extras); + } else if ("force_time_injection".equals(command)) { + sendMessage(INJECT_NTP_TIME, 0, null); + result = true; + } else if ("force_xtra_injection".equals(command)) { + if (native_supports_xtra()) { + xtraDownloadRequest(); + result = true; + } + } else { + Log.w(TAG, "sendExtraCommand: unknown command " + command); + } + + Binder.restoreCallingIdentity(identity); + return result; + } + + private boolean deleteAidingData(Bundle extras) { + int flags; + + if (extras == null) { + flags = GPS_DELETE_ALL; + } else { + flags = 0; + if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS; + if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC; + if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION; + if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME; + if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO; + if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC; + if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH; + if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR; + if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER; + if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA; + if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI; + if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO; + if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL; + } + + if (flags != 0) { + native_delete_aiding_data(flags); + return true; + } + + return false; + } + + private void startNavigating(boolean singleShot) { + if (!mStarted) { + if (DEBUG) Log.d(TAG, "startNavigating"); + mStarted = true; + mSingleShot = singleShot; + mPositionMode = GPS_POSITION_MODE_STANDALONE; + + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { + if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { + mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; + } else if (hasCapability(GPS_CAPABILITY_MSB)) { + mPositionMode = GPS_POSITION_MODE_MS_BASED; + } + } + + int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); + if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, + interval, 0, 0)) { + mStarted = false; + Log.e(TAG, "set_position_mode failed in startNavigating()"); + return; + } + if (!native_start()) { + mStarted = false; + Log.e(TAG, "native_start failed in startNavigating()"); + return; + } + + // reset SV count to zero + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); + mFixRequestTime = System.currentTimeMillis(); + if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT + // and our fix interval is not short + if (mFixInterval >= NO_FIX_TIMEOUT) { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent); + } + } + } + } + + private void stopNavigating() { + if (DEBUG) Log.d(TAG, "stopNavigating"); + if (mStarted) { + mStarted = false; + mSingleShot = false; + native_stop(); + mTTFF = 0; + mLastFixTime = 0; + mLocationFlags = LOCATION_INVALID; + + // reset SV count to zero + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0); + } + } + + private void hibernate() { + // stop GPS until our next fix interval arrives + stopNavigating(); + mAlarmManager.cancel(mTimeoutIntent); + mAlarmManager.cancel(mWakeupIntent); + long now = SystemClock.elapsedRealtime(); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + mFixInterval, mWakeupIntent); + } + + private boolean hasCapability(int capability) { + return ((mEngineCapabilities & capability) != 0); + } + + /** + * called from native code to update our position. + */ + private void reportLocation(int flags, double latitude, double longitude, double altitude, + float speed, float bearing, float accuracy, long timestamp) { + if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + + " timestamp: " + timestamp); + + synchronized (mLocation) { + mLocationFlags = flags; + if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mLocation.setLatitude(latitude); + mLocation.setLongitude(longitude); + mLocation.setTime(timestamp); + } + if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { + mLocation.setAltitude(altitude); + } else { + mLocation.removeAltitude(); + } + if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { + mLocation.setSpeed(speed); + } else { + mLocation.removeSpeed(); + } + if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { + mLocation.setBearing(bearing); + } else { + mLocation.removeBearing(); + } + if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { + mLocation.setAccuracy(accuracy); + } else { + mLocation.removeAccuracy(); + } + + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + + mLastFixTime = System.currentTimeMillis(); + // report time to first fix + if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mTTFF = (int)(mLastFixTime - mFixRequestTime); + if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF); + + // notify status listeners + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onFirstFix(mTTFF); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in stopNavigating"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + + if (mSingleShot) { + stopNavigating(); + } + if (mStarted && mStatus != LocationProvider.AVAILABLE) { + // we want to time out if we do not receive a fix + // within the time out and we are requesting infrequent fixes + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) { + mAlarmManager.cancel(mTimeoutIntent); + } + + // send an intent to notify that the GPS is receiving fixes. + Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true); + mContext.sendBroadcast(intent); + updateStatus(LocationProvider.AVAILABLE, mSvCount); + } + + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted && mFixInterval > 1000) { + if (DEBUG) Log.d(TAG, "got fix, hibernating"); + hibernate(); + } + } + + /** + * called from native code to update our status + */ + private void reportStatus(int status) { + if (VERBOSE) Log.v(TAG, "reportStatus status: " + status); + + synchronized(mListeners) { + boolean wasNavigating = mNavigating; + + switch (status) { + case GPS_STATUS_SESSION_BEGIN: + mNavigating = true; + mEngineOn = true; + break; + case GPS_STATUS_SESSION_END: + mNavigating = false; + break; + case GPS_STATUS_ENGINE_ON: + mEngineOn = true; + break; + case GPS_STATUS_ENGINE_OFF: + mEngineOn = false; + mNavigating = false; + break; + } + + if (wasNavigating != mNavigating) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + if (mNavigating) { + listener.mListener.onGpsStarted(); + } else { + listener.mListener.onGpsStopped(); + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + + try { + // update battery stats + for (int i=mClientUids.size() - 1; i >= 0; i--) { + int uid = mClientUids.keyAt(i); + if (mNavigating) { + mBatteryStats.noteStartGps(uid); + } else { + mBatteryStats.noteStopGps(uid); + } + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + } + + // send an intent to notify that the GPS has been enabled or disabled. + Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating); + mContext.sendBroadcast(intent); + } + } + } + + /** + * called from native code to update SV info + */ + private void reportSvStatus() { + + int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); + + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, + mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], + mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportSvInfo"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + + if (VERBOSE) { + Log.v(TAG, "SV count: " + svCount + + " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) + + " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); + for (int i = 0; i < svCount; i++) { + Log.v(TAG, "sv: " + mSvs[i] + + " snr: " + (float)mSnrs[i]/10 + + " elev: " + mSvElevations[i] + + " azimuth: " + mSvAzimuths[i] + + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + + ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " A") + + ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U")); + } + } + + // return number of sets used in fix instead of total + updateStatus(mStatus, Integer.bitCount(mSvMasks[USED_FOR_FIX_MASK])); + + if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 && + System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) { + // send an intent to notify that the GPS is no longer receiving fixes. + Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); + intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false); + mContext.sendBroadcast(intent); + updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount); + } + } + + /** + * called from native code to update AGPS status + */ + private void reportAGpsStatus(int type, int status) { + switch (status) { + case GPS_REQUEST_AGPS_DATA_CONN: + int result = mConnMgr.startUsingNetworkFeature( + ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + if (result == Phone.APN_ALREADY_ACTIVE) { + if (mAGpsApn != null) { + native_agps_data_conn_open(mAGpsApn); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + Log.e(TAG, "mAGpsApn not set when receiving Phone.APN_ALREADY_ACTIVE"); + native_agps_data_conn_failed(); + } + } else if (result == Phone.APN_REQUEST_STARTED) { + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING; + } else { + native_agps_data_conn_failed(); + } + break; + case GPS_RELEASE_AGPS_DATA_CONN: + if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) { + mConnMgr.stopUsingNetworkFeature( + ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL); + native_agps_data_conn_closed(); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + } + break; + case GPS_AGPS_DATA_CONNECTED: + // Log.d(TAG, "GPS_AGPS_DATA_CONNECTED"); + break; + case GPS_AGPS_DATA_CONN_DONE: + // Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE"); + break; + case GPS_AGPS_DATA_CONN_FAILED: + // Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED"); + break; + } + } + + /** + * called from native code to report NMEA data received + */ + private void reportNmea(long timestamp) { + synchronized(mListeners) { + int size = mListeners.size(); + if (size > 0) { + // don't bother creating the String if we have no listeners + int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length); + String nmea = new String(mNmeaBuffer, 0, length); + + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onNmeaReceived(timestamp, nmea); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportNmea"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + } + + /** + * called from native code to inform us what the GPS engine capabilities are + */ + private void setEngineCapabilities(int capabilities) { + mEngineCapabilities = capabilities; + } + + /** + * called from native code to request XTRA data + */ + private void xtraDownloadRequest() { + if (DEBUG) Log.d(TAG, "xtraDownloadRequest"); + sendMessage(DOWNLOAD_XTRA_DATA, 0, null); + } + + //============================================================= + // NI Client support + //============================================================= + private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { + // Sends a response for an NI reqeust to HAL. + public boolean sendNiResponse(int notificationId, int userResponse) + { + // TODO Add Permission check + + StringBuilder extrasBuf = new StringBuilder(); + + if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + + ", response: " + userResponse); + + native_send_ni_response(notificationId, userResponse); + + return true; + } + }; + + public INetInitiatedListener getNetInitiatedListener() { + return mNetInitiatedListener; + } + + // Called by JNI function to report an NI request. + @SuppressWarnings("deprecation") + public void reportNiNotification( + int notificationId, + int niType, + int notifyFlags, + int timeout, + int defaultResponse, + String requestorId, + String text, + int requestorIdEncoding, + int textEncoding, + String extras // Encoded extra data + ) + { + Log.i(TAG, "reportNiNotification: entered"); + Log.i(TAG, "notificationId: " + notificationId + + ", niType: " + niType + + ", notifyFlags: " + notifyFlags + + ", timeout: " + timeout + + ", defaultResponse: " + defaultResponse); + + Log.i(TAG, "requestorId: " + requestorId + + ", text: " + text + + ", requestorIdEncoding: " + requestorIdEncoding + + ", textEncoding: " + textEncoding); + + GpsNiNotification notification = new GpsNiNotification(); + + notification.notificationId = notificationId; + notification.niType = niType; + notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; + notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; + notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; + notification.timeout = timeout; + notification.defaultResponse = defaultResponse; + notification.requestorId = requestorId; + notification.text = text; + notification.requestorIdEncoding = requestorIdEncoding; + notification.textEncoding = textEncoding; + + // Process extras, assuming the format is + // one of more lines of "key = value" + Bundle bundle = new Bundle(); + + if (extras == null) extras = ""; + Properties extraProp = new Properties(); + + try { + extraProp.load(new StringBufferInputStream(extras)); + } + catch (IOException e) + { + Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras); + } + + for (Entry<Object, Object> ent : extraProp.entrySet()) + { + bundle.putString((String) ent.getKey(), (String) ent.getValue()); + } + + notification.extras = bundle; + + mNIHandler.handleNiNotification(notification); + } + + private void sendMessage(int message, int arg, Object obj) { + // hold a wake lock while messages are pending + synchronized (mWakeLock) { + mPendingMessageBits |= (1 << message); + mWakeLock.acquire(); + mHandler.removeMessages(message); + Message m = Message.obtain(mHandler, message); + m.arg1 = arg; + m.obj = obj; + mHandler.sendMessage(m); + } + } + + private final class ProviderHandler extends Handler { + @Override + public void handleMessage(Message msg) + { + int message = msg.what; + switch (message) { + case ENABLE: + if (msg.arg1 == 1) { + handleEnable(); + } else { + handleDisable(); + } + break; + case ENABLE_TRACKING: + handleEnableLocationTracking(msg.arg1 == 1); + break; + case REQUEST_SINGLE_SHOT: + handleRequestSingleShot(); + break; + case UPDATE_NETWORK_STATE: + handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); + break; + case INJECT_NTP_TIME: + handleInjectNtpTime(); + break; + case DOWNLOAD_XTRA_DATA: + if (native_supports_xtra()) { + handleDownloadXtraData(); + } + break; + case UPDATE_LOCATION: + handleUpdateLocation((Location)msg.obj); + break; + case ADD_LISTENER: + handleAddListener(msg.arg1); + break; + case REMOVE_LISTENER: + handleRemoveListener(msg.arg1); + break; + } + // release wake lock if no messages are pending + synchronized (mWakeLock) { + mPendingMessageBits &= ~(1 << message); + if (message == ADD_LISTENER || message == REMOVE_LISTENER) { + mPendingListenerMessages--; + } + if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) { + mWakeLock.release(); + } + } + } + }; + + private final class GpsLocationProviderThread extends Thread { + + public GpsLocationProviderThread() { + super("GpsLocationProvider"); + } + + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + initialize(); + Looper.prepare(); + mHandler = new ProviderHandler(); + // signal when we are initialized and ready to go + mInitializedLatch.countDown(); + Looper.loop(); + } + } + + // for GPS SV statistics + private static final int MAX_SVS = 32; + private static final int EPHEMERIS_MASK = 0; + private static final int ALMANAC_MASK = 1; + private static final int USED_FOR_FIX_MASK = 2; + + // preallocated arrays, to avoid memory allocation in reportStatus() + private int mSvs[] = new int[MAX_SVS]; + private float mSnrs[] = new float[MAX_SVS]; + private float mSvElevations[] = new float[MAX_SVS]; + private float mSvAzimuths[] = new float[MAX_SVS]; + private int mSvMasks[] = new int[3]; + private int mSvCount; + // preallocated to avoid memory allocation in reportNmea() + private byte[] mNmeaBuffer = new byte[120]; + + static { class_init_native(); } + private static native void class_init_native(); + private static native boolean native_is_supported(); + + private native boolean native_init(); + private native void native_cleanup(); + private native boolean native_set_position_mode(int mode, int recurrence, int min_interval, + int preferred_accuracy, int preferred_time); + private native boolean native_start(); + private native boolean native_stop(); + private native void native_delete_aiding_data(int flags); + // returns number of SVs + // mask[0] is ephemeris mask and mask[1] is almanac mask + private native int native_read_sv_status(int[] svs, float[] snrs, + float[] elevations, float[] azimuths, int[] masks); + private native int native_read_nmea(byte[] buffer, int bufferSize); + private native void native_inject_location(double latitude, double longitude, float accuracy); + + // XTRA Support + private native void native_inject_time(long time, long timeReference, int uncertainty); + private native boolean native_supports_xtra(); + private native void native_inject_xtra_data(byte[] data, int length); + + // DEBUG Support + private native String native_get_internal_state(); + + // AGPS Support + private native void native_agps_data_conn_open(String apn); + private native void native_agps_data_conn_closed(); + private native void native_agps_data_conn_failed(); + private native void native_set_agps_server(int type, String hostname, int port); + + // Network-initiated (NI) Support + private native void native_send_ni_response(int notificationId, int userResponse); +} diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java new file mode 100644 index 0000000..bc96980 --- /dev/null +++ b/services/java/com/android/server/location/GpsXtraDownloader.java @@ -0,0 +1,171 @@ +/* + * 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.location; + +import android.content.Context; +import android.net.Proxy; +import android.net.http.AndroidHttpClient; +import android.util.Config; +import android.util.Log; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.params.ConnRouteParams; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Random; + +/** + * A class for downloading GPS XTRA data. + * + * {@hide} + */ +public class GpsXtraDownloader { + + private static final String TAG = "GpsXtraDownloader"; + + private Context mContext; + private String[] mXtraServers; + // to load balance our server requests + private int mNextServerIndex; + + GpsXtraDownloader(Context context, Properties properties) { + mContext = context; + + // read XTRA servers from the Properties object + int count = 0; + String server1 = properties.getProperty("XTRA_SERVER_1"); + String server2 = properties.getProperty("XTRA_SERVER_2"); + String server3 = properties.getProperty("XTRA_SERVER_3"); + if (server1 != null) count++; + if (server2 != null) count++; + if (server3 != null) count++; + + if (count == 0) { + Log.e(TAG, "No XTRA servers were specified in the GPS configuration"); + return; + } else { + mXtraServers = new String[count]; + count = 0; + if (server1 != null) mXtraServers[count++] = server1; + if (server2 != null) mXtraServers[count++] = server2; + if (server3 != null) mXtraServers[count++] = server3; + + // randomize first server + Random random = new Random(); + mNextServerIndex = random.nextInt(count); + } + } + + byte[] downloadXtraData() { + String proxyHost = Proxy.getHost(mContext); + int proxyPort = Proxy.getPort(mContext); + boolean useProxy = (proxyHost != null && proxyPort != -1); + byte[] result = null; + int startIndex = mNextServerIndex; + + if (mXtraServers == null) { + return null; + } + + // load balance our requests among the available servers + while (result == null) { + result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort); + + // increment mNextServerIndex and wrap around if necessary + mNextServerIndex++; + if (mNextServerIndex == mXtraServers.length) { + mNextServerIndex = 0; + } + // break if we have tried all the servers + if (mNextServerIndex == startIndex) break; + } + + return result; + } + + protected static byte[] doDownload(String url, boolean isProxySet, + String proxyHost, int proxyPort) { + if (Config.LOGD) Log.d(TAG, "Downloading XTRA data from " + url); + + AndroidHttpClient client = null; + try { + client = AndroidHttpClient.newInstance("Android"); + HttpUriRequest req = new HttpGet(url); + + if (isProxySet) { + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + ConnRouteParams.setDefaultProxy(req.getParams(), proxy); + } + + req.addHeader( + "Accept", + "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"); + + req.addHeader( + "x-wap-profile", + "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#"); + + HttpResponse response = client.execute(req); + StatusLine status = response.getStatusLine(); + if (status.getStatusCode() != 200) { // HTTP 200 is success. + if (Config.LOGD) Log.d(TAG, "HTTP error: " + status.getReasonPhrase()); + return null; + } + + HttpEntity entity = response.getEntity(); + byte[] body = null; + if (entity != null) { + try { + if (entity.getContentLength() > 0) { + body = new byte[(int) entity.getContentLength()]; + DataInputStream dis = new DataInputStream(entity.getContent()); + try { + dis.readFully(body); + } finally { + try { + dis.close(); + } catch (IOException e) { + Log.e(TAG, "Unexpected IOException.", e); + } + } + } + } finally { + if (entity != null) { + entity.consumeContent(); + } + } + } + return body; + } catch (Exception e) { + if (Config.LOGD) Log.d(TAG, "error " + e); + } finally { + if (client != null) { + client.close(); + } + } + return null; + } + +} diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java new file mode 100644 index 0000000..084ab81 --- /dev/null +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -0,0 +1,56 @@ +/* + * 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.location; + +import android.location.Criteria; +import android.location.Location; +import android.net.NetworkInfo; +import android.os.Bundle; + +/** + * Location Manager's interface for location providers. + * + * {@hide} + */ +public interface LocationProviderInterface { + String getName(); + boolean requiresNetwork(); + boolean requiresSatellite(); + boolean requiresCell(); + boolean hasMonetaryCost(); + boolean supportsAltitude(); + boolean supportsSpeed(); + boolean supportsBearing(); + int getPowerRequirement(); + boolean meetsCriteria(Criteria criteria); + int getAccuracy(); + boolean isEnabled(); + void enable(); + void disable(); + int getStatus(Bundle extras); + long getStatusUpdateTime(); + void enableLocationTracking(boolean enable); + /* returns false if single shot is not supported */ + boolean requestSingleShotFix(); + String getInternalState(); + void setMinTime(long minTime); + void updateNetworkState(int state, NetworkInfo info); + void updateLocation(Location location); + boolean sendExtraCommand(String command, Bundle extras); + void addListener(int uid); + void removeListener(int uid); +} diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java new file mode 100644 index 0000000..24d7737 --- /dev/null +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2009 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.location; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Criteria; +import android.location.ILocationProvider; +import android.location.Location; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.location.DummyLocationProvider; + +/** + * A class for proxying location providers implemented as services. + * + * {@hide} + */ +public class LocationProviderProxy implements LocationProviderInterface { + + private static final String TAG = "LocationProviderProxy"; + + private final Context mContext; + private final String mName; + private ILocationProvider mProvider; + private Handler mHandler; + private final Connection mServiceConnection = new Connection(); + + // cached values set by the location manager + private boolean mLocationTracking = false; + private boolean mEnabled = false; + private long mMinTime = -1; + private int mNetworkState; + private NetworkInfo mNetworkInfo; + + // for caching requiresNetwork, requiresSatellite, etc. + private DummyLocationProvider mCachedAttributes; + + // constructor for proxying location providers implemented in a separate service + public LocationProviderProxy(Context context, String name, String serviceName, + Handler handler) { + mContext = context; + mName = name; + mHandler = handler; + mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE); + } + + private class Connection implements ServiceConnection { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className); + synchronized (this) { + mProvider = ILocationProvider.Stub.asInterface(service); + if (mProvider != null) { + mHandler.post(mServiceConnectedTask); + } + } + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className); + synchronized (this) { + mProvider = null; + } + } + } + + private Runnable mServiceConnectedTask = new Runnable() { + public void run() { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + if (provider == null) { + return; + } + } + + if (mCachedAttributes == null) { + try { + mCachedAttributes = new DummyLocationProvider(mName, null); + mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); + mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); + mCachedAttributes.setRequiresCell(provider.requiresCell()); + mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost()); + mCachedAttributes.setSupportsAltitude(provider.supportsAltitude()); + mCachedAttributes.setSupportsSpeed(provider.supportsSpeed()); + mCachedAttributes.setSupportsBearing(provider.supportsBearing()); + mCachedAttributes.setPowerRequirement(provider.getPowerRequirement()); + mCachedAttributes.setAccuracy(provider.getAccuracy()); + } catch (RemoteException e) { + mCachedAttributes = null; + } + } + + // resend previous values from the location manager if the service has restarted + try { + if (mEnabled) { + provider.enable(); + } + if (mLocationTracking) { + provider.enableLocationTracking(true); + } + if (mMinTime >= 0) { + provider.setMinTime(mMinTime); + } + if (mNetworkInfo != null) { + provider.updateNetworkState(mNetworkState, mNetworkInfo); + } + } catch (RemoteException e) { + } + } + }; + + public String getName() { + return mName; + } + + public boolean requiresNetwork() { + if (mCachedAttributes != null) { + return mCachedAttributes.requiresNetwork(); + } else { + return false; + } + } + + public boolean requiresSatellite() { + if (mCachedAttributes != null) { + return mCachedAttributes.requiresSatellite(); + } else { + return false; + } + } + + public boolean requiresCell() { + if (mCachedAttributes != null) { + return mCachedAttributes.requiresCell(); + } else { + return false; + } + } + + public boolean hasMonetaryCost() { + if (mCachedAttributes != null) { + return mCachedAttributes.hasMonetaryCost(); + } else { + return false; + } + } + + public boolean supportsAltitude() { + if (mCachedAttributes != null) { + return mCachedAttributes.supportsAltitude(); + } else { + return false; + } + } + + public boolean supportsSpeed() { + if (mCachedAttributes != null) { + return mCachedAttributes.supportsSpeed(); + } else { + return false; + } + } + + public boolean supportsBearing() { + if (mCachedAttributes != null) { + return mCachedAttributes.supportsBearing(); + } else { + return false; + } + } + + public int getPowerRequirement() { + if (mCachedAttributes != null) { + return mCachedAttributes.getPowerRequirement(); + } else { + return -1; + } + } + + public boolean meetsCriteria(Criteria criteria) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.meetsCriteria(criteria); + } catch (RemoteException e) { + } + } + // default implementation if we lost connection to the provider + if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && + (criteria.getAccuracy() < getAccuracy())) { + return false; + } + int criteriaPower = criteria.getPowerRequirement(); + if ((criteriaPower != Criteria.NO_REQUIREMENT) && + (criteriaPower < getPowerRequirement())) { + return false; + } + if (criteria.isAltitudeRequired() && !supportsAltitude()) { + return false; + } + if (criteria.isSpeedRequired() && !supportsSpeed()) { + return false; + } + if (criteria.isBearingRequired() && !supportsBearing()) { + return false; + } + return true; + } + + public int getAccuracy() { + if (mCachedAttributes != null) { + return mCachedAttributes.getAccuracy(); + } else { + return -1; + } + } + + public void enable() { + mEnabled = true; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.enable(); + } catch (RemoteException e) { + } + } + } + + public void disable() { + mEnabled = false; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.disable(); + } catch (RemoteException e) { + } + } + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getStatus(extras); + } catch (RemoteException e) { + } + } + return 0; + } + + public long getStatusUpdateTime() { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + return provider.getStatusUpdateTime(); + } catch (RemoteException e) { + } + } + return 0; + } + + public String getInternalState() { + try { + return mProvider.getInternalState(); + } catch (RemoteException e) { + Log.e(TAG, "getInternalState failed", e); + return null; + } + } + + public boolean isLocationTracking() { + return mLocationTracking; + } + + public void enableLocationTracking(boolean enable) { + mLocationTracking = enable; + if (!enable) { + mMinTime = -1; + } + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.enableLocationTracking(enable); + } catch (RemoteException e) { + } + } + } + + public boolean requestSingleShotFix() { + return false; + } + + public long getMinTime() { + return mMinTime; + } + + public void setMinTime(long minTime) { + mMinTime = minTime; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.setMinTime(minTime); + } catch (RemoteException e) { + } + } + } + + public void updateNetworkState(int state, NetworkInfo info) { + mNetworkState = state; + mNetworkInfo = info; + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.updateNetworkState(state, info); + } catch (RemoteException e) { + } + } + } + + public void updateLocation(Location location) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.updateLocation(location); + } catch (RemoteException e) { + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.sendExtraCommand(command, extras); + } catch (RemoteException e) { + } + } + return false; + } + + public void addListener(int uid) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.addListener(uid); + } catch (RemoteException e) { + } + } + } + + public void removeListener(int uid) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + if (provider != null) { + try { + provider.removeListener(uid); + } catch (RemoteException e) { + } + } + } +} diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java new file mode 100644 index 0000000..01b34b7 --- /dev/null +++ b/services/java/com/android/server/location/MockProvider.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2009 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.location; + +import android.location.Criteria; +import android.location.ILocationManager; +import android.location.Location; +import android.location.LocationProvider; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; +import android.util.PrintWriterPrinter; + +import java.io.PrintWriter; + +/** + * A mock location provider used by LocationManagerService to implement test providers. + * + * {@hide} + */ +public class MockProvider implements LocationProviderInterface { + private final String mName; + private final ILocationManager mLocationManager; + private final boolean mRequiresNetwork; + private final boolean mRequiresSatellite; + private final boolean mRequiresCell; + private final boolean mHasMonetaryCost; + private final boolean mSupportsAltitude; + private final boolean mSupportsSpeed; + private final boolean mSupportsBearing; + private final int mPowerRequirement; + private final int mAccuracy; + private final Location mLocation; + private int mStatus; + private long mStatusUpdateTime; + private final Bundle mExtras = new Bundle(); + private boolean mHasLocation; + private boolean mHasStatus; + private boolean mEnabled; + + private static final String TAG = "MockProvider"; + + public MockProvider(String name, ILocationManager locationManager, + boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + mName = name; + mLocationManager = locationManager; + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsBearing = supportsBearing; + mSupportsSpeed = supportsSpeed; + mPowerRequirement = powerRequirement; + mAccuracy = accuracy; + mLocation = new Location(name); + } + + public String getName() { + return mName; + } + + public void disable() { + mEnabled = false; + } + + public void enable() { + mEnabled = true; + } + + public boolean isEnabled() { + return mEnabled; + } + + public int getStatus(Bundle extras) { + if (mHasStatus) { + extras.clear(); + extras.putAll(mExtras); + return mStatus; + } else { + return LocationProvider.AVAILABLE; + } + } + + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + public int getAccuracy() { + return mAccuracy; + } + + public int getPowerRequirement() { + return mPowerRequirement; + } + + public boolean hasMonetaryCost() { + return mHasMonetaryCost; + } + + public boolean requiresCell() { + return mRequiresCell; + } + + public boolean requiresNetwork() { + return mRequiresNetwork; + } + + public boolean requiresSatellite() { + return mRequiresSatellite; + } + + public boolean supportsAltitude() { + return mSupportsAltitude; + } + + public boolean supportsBearing() { + return mSupportsBearing; + } + + public boolean supportsSpeed() { + return mSupportsSpeed; + } + + public boolean meetsCriteria(Criteria criteria) { + if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && + (criteria.getAccuracy() < mAccuracy)) { + return false; + } + int criteriaPower = criteria.getPowerRequirement(); + if ((criteriaPower != Criteria.NO_REQUIREMENT) && + (criteriaPower < mPowerRequirement)) { + return false; + } + if (criteria.isAltitudeRequired() && !mSupportsAltitude) { + return false; + } + if (criteria.isSpeedRequired() && !mSupportsSpeed) { + return false; + } + if (criteria.isBearingRequired() && !mSupportsBearing) { + return false; + } + return true; + } + + public void setLocation(Location l) { + mLocation.set(l); + mHasLocation = true; + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + + public void clearLocation() { + mHasLocation = false; + } + + public void setStatus(int status, Bundle extras, long updateTime) { + mStatus = status; + mStatusUpdateTime = updateTime; + mExtras.clear(); + if (extras != null) { + mExtras.putAll(extras); + } + mHasStatus = true; + } + + public void clearStatus() { + mHasStatus = false; + mStatusUpdateTime = 0; + } + + public String getInternalState() { + return null; + } + + public void enableLocationTracking(boolean enable) { + } + + public boolean requestSingleShotFix() { + return false; + } + + public void setMinTime(long minTime) { + } + + public void updateNetworkState(int state, NetworkInfo info) { + } + + public void updateLocation(Location location) { + } + + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } + + public void addListener(int uid) { + } + + public void removeListener(int uid) { + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + mName); + pw.println(prefix + "mHasLocation=" + mHasLocation); + pw.println(prefix + "mLocation:"); + mLocation.dump(new PrintWriterPrinter(pw), prefix + " "); + pw.println(prefix + "mHasStatus=" + mHasStatus); + pw.println(prefix + "mStatus=" + mStatus); + pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime); + pw.println(prefix + "mExtras=" + mExtras); + } +} diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java new file mode 100644 index 0000000..7fc93f8 --- /dev/null +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -0,0 +1,152 @@ +/* + * 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.location; + +import android.location.Criteria; +import android.location.ILocationManager; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +/** + * A passive location provider reports locations received from other providers + * for clients that want to listen passively without actually triggering + * location updates. + * + * {@hide} + */ +public class PassiveProvider implements LocationProviderInterface { + + private static final String TAG = "PassiveProvider"; + + private final ILocationManager mLocationManager; + private boolean mTracking; + + public PassiveProvider(ILocationManager locationManager) { + mLocationManager = locationManager; + } + + public String getName() { + return LocationManager.PASSIVE_PROVIDER; + } + + public boolean requiresNetwork() { + return false; + } + + public boolean requiresSatellite() { + return false; + } + + public boolean requiresCell() { + return false; + } + + public boolean hasMonetaryCost() { + return false; + } + + public boolean supportsAltitude() { + return false; + } + + public boolean supportsSpeed() { + return false; + } + + public boolean supportsBearing() { + return false; + } + + public int getPowerRequirement() { + return -1; + } + + public boolean meetsCriteria(Criteria criteria) { + // We do not want to match the special passive provider based on criteria. + return false; + } + + public int getAccuracy() { + return -1; + } + + public boolean isEnabled() { + return true; + } + + public void enable() { + } + + public void disable() { + } + + public int getStatus(Bundle extras) { + if (mTracking) { + return LocationProvider.AVAILABLE; + } else { + return LocationProvider.TEMPORARILY_UNAVAILABLE; + } + } + + public long getStatusUpdateTime() { + return -1; + } + + public String getInternalState() { + return null; + } + + public void enableLocationTracking(boolean enable) { + mTracking = enable; + } + + public boolean requestSingleShotFix() { + return false; + } + + public void setMinTime(long minTime) { + } + + public void updateNetworkState(int state, NetworkInfo info) { + } + + public void updateLocation(Location location) { + if (mTracking) { + try { + // pass the location back to the location manager + mLocationManager.reportLocation(location, true); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } + } + } + + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } + + public void addListener(int uid) { + } + + public void removeListener(int uid) { + } +} diff --git a/services/java/com/android/server/sip/SipHelper.java b/services/java/com/android/server/sip/SipHelper.java new file mode 100644 index 0000000..83eeb84 --- /dev/null +++ b/services/java/com/android/server/sip/SipHelper.java @@ -0,0 +1,452 @@ +/* + * 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.sip; + +import gov.nist.javax.sip.SipStackExt; +import gov.nist.javax.sip.clientauthutils.AccountManager; +import gov.nist.javax.sip.clientauthutils.AuthenticationHelper; + +import android.net.sip.SessionDescription; +import android.net.sip.SipProfile; +import android.util.Log; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.EventObject; +import java.util.List; +import javax.sip.ClientTransaction; +import javax.sip.Dialog; +import javax.sip.DialogTerminatedEvent; +import javax.sip.InvalidArgumentException; +import javax.sip.ListeningPoint; +import javax.sip.PeerUnavailableException; +import javax.sip.RequestEvent; +import javax.sip.ResponseEvent; +import javax.sip.ServerTransaction; +import javax.sip.SipException; +import javax.sip.SipFactory; +import javax.sip.SipProvider; +import javax.sip.SipStack; +import javax.sip.Transaction; +import javax.sip.TransactionAlreadyExistsException; +import javax.sip.TransactionTerminatedEvent; +import javax.sip.TransactionUnavailableException; +import javax.sip.TransactionState; +import javax.sip.address.Address; +import javax.sip.address.AddressFactory; +import javax.sip.address.SipURI; +import javax.sip.header.CSeqHeader; +import javax.sip.header.CallIdHeader; +import javax.sip.header.ContactHeader; +import javax.sip.header.FromHeader; +import javax.sip.header.Header; +import javax.sip.header.HeaderFactory; +import javax.sip.header.MaxForwardsHeader; +import javax.sip.header.ToHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.Message; +import javax.sip.message.MessageFactory; +import javax.sip.message.Request; +import javax.sip.message.Response; + +/** + * Helper class for holding SIP stack related classes and for various low-level + * SIP tasks like sending messages. + */ +class SipHelper { + private static final String TAG = SipHelper.class.getSimpleName(); + + private SipStack mSipStack; + private SipProvider mSipProvider; + private AddressFactory mAddressFactory; + private HeaderFactory mHeaderFactory; + private MessageFactory mMessageFactory; + + public SipHelper(SipStack sipStack, SipProvider sipProvider) + throws PeerUnavailableException { + mSipStack = sipStack; + mSipProvider = sipProvider; + + SipFactory sipFactory = SipFactory.getInstance(); + mAddressFactory = sipFactory.createAddressFactory(); + mHeaderFactory = sipFactory.createHeaderFactory(); + mMessageFactory = sipFactory.createMessageFactory(); + } + + private FromHeader createFromHeader(SipProfile profile, String tag) + throws ParseException { + return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag); + } + + private ToHeader createToHeader(SipProfile profile) throws ParseException { + return createToHeader(profile, null); + } + + private ToHeader createToHeader(SipProfile profile, String tag) + throws ParseException { + return mHeaderFactory.createToHeader(profile.getSipAddress(), tag); + } + + private CallIdHeader createCallIdHeader() { + return mSipProvider.getNewCallId(); + } + + private CSeqHeader createCSeqHeader(String method) + throws ParseException, InvalidArgumentException { + long sequence = (long) (Math.random() * 10000); + return mHeaderFactory.createCSeqHeader(sequence, method); + } + + private MaxForwardsHeader createMaxForwardsHeader() + throws InvalidArgumentException { + return mHeaderFactory.createMaxForwardsHeader(70); + } + + private MaxForwardsHeader createMaxForwardsHeader(int max) + throws InvalidArgumentException { + return mHeaderFactory.createMaxForwardsHeader(max); + } + + private ListeningPoint getListeningPoint() throws SipException { + ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP); + if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP); + if (lp == null) { + ListeningPoint[] lps = mSipProvider.getListeningPoints(); + if ((lps != null) && (lps.length > 0)) lp = lps[0]; + } + if (lp == null) { + throw new SipException("no listening point is available"); + } + return lp; + } + + private List<ViaHeader> createViaHeaders() + throws ParseException, SipException { + List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1); + ListeningPoint lp = getListeningPoint(); + ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(), + lp.getPort(), lp.getTransport(), null); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + return viaHeaders; + } + + private ContactHeader createContactHeader(SipProfile profile) + throws ParseException, SipException { + ListeningPoint lp = getListeningPoint(); + SipURI contactURI = + createSipUri(profile.getUserName(), profile.getProtocol(), lp); + + Address contactAddress = mAddressFactory.createAddress(contactURI); + contactAddress.setDisplayName(profile.getDisplayName()); + + return mHeaderFactory.createContactHeader(contactAddress); + } + + private ContactHeader createWildcardContactHeader() { + ContactHeader contactHeader = mHeaderFactory.createContactHeader(); + contactHeader.setWildCard(); + return contactHeader; + } + + private SipURI createSipUri(String username, String transport, + ListeningPoint lp) throws ParseException { + SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress()); + try { + uri.setPort(lp.getPort()); + uri.setTransportParam(transport); + } catch (InvalidArgumentException e) { + throw new RuntimeException(e); + } + return uri; + } + + public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag) + throws SipException { + try { + Request request = createRequest(Request.OPTIONS, userProfile, tag); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } catch (Exception e) { + throw new SipException("sendKeepAlive()", e); + } + } + + public ClientTransaction sendRegister(SipProfile userProfile, String tag, + int expiry) throws SipException { + try { + Request request = createRequest(Request.REGISTER, userProfile, tag); + if (expiry == 0) { + // remove all previous registrations by wildcard + // rfc3261#section-10.2.2 + request.addHeader(createWildcardContactHeader()); + } else { + request.addHeader(createContactHeader(userProfile)); + } + request.addHeader(mHeaderFactory.createExpiresHeader(expiry)); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } catch (ParseException e) { + throw new SipException("sendRegister()", e); + } + } + + private Request createRequest(String requestType, SipProfile userProfile, + String tag) throws ParseException, SipException { + FromHeader fromHeader = createFromHeader(userProfile, tag); + ToHeader toHeader = createToHeader(userProfile); + SipURI requestURI = mAddressFactory.createSipURI("sip:" + + userProfile.getSipDomain()); + List<ViaHeader> viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(requestType); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + Request request = mMessageFactory.createRequest(requestURI, + requestType, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + Header userAgentHeader = mHeaderFactory.createHeader("User-Agent", + "SIPAUA/0.1.001"); + request.addHeader(userAgentHeader); + return request; + } + + public ClientTransaction handleChallenge(ResponseEvent responseEvent, + AccountManager accountManager) throws SipException { + AuthenticationHelper authenticationHelper = + ((SipStackExt) mSipStack).getAuthenticationHelper( + accountManager, mHeaderFactory); + ClientTransaction tid = responseEvent.getClientTransaction(); + ClientTransaction ct = authenticationHelper.handleChallenge( + responseEvent.getResponse(), tid, mSipProvider, 5); + ct.sendRequest(); + return ct; + } + + public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, + SessionDescription sessionDescription, String tag) + throws SipException { + try { + FromHeader fromHeader = createFromHeader(caller, tag); + ToHeader toHeader = createToHeader(callee); + SipURI requestURI = callee.getUri(); + List<ViaHeader> viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + + Request request = mMessageFactory.createRequest(requestURI, + Request.INVITE, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + + request.addHeader(createContactHeader(caller)); + request.setContent(sessionDescription.getContent(), + mHeaderFactory.createContentTypeHeader( + "application", sessionDescription.getType())); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + clientTransaction.sendRequest(); + return clientTransaction; + } catch (ParseException e) { + throw new SipException("sendInvite()", e); + } + } + + public ClientTransaction sendReinvite(Dialog dialog, + SessionDescription sessionDescription) throws SipException { + try { + Request request = dialog.createRequest(Request.INVITE); + request.setContent(sessionDescription.getContent(), + mHeaderFactory.createContentTypeHeader( + "application", sessionDescription.getType())); + + ClientTransaction clientTransaction = + mSipProvider.getNewClientTransaction(request); + dialog.sendRequest(clientTransaction); + return clientTransaction; + } catch (ParseException e) { + throw new SipException("sendReinvite()", e); + } + } + + private ServerTransaction getServerTransaction(RequestEvent event) + throws SipException { + ServerTransaction transaction = event.getServerTransaction(); + if (transaction == null) { + Request request = event.getRequest(); + return mSipProvider.getNewServerTransaction(request); + } else { + return transaction; + } + } + + /** + * @param event the INVITE request event + */ + public ServerTransaction sendRinging(RequestEvent event, String tag) + throws SipException { + try { + Request request = event.getRequest(); + ServerTransaction transaction = getServerTransaction(event); + + Response response = mMessageFactory.createResponse(Response.RINGING, + request); + + ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME); + toHeader.setTag(tag); + response.addHeader(toHeader); + transaction.sendResponse(response); + return transaction; + } catch (ParseException e) { + throw new SipException("sendRinging()", e); + } + } + + /** + * @param event the INVITE request event + */ + public ServerTransaction sendInviteOk(RequestEvent event, + SipProfile localProfile, SessionDescription sessionDescription, + ServerTransaction inviteTransaction) + throws SipException { + try { + Request request = event.getRequest(); + Response response = mMessageFactory.createResponse(Response.OK, + request); + response.addHeader(createContactHeader(localProfile)); + response.setContent(sessionDescription.getContent(), + mHeaderFactory.createContentTypeHeader( + "application", sessionDescription.getType())); + + if (inviteTransaction == null) { + inviteTransaction = getServerTransaction(event); + } + if (inviteTransaction.getState() != TransactionState.COMPLETED) { + inviteTransaction.sendResponse(response); + } + + return inviteTransaction; + } catch (ParseException e) { + throw new SipException("sendInviteOk()", e); + } + } + + public void sendInviteBusyHere(RequestEvent event, + ServerTransaction inviteTransaction) throws SipException { + try { + Request request = event.getRequest(); + Response response = mMessageFactory.createResponse( + Response.BUSY_HERE, request); + + if (inviteTransaction.getState() != TransactionState.COMPLETED) { + inviteTransaction.sendResponse(response); + } + } catch (ParseException e) { + throw new SipException("sendInviteBusyHere()", e); + } + } + + /** + * @param event the INVITE ACK request event + */ + public void sendInviteAck(ResponseEvent event, Dialog dialog) + throws SipException { + Response response = event.getResponse(); + long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) + .getSeqNumber(); + dialog.sendAck(dialog.createAck(cseq)); + } + + public void sendBye(Dialog dialog) throws SipException { + Request byeRequest = dialog.createRequest(Request.BYE); + Log.d(TAG, "send BYE: " + byeRequest); + dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest)); + } + + public void sendCancel(ClientTransaction inviteTransaction) + throws SipException { + Request cancelRequest = inviteTransaction.createCancel(); + mSipProvider.getNewClientTransaction(cancelRequest).sendRequest(); + } + + public void sendResponse(RequestEvent event, int responseCode) + throws SipException { + try { + getServerTransaction(event).sendResponse( + mMessageFactory.createResponse( + responseCode, event.getRequest())); + } catch (ParseException e) { + throw new SipException("sendResponse()", e); + } + } + + public void sendInviteRequestTerminated(Request inviteRequest, + ServerTransaction inviteTransaction) throws SipException { + try { + inviteTransaction.sendResponse(mMessageFactory.createResponse( + Response.REQUEST_TERMINATED, inviteRequest)); + } catch (ParseException e) { + throw new SipException("sendInviteRequestTerminated()", e); + } + } + + public static String getCallId(EventObject event) { + if (event == null) return null; + if (event instanceof RequestEvent) { + return getCallId(((RequestEvent) event).getRequest()); + } else if (event instanceof ResponseEvent) { + return getCallId(((ResponseEvent) event).getResponse()); + } else if (event instanceof DialogTerminatedEvent) { + Dialog dialog = ((DialogTerminatedEvent) event).getDialog(); + return getCallId(((DialogTerminatedEvent) event).getDialog()); + } else if (event instanceof TransactionTerminatedEvent) { + TransactionTerminatedEvent e = (TransactionTerminatedEvent) event; + return getCallId(e.isServerTransaction() + ? e.getServerTransaction() + : e.getClientTransaction()); + } else { + Object source = event.getSource(); + if (source instanceof Transaction) { + return getCallId(((Transaction) source)); + } else if (source instanceof Dialog) { + return getCallId((Dialog) source); + } + } + return ""; + } + + public static String getCallId(Transaction transaction) { + return ((transaction != null) ? getCallId(transaction.getRequest()) + : ""); + } + + private static String getCallId(Message message) { + CallIdHeader callIdHeader = + (CallIdHeader) message.getHeader(CallIdHeader.NAME); + return callIdHeader.getCallId(); + } + + private static String getCallId(Dialog dialog) { + return dialog.getCallId().getCallId(); + } +} diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java new file mode 100644 index 0000000..1142136 --- /dev/null +++ b/services/java/com/android/server/sip/SipService.java @@ -0,0 +1,1091 @@ +/* + * 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.sip; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.sip.ISipService; +import android.net.sip.ISipSession; +import android.net.sip.ISipSessionListener; +import android.net.sip.SipManager; +import android.net.sip.SipProfile; +import android.net.sip.SipSessionAdapter; +import android.net.sip.SipSessionState; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeSet; +import javax.sip.SipException; + +/** + */ +public final class SipService extends ISipService.Stub { + private static final String TAG = "SipService"; + private static final int EXPIRY_TIME = 3600; + private static final int SHORT_EXPIRY_TIME = 10; + private static final int MIN_EXPIRY_TIME = 60; + + private Context mContext; + private String mLocalIp; + private String mNetworkType; + private boolean mConnected; + private WakeupTimer mTimer; + private WifiManager.WifiLock mWifiLock; + + // SipProfile URI --> group + private Map<String, SipSessionGroupExt> mSipGroups = + new HashMap<String, SipSessionGroupExt>(); + + // session ID --> session + private Map<String, ISipSession> mPendingSessions = + new HashMap<String, ISipSession>(); + + private ConnectivityReceiver mConnectivityReceiver; + + public SipService(Context context) { + Log.v(TAG, " service started!"); + mContext = context; + mConnectivityReceiver = new ConnectivityReceiver(); + context.registerReceiver(mConnectivityReceiver, + new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + + mTimer = new WakeupTimer(context); + } + + public synchronized SipProfile[] getListOfProfiles() { + SipProfile[] profiles = new SipProfile[mSipGroups.size()]; + int i = 0; + for (SipSessionGroupExt group : mSipGroups.values()) { + profiles[i++] = group.getLocalProfile(); + } + return profiles; + } + + public void open(SipProfile localProfile) { + if (localProfile.getAutoRegistration()) { + openToReceiveCalls(localProfile); + } else { + openToMakeCalls(localProfile); + } + } + + private void openToMakeCalls(SipProfile localProfile) { + try { + createGroup(localProfile); + } catch (SipException e) { + Log.e(TAG, "openToMakeCalls()", e); + // TODO: how to send the exception back + } + } + + private void openToReceiveCalls(SipProfile localProfile) { + open3(localProfile, SipManager.SIP_INCOMING_CALL_ACTION, null); + } + + public synchronized void open3(SipProfile localProfile, + String incomingCallBroadcastAction, ISipSessionListener listener) { + if (TextUtils.isEmpty(incomingCallBroadcastAction)) { + throw new RuntimeException( + "empty broadcast action for incoming call"); + } + Log.v(TAG, "open3: " + localProfile.getUriString() + ": " + + incomingCallBroadcastAction + ": " + listener); + try { + SipSessionGroupExt group = createGroup(localProfile, + incomingCallBroadcastAction, listener); + if (localProfile.getAutoRegistration()) { + group.openToReceiveCalls(); + if (isWifiOn()) grabWifiLock(); + } + } catch (SipException e) { + Log.e(TAG, "openToReceiveCalls()", e); + // TODO: how to send the exception back + } + } + + public synchronized void close(String localProfileUri) { + SipSessionGroupExt group = mSipGroups.remove(localProfileUri); + if (group != null) { + notifyProfileRemoved(group.getLocalProfile()); + group.closeToNotReceiveCalls(); + if (isWifiOn() && !anyOpened()) releaseWifiLock(); + } + } + + public synchronized boolean isOpened(String localProfileUri) { + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + return ((group != null) ? group.isOpened() : false); + } + + public synchronized boolean isRegistered(String localProfileUri) { + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + return ((group != null) ? group.isRegistered() : false); + } + + public synchronized void setRegistrationListener(String localProfileUri, + ISipSessionListener listener) { + SipSessionGroupExt group = mSipGroups.get(localProfileUri); + if (group != null) group.setListener(listener); + } + + public synchronized ISipSession createSession(SipProfile localProfile, + ISipSessionListener listener) { + if (!mConnected) return null; + try { + SipSessionGroupExt group = createGroup(localProfile); + return group.createSession(listener); + } catch (SipException e) { + Log.w(TAG, "createSession()", e); + return null; + } + } + + public synchronized ISipSession getPendingSession(String callId) { + if (callId == null) return null; + return mPendingSessions.get(callId); + } + + private String determineLocalIp() { + try { + DatagramSocket s = new DatagramSocket(); + s.connect(InetAddress.getByName("192.168.1.1"), 80); + return s.getLocalAddress().getHostAddress(); + } catch (IOException e) { + Log.w(TAG, "determineLocalIp()", e); + // dont do anything; there should be a connectivity change going + return null; + } + } + + private SipSessionGroupExt createGroup(SipProfile localProfile) + throws SipException { + String key = localProfile.getUriString(); + SipSessionGroupExt group = mSipGroups.get(key); + if (group == null) { + group = new SipSessionGroupExt(localProfile, null, null); + mSipGroups.put(key, group); + notifyProfileAdded(localProfile); + } + return group; + } + + private SipSessionGroupExt createGroup(SipProfile localProfile, + String incomingCallBroadcastAction, ISipSessionListener listener) + throws SipException { + String key = localProfile.getUriString(); + SipSessionGroupExt group = mSipGroups.get(key); + if (group != null) { + group.setIncomingCallBroadcastAction( + incomingCallBroadcastAction); + group.setListener(listener); + } else { + group = new SipSessionGroupExt(localProfile, + incomingCallBroadcastAction, listener); + mSipGroups.put(key, group); + notifyProfileAdded(localProfile); + } + return group; + } + + private void notifyProfileAdded(SipProfile localProfile) { + Log.d(TAG, "notify: profile added: " + localProfile); + Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION); + intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString()); + mContext.sendBroadcast(intent); + } + + private void notifyProfileRemoved(SipProfile localProfile) { + Log.d(TAG, "notify: profile removed: " + localProfile); + Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION); + intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString()); + mContext.sendBroadcast(intent); + } + + private boolean anyOpened() { + for (SipSessionGroupExt group : mSipGroups.values()) { + if (group.isOpened()) return true; + } + return false; + } + + private void grabWifiLock() { + if (mWifiLock == null) { + Log.v(TAG, "acquire wifi lock"); + mWifiLock = ((WifiManager) + mContext.getSystemService(Context.WIFI_SERVICE)) + .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); + mWifiLock.acquire(); + } + } + + private void releaseWifiLock() { + if (mWifiLock != null) { + Log.v(TAG, "release wifi lock"); + mWifiLock.release(); + mWifiLock = null; + } + } + + private boolean isWifiOn() { + return "WIFI".equalsIgnoreCase(mNetworkType); + //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType)); + } + + private synchronized void onConnectivityChanged( + String type, boolean connected) { + Log.v(TAG, "onConnectivityChanged(): " + + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") + + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); + + boolean sameType = type.equals(mNetworkType); + if (!sameType && !connected) return; + + boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); + boolean isWifi = "WIFI".equalsIgnoreCase(type); + boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); + boolean wifiOn = isWifi && connected; + if (wifiOff) { + releaseWifiLock(); + } else if (wifiOn) { + if (anyOpened()) grabWifiLock(); + } + + try { + boolean wasConnected = mConnected; + mNetworkType = type; + mConnected = connected; + + if (wasConnected) { + mLocalIp = null; + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onConnectivityChanged(false); + } + } + + if (connected) { + mLocalIp = determineLocalIp(); + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onConnectivityChanged(true); + } + } + + } catch (SipException e) { + Log.e(TAG, "onConnectivityChanged()", e); + } + } + + private synchronized void addPendingSession(ISipSession session) { + try { + mPendingSessions.put(session.getCallId(), session); + } catch (RemoteException e) { + // should not happen with a local call + Log.e(TAG, "addPendingSession()", e); + } + } + + private class SipSessionGroupExt extends SipSessionAdapter { + private SipSessionGroup mSipGroup; + private String mIncomingCallBroadcastAction; + private boolean mOpened; + + private AutoRegistrationProcess mAutoRegistration = + new AutoRegistrationProcess(); + + public SipSessionGroupExt(SipProfile localProfile, + String incomingCallBroadcastAction, + ISipSessionListener listener) throws SipException { + String password = localProfile.getPassword(); + SipProfile p = duplicate(localProfile); + mSipGroup = createSipSessionGroup(mLocalIp, p, password); + mIncomingCallBroadcastAction = incomingCallBroadcastAction; + mAutoRegistration.setListener(listener); + } + + public SipProfile getLocalProfile() { + return mSipGroup.getLocalProfile(); + } + + // network connectivity is tricky because network can be disconnected + // at any instant so need to deal with exceptions carefully even when + // you think you are connected + private SipSessionGroup createSipSessionGroup(String localIp, + SipProfile localProfile, String password) throws SipException { + try { + return new SipSessionGroup(localIp, localProfile, password); + } catch (IOException e) { + // network disconnected + Log.w(TAG, "createSipSessionGroup(): network disconnected?"); + if (localIp != null) { + return createSipSessionGroup(null, localProfile, password); + } else { + // recursive + Log.wtf(TAG, "impossible!"); + throw new RuntimeException("createSipSessionGroup"); + } + } + } + + private SipProfile duplicate(SipProfile p) { + try { + return new SipProfile.Builder(p.getUserName(), p.getSipDomain()) + .setProfileName(p.getProfileName()) + .setPassword("*") + .setPort(p.getPort()) + .setProtocol(p.getProtocol()) + .setOutboundProxy(p.getProxyAddress()) + .setSendKeepAlive(p.getSendKeepAlive()) + .setAutoRegistration(p.getAutoRegistration()) + .setDisplayName(p.getDisplayName()) + .build(); + } catch (Exception e) { + Log.wtf(TAG, "duplicate()", e); + throw new RuntimeException("duplicate profile", e); + } + } + + public void setListener(ISipSessionListener listener) { + mAutoRegistration.setListener(listener); + } + + public void setIncomingCallBroadcastAction(String action) { + mIncomingCallBroadcastAction = action; + } + + public void openToReceiveCalls() throws SipException { + mOpened = true; + if (mConnected) { + mSipGroup.openToReceiveCalls(this); + mAutoRegistration.start(mSipGroup); + } + Log.v(TAG, " openToReceiveCalls: " + getUri() + ": " + + mIncomingCallBroadcastAction); + } + + public void onConnectivityChanged(boolean connected) + throws SipException { + if (connected) { + resetGroup(mLocalIp); + if (mOpened) openToReceiveCalls(); + } else { + // close mSipGroup but remember mOpened + Log.v(TAG, " close auto reg temporarily: " + getUri() + ": " + + mIncomingCallBroadcastAction); + mSipGroup.close(); + mAutoRegistration.stop(); + } + } + + private void resetGroup(String localIp) throws SipException { + try { + mSipGroup.reset(localIp); + } catch (IOException e) { + // network disconnected + Log.w(TAG, "resetGroup(): network disconnected?"); + if (localIp != null) { + resetGroup(null); // reset w/o local IP + } else { + // recursive + Log.wtf(TAG, "impossible!"); + throw new RuntimeException("resetGroup"); + } + } + } + + public void closeToNotReceiveCalls() { + mOpened = false; + mSipGroup.closeToNotReceiveCalls(); + mAutoRegistration.stop(); + Log.v(TAG, " close: " + getUri() + ": " + + mIncomingCallBroadcastAction); + } + + public ISipSession createSession(ISipSessionListener listener) { + return mSipGroup.createSession(listener); + } + + @Override + public void onRinging(ISipSession session, SipProfile caller, + byte[] sessionDescription) { + synchronized (SipService.this) { + try { + if (!isRegistered()) { + session.endCall(); + return; + } + + // send out incoming call broadcast + Log.d(TAG, " ringing~~ " + getUri() + ": " + caller.getUri() + + ": " + session.getCallId()); + addPendingSession(session); + Intent intent = SipManager.createIncomingCallBroadcast( + mIncomingCallBroadcastAction, session.getCallId(), + sessionDescription); + Log.d(TAG, " send out intent: " + intent); + mContext.sendBroadcast(intent); + } catch (RemoteException e) { + // should never happen with a local call + Log.e(TAG, "processCall()", e); + } + } + } + + @Override + public void onError(ISipSession session, String errorClass, + String message) { + Log.v(TAG, "sip session error: " + errorClass + ": " + message); + } + + public boolean isOpened() { + return mOpened; + } + + public boolean isRegistered() { + return mAutoRegistration.isRegistered(); + } + + private String getUri() { + return mSipGroup.getLocalProfileUri(); + } + } + + private class KeepAliveProcess implements Runnable { + private static final String TAG = "\\KEEPALIVE/"; + private static final int INTERVAL = 15; + private SipSessionGroup.SipSessionImpl mSession; + + public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { + mSession = session; + } + + public void start() { + mTimer.set(INTERVAL * 1000, this); + } + + public void run() { + synchronized (SipService.this) { + SipSessionGroup.SipSessionImpl session = mSession.duplicate(); + Log.d(TAG, " ~~~ keepalive"); + mTimer.cancel(this); + session.sendKeepAlive(); + if (session.isReRegisterRequired()) { + mSession.register(EXPIRY_TIME); + } else { + mTimer.set(INTERVAL * 1000, this); + } + } + } + + public void stop() { + mTimer.cancel(this); + } + } + + private class AutoRegistrationProcess extends SipSessionAdapter + implements Runnable { + private SipSessionGroup.SipSessionImpl mSession; + private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); + private KeepAliveProcess mKeepAliveProcess; + private int mBackoff = 1; + private boolean mRegistered; + private long mExpiryTime; + + private String getAction() { + return toString(); + } + + public void start(SipSessionGroup group) { + if (mSession == null) { + mBackoff = 1; + mSession = (SipSessionGroup.SipSessionImpl) + group.createSession(this); + // return right away if no active network connection. + if (mSession == null) return; + + // start unregistration to clear up old registration at server + // TODO: when rfc5626 is deployed, use reg-id and sip.instance + // in registration to avoid adding duplicate entries to server + mSession.unregister(); + Log.v(TAG, "start AutoRegistrationProcess for " + + mSession.getLocalProfile().getUriString()); + } + } + + public void stop() { + if (mSession == null) return; + if (mConnected) mSession.unregister(); + mTimer.cancel(this); + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + mSession = null; + mRegistered = false; + } + + private boolean isStopped() { + return (mSession == null); + } + + public void setListener(ISipSessionListener listener) { + Log.v(TAG, "setListener(): " + listener); + mProxy.setListener(listener); + if (mSession == null) return; + + try { + if ((mSession != null) && SipSessionState.REGISTERING.equals( + mSession.getState())) { + mProxy.onRegistering(mSession); + } else if (mRegistered) { + int duration = (int) + (mExpiryTime - SystemClock.elapsedRealtime()); + mProxy.onRegistrationDone(mSession, duration); + } + } catch (Throwable t) { + Log.w(TAG, "setListener(): " + t); + } + } + + public boolean isRegistered() { + return mRegistered; + } + + public void run() { + Log.v(TAG, " ~~~ registering"); + synchronized (SipService.this) { + if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); + } + } + + private boolean isBehindNAT(String address) { + try { + byte[] d = InetAddress.getByName(address).getAddress(); + if ((d[0] == 10) || + (((0x000000FF & ((int)d[0])) == 172) && + ((0x000000F0 & ((int)d[1])) == 16)) || + (((0x000000FF & ((int)d[0])) == 192) && + ((0x000000FF & ((int)d[1])) == 168))) { + return true; + } + } catch (UnknownHostException e) { + Log.e(TAG, "isBehindAT()" + address, e); + } + return false; + } + + private void restart(int duration) { + Log.v(TAG, "Refresh registration " + duration + "s later."); + mTimer.cancel(this); + mTimer.set(duration * 1000, this); + } + + private int backoffDuration() { + int duration = SHORT_EXPIRY_TIME * mBackoff; + if (duration > 3600) { + duration = 3600; + } else { + mBackoff *= 2; + } + return duration; + } + + @Override + public void onRegistering(ISipSession session) { + Log.v(TAG, "onRegistering(): " + session + ": " + mSession); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + mRegistered = false; + try { + mProxy.onRegistering(session); + } catch (Throwable t) { + Log.w(TAG, "onRegistering()", t); + } + } + } + + @Override + public void onRegistrationDone(ISipSession session, int duration) { + Log.v(TAG, "onRegistrationDone(): " + session + ": " + mSession); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + try { + mProxy.onRegistrationDone(session, duration); + } catch (Throwable t) { + Log.w(TAG, "onRegistrationDone()", t); + } + if (isStopped()) return; + + if (duration > 0) { + mSession.clearReRegisterRequired(); + mExpiryTime = SystemClock.elapsedRealtime() + + (duration * 1000); + + if (!mRegistered) { + mRegistered = true; + // allow some overlap to avoid call drop during renew + duration -= MIN_EXPIRY_TIME; + if (duration < MIN_EXPIRY_TIME) { + duration = MIN_EXPIRY_TIME; + } + restart(duration); + + if (isBehindNAT(mLocalIp) || + mSession.getLocalProfile().getSendKeepAlive()) { + if (mKeepAliveProcess == null) { + mKeepAliveProcess = + new KeepAliveProcess(mSession); + } + mKeepAliveProcess.start(); + } + } + } else { + mRegistered = false; + mExpiryTime = -1L; + Log.v(TAG, "Refresh registration immediately"); + run(); + } + } + } + + @Override + public void onRegistrationFailed(ISipSession session, String className, + String message) { + Log.v(TAG, "onRegistrationFailed(): " + session + ": " + mSession + + ": " + className + ": " + message); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + try { + mProxy.onRegistrationFailed(session, className, message); + } catch (Throwable t) { + Log.w(TAG, "onRegistrationFailed(): " + t); + } + + if (!isStopped()) onError(); + } + } + + @Override + public void onRegistrationTimeout(ISipSession session) { + Log.v(TAG, "onRegistrationTimeout(): " + session + ": " + mSession); + synchronized (SipService.this) { + if (!isStopped() && (session != mSession)) return; + try { + mProxy.onRegistrationTimeout(session); + } catch (Throwable t) { + Log.w(TAG, "onRegistrationTimeout(): " + t); + } + + if (!isStopped()) { + mRegistered = false; + onError(); + } + } + } + + private void onError() { + mRegistered = false; + restart(backoffDuration()); + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + } + } + + private class ConnectivityReceiver extends BroadcastReceiver { + private Timer mTimer = new Timer(); + private MyTimerTask mTask; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Bundle b = intent.getExtras(); + if (b != null) { + NetworkInfo netInfo = (NetworkInfo) + b.get(ConnectivityManager.EXTRA_NETWORK_INFO); + String type = netInfo.getTypeName(); + NetworkInfo.State state = netInfo.getState(); + if (state == NetworkInfo.State.CONNECTED) { + Log.v(TAG, "Connectivity alert: CONNECTED " + type); + onChanged(type, true); + } else if (state == NetworkInfo.State.DISCONNECTED) { + Log.v(TAG, "Connectivity alert: DISCONNECTED " + type); + onChanged(type, false); + } else { + Log.d(TAG, "Connectivity alert not processed: " + state + + " " + type); + } + } + } + } + + private void onChanged(String type, boolean connected) { + synchronized (SipService.this) { + // When turning on WIFI, it needs some time for network + // connectivity to get stabile so we defer good news (because + // we want to skip the interim ones) but deliver bad news + // immediately + if (connected) { + if (mTask != null) mTask.cancel(); + mTask = new MyTimerTask(type, connected); + mTimer.schedule(mTask, 3 * 1000L); + // TODO: hold wakup lock so that we can finish change before + // the device goes to sleep + } else { + if ((mTask != null) && mTask.mNetworkType.equals(type)) { + mTask.cancel(); + } + onConnectivityChanged(type, false); + } + } + } + + private class MyTimerTask extends TimerTask { + private boolean mConnected; + private String mNetworkType; + + public MyTimerTask(String type, boolean connected) { + mNetworkType = type; + mConnected = connected; + } + + @Override + public void run() { + synchronized (SipService.this) { + if (mTask != this) { + Log.w(TAG, " unexpected task: " + mNetworkType + + (mConnected ? " CONNECTED" : "DISCONNECTED")); + return; + } + mTask = null; + Log.v(TAG, " deliver change for " + mNetworkType + + (mConnected ? " CONNECTED" : "DISCONNECTED")); + onConnectivityChanged(mNetworkType, mConnected); + } + } + } + } + + // TODO: clean up pending SipSession(s) periodically + + + /** + * Timer that can schedule events to occur even when the device is in sleep. + * Only used internally in this package. + */ + class WakeupTimer extends BroadcastReceiver { + private static final String TAG = "_SIP.WkTimer_"; + private static final String TRIGGER_TIME = "TriggerTime"; + + private Context mContext; + private AlarmManager mAlarmManager; + + // runnable --> time to execute in SystemClock + private TreeSet<MyEvent> mEventQueue = + new TreeSet<MyEvent>(new MyEventComparator()); + + private PendingIntent mPendingIntent; + + public WakeupTimer(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) + context.getSystemService(Context.ALARM_SERVICE); + + IntentFilter filter = new IntentFilter(getAction()); + context.registerReceiver(this, filter); + } + + /** + * Stops the timer. No event can be scheduled after this method is called. + */ + public synchronized void stop() { + mContext.unregisterReceiver(this); + if (mPendingIntent != null) { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + mEventQueue.clear(); + mEventQueue = null; + } + + private synchronized boolean stopped() { + if (mEventQueue == null) { + Log.w(TAG, "Timer stopped"); + return true; + } else { + return false; + } + } + + private void cancelAlarm() { + mAlarmManager.cancel(mPendingIntent); + mPendingIntent = null; + } + + private void recalculatePeriods() { + if (mEventQueue.isEmpty()) return; + + MyEvent firstEvent = mEventQueue.first(); + int minPeriod = firstEvent.mMaxPeriod; + long minTriggerTime = firstEvent.mTriggerTime; + for (MyEvent e : mEventQueue) { + e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; + int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod + - minTriggerTime); + interval = interval / minPeriod * minPeriod; + e.mTriggerTime = minTriggerTime + interval; + } + TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>( + mEventQueue.comparator()); + newQueue.addAll((Collection<MyEvent>) mEventQueue); + mEventQueue.clear(); + mEventQueue = newQueue; + Log.v(TAG, "queue re-calculated"); + printQueue(); + } + + // Determines the period and the trigger time of the new event and insert it + // to the queue. + private void insertEvent(MyEvent event) { + long now = SystemClock.elapsedRealtime(); + if (mEventQueue.isEmpty()) { + event.mTriggerTime = now + event.mPeriod; + mEventQueue.add(event); + return; + } + MyEvent firstEvent = mEventQueue.first(); + int minPeriod = firstEvent.mPeriod; + if (minPeriod <= event.mMaxPeriod) { + event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; + int interval = event.mMaxPeriod; + interval -= (int) (firstEvent.mTriggerTime - now); + interval = interval / minPeriod * minPeriod; + event.mTriggerTime = firstEvent.mTriggerTime + interval; + mEventQueue.add(event); + } else { + long triggerTime = now + event.mPeriod; + if (firstEvent.mTriggerTime < triggerTime) { + event.mTriggerTime = firstEvent.mTriggerTime; + event.mLastTriggerTime -= event.mPeriod; + } else { + event.mTriggerTime = triggerTime; + } + mEventQueue.add(event); + recalculatePeriods(); + } + } + + /** + * Sets a periodic timer. + * + * @param period the timer period; in milli-second + * @param callback is called back when the timer goes off; the same callback + * can be specified in multiple timer events + */ + public synchronized void set(int period, Runnable callback) { + if (stopped()) return; + + long now = SystemClock.elapsedRealtime(); + MyEvent event = new MyEvent(period, callback, now); + insertEvent(event); + + if (mEventQueue.first() == event) { + if (mEventQueue.size() > 1) cancelAlarm(); + scheduleNext(); + } + + long triggerTime = event.mTriggerTime; + Log.v(TAG, " add event " + event + " scheduled at " + + showTime(triggerTime) + " at " + showTime(now) + + ", #events=" + mEventQueue.size()); + printQueue(); + } + + /** + * Cancels all the timer events with the specified callback. + * + * @param callback the callback + */ + public synchronized void cancel(Runnable callback) { + if (stopped() || mEventQueue.isEmpty()) return; + Log.d(TAG, "cancel:" + callback); + + MyEvent firstEvent = mEventQueue.first(); + for (Iterator<MyEvent> iter = mEventQueue.iterator(); + iter.hasNext();) { + MyEvent event = iter.next(); + if (event.mCallback == callback) { + iter.remove(); + Log.d(TAG, " cancel found:" + event); + } + } + if (mEventQueue.isEmpty()) { + cancelAlarm(); + } else if (mEventQueue.first() != firstEvent) { + cancelAlarm(); + firstEvent = mEventQueue.first(); + firstEvent.mPeriod = firstEvent.mMaxPeriod; + firstEvent.mTriggerTime = firstEvent.mLastTriggerTime + + firstEvent.mPeriod; + recalculatePeriods(); + scheduleNext(); + } + Log.d(TAG, "after cancel:"); + printQueue(); + } + + private void scheduleNext() { + if (stopped() || mEventQueue.isEmpty()) return; + + if (mPendingIntent != null) { + throw new RuntimeException("pendingIntent is not null!"); + } + + MyEvent event = mEventQueue.first(); + Intent intent = new Intent(getAction()); + intent.putExtra(TRIGGER_TIME, event.mTriggerTime); + PendingIntent pendingIntent = mPendingIntent = + PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + event.mTriggerTime, pendingIntent); + } + + @Override + public synchronized void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (getAction().equals(action) + && intent.getExtras().containsKey(TRIGGER_TIME)) { + mPendingIntent = null; + long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); + execute(triggerTime); + } else { + Log.d(TAG, "unrecognized intent: " + intent); + } + } + + private void printQueue() { + int count = 0; + for (MyEvent event : mEventQueue) { + Log.d(TAG, " " + event + ": scheduled at " + + showTime(event.mTriggerTime) + ": last at " + + showTime(event.mLastTriggerTime)); + if (++count >= 5) break; + } + if (mEventQueue.size() > count) { + Log.d(TAG, " ....."); + } else if (count == 0) { + Log.d(TAG, " <empty>"); + } + } + + private void execute(long triggerTime) { + Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": " + + mEventQueue.size()); + if (stopped() || mEventQueue.isEmpty()) return; + + for (MyEvent event : mEventQueue) { + if (event.mTriggerTime != triggerTime) break; + Log.d(TAG, "execute " + event); + + event.mLastTriggerTime = event.mTriggerTime; + event.mTriggerTime += event.mPeriod; + + // run the callback in a new thread to prevent deadlock + new Thread(event.mCallback).start(); + } + Log.d(TAG, "after timeout execution"); + printQueue(); + scheduleNext(); + } + + private String getAction() { + return toString(); + } + + private String showTime(long time) { + int ms = (int) (time % 1000); + int s = (int) (time / 1000); + int m = s / 60; + s %= 60; + return String.format("%d.%d.%d", m, s, ms); + } + } + + private static class MyEvent { + int mPeriod; + int mMaxPeriod; + long mTriggerTime; + long mLastTriggerTime; + Runnable mCallback; + + MyEvent(int period, Runnable callback, long now) { + mPeriod = mMaxPeriod = period; + mCallback = callback; + mLastTriggerTime = now; + } + + @Override + public String toString() { + String s = super.toString(); + s = s.substring(s.indexOf("@")); + return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" + + toString(mCallback); + } + + private String toString(Object o) { + String s = o.toString(); + int index = s.indexOf("$"); + if (index > 0) s = s.substring(index + 1); + return s; + } + } + + private static class MyEventComparator implements Comparator<MyEvent> { + public int compare(MyEvent e1, MyEvent e2) { + if (e1 == e2) return 0; + int diff = e1.mMaxPeriod - e2.mMaxPeriod; + if (diff == 0) diff = -1; + return diff; + } + + public boolean equals(Object that) { + return (this == that); + } + } +} diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java new file mode 100644 index 0000000..db3f536 --- /dev/null +++ b/services/java/com/android/server/sip/SipSessionGroup.java @@ -0,0 +1,1081 @@ +/* + * 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.sip; + +import gov.nist.javax.sip.clientauthutils.AccountManager; +import gov.nist.javax.sip.clientauthutils.UserCredentials; +import gov.nist.javax.sip.header.SIPHeaderNames; +import gov.nist.javax.sip.header.WWWAuthenticate; + +import android.net.sip.ISipSession; +import android.net.sip.ISipSessionListener; +import android.net.sip.SessionDescription; +import android.net.sip.SipProfile; +import android.net.sip.SipSessionAdapter; +import android.net.sip.SipSessionState; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.text.ParseException; +import java.util.Collection; +import java.util.EventObject; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.TooManyListenersException; + +import javax.sip.ClientTransaction; +import javax.sip.Dialog; +import javax.sip.DialogTerminatedEvent; +import javax.sip.IOExceptionEvent; +import javax.sip.InvalidArgumentException; +import javax.sip.ListeningPoint; +import javax.sip.RequestEvent; +import javax.sip.ResponseEvent; +import javax.sip.ServerTransaction; +import javax.sip.SipException; +import javax.sip.SipFactory; +import javax.sip.SipListener; +import javax.sip.SipProvider; +import javax.sip.SipStack; +import javax.sip.TimeoutEvent; +import javax.sip.Transaction; +import javax.sip.TransactionState; +import javax.sip.TransactionTerminatedEvent; +import javax.sip.address.Address; +import javax.sip.address.SipURI; +import javax.sip.header.CSeqHeader; +import javax.sip.header.ExpiresHeader; +import javax.sip.header.FromHeader; +import javax.sip.header.MinExpiresHeader; +import javax.sip.header.ViaHeader; +import javax.sip.message.Message; +import javax.sip.message.Request; +import javax.sip.message.Response; + +/** + * Manages {@link ISipSession}'s for a SIP account. + */ +class SipSessionGroup implements SipListener { + private static final String TAG = "SipSession"; + private static final String ANONYMOUS = "anonymous"; + private static final int EXPIRY_TIME = 3600; + + private static final EventObject DEREGISTER = new EventObject("Deregister"); + private static final EventObject END_CALL = new EventObject("End call"); + private static final EventObject HOLD_CALL = new EventObject("Hold call"); + private static final EventObject CONTINUE_CALL + = new EventObject("Continue call"); + + private final SipProfile mLocalProfile; + private final String mPassword; + + private SipStack mSipStack; + private SipHelper mSipHelper; + private String mLastNonce; + private int mRPort; + + // session that processes INVITE requests + private SipSessionImpl mCallReceiverSession; + private String mLocalIp; + + // call-id-to-SipSession map + private Map<String, SipSessionImpl> mSessionMap = + new HashMap<String, SipSessionImpl>(); + + /** + * @param myself the local profile with password crossed out + * @param password the password of the profile + * @throws IOException if cannot assign requested address + */ + public SipSessionGroup(String localIp, SipProfile myself, String password) + throws SipException, IOException { + mLocalProfile = myself; + mPassword = password; + reset(localIp); + } + + void reset(String localIp) throws SipException, IOException { + mLocalIp = localIp; + if (localIp == null) return; + + SipProfile myself = mLocalProfile; + SipFactory sipFactory = SipFactory.getInstance(); + Properties properties = new Properties(); + properties.setProperty("javax.sip.STACK_NAME", getStackName()); + String outboundProxy = myself.getProxyAddress(); + if (!TextUtils.isEmpty(outboundProxy)) { + properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy + + ":" + myself.getPort() + "/" + myself.getProtocol()); + } + SipStack stack = mSipStack = sipFactory.createSipStack(properties); + + try { + SipProvider provider = stack.createSipProvider( + stack.createListeningPoint(localIp, allocateLocalPort(), + myself.getProtocol())); + provider.addSipListener(this); + mSipHelper = new SipHelper(stack, provider); + } catch (InvalidArgumentException e) { + throw new IOException(e.getMessage()); + } catch (TooManyListenersException e) { + // must never happen + throw new SipException("SipSessionGroup constructor", e); + } + Log.d(TAG, " start stack for " + myself.getUriString()); + stack.start(); + + mLastNonce = null; + mCallReceiverSession = null; + mSessionMap.clear(); + } + + public SipProfile getLocalProfile() { + return mLocalProfile; + } + + public String getLocalProfileUri() { + return mLocalProfile.getUriString(); + } + + private String getStackName() { + return "stack" + System.currentTimeMillis(); + } + + public synchronized void close() { + Log.d(TAG, " close stack for " + mLocalProfile.getUriString()); + mSessionMap.clear(); + closeToNotReceiveCalls(); + if (mSipStack != null) { + mSipStack.stop(); + mSipStack = null; + mSipHelper = null; + } + } + + public synchronized boolean isClosed() { + return (mSipStack == null); + } + + // For internal use, require listener not to block in callbacks. + public synchronized void openToReceiveCalls(ISipSessionListener listener) { + if (mCallReceiverSession == null) { + mCallReceiverSession = new SipSessionCallReceiverImpl(listener); + } else { + mCallReceiverSession.setListener(listener); + } + } + + public synchronized void closeToNotReceiveCalls() { + mCallReceiverSession = null; + } + + public ISipSession createSession(ISipSessionListener listener) { + return (isClosed() ? null : new SipSessionImpl(listener)); + } + + private static int allocateLocalPort() throws SipException { + try { + DatagramSocket s = new DatagramSocket(); + int localPort = s.getLocalPort(); + s.close(); + return localPort; + } catch (IOException e) { + throw new SipException("allocateLocalPort()", e); + } + } + + private synchronized SipSessionImpl getSipSession(EventObject event) { + String key = SipHelper.getCallId(event); + Log.d(TAG, " sesssion key from event: " + key); + Log.d(TAG, " active sessions:"); + for (String k : mSessionMap.keySet()) { + Log.d(TAG, " ..... '" + k + "': " + mSessionMap.get(k)); + } + SipSessionImpl session = mSessionMap.get(key); + return ((session != null) ? session : mCallReceiverSession); + } + + private synchronized void addSipSession(SipSessionImpl newSession) { + removeSipSession(newSession); + String key = newSession.getCallId(); + Log.d(TAG, " +++++ add a session with key: '" + key + "'"); + mSessionMap.put(key, newSession); + for (String k : mSessionMap.keySet()) { + Log.d(TAG, " ..... " + k + ": " + mSessionMap.get(k)); + } + } + + private synchronized void removeSipSession(SipSessionImpl session) { + if (session == mCallReceiverSession) return; + String key = session.getCallId(); + SipSessionImpl s = mSessionMap.remove(key); + // sanity check + if ((s != null) && (s != session)) { + Log.w(TAG, "session " + session + " is not associated with key '" + + key + "'"); + mSessionMap.put(key, s); + for (Map.Entry<String, SipSessionImpl> entry + : mSessionMap.entrySet()) { + if (entry.getValue() == s) { + key = entry.getKey(); + mSessionMap.remove(key); + } + } + } + Log.d(TAG, " remove session " + session + " with key '" + key + "'"); + + for (String k : mSessionMap.keySet()) { + Log.d(TAG, " ..... " + k + ": " + mSessionMap.get(k)); + } + } + + public void processRequest(RequestEvent event) { + process(event); + } + + public void processResponse(ResponseEvent event) { + process(event); + } + + public void processIOException(IOExceptionEvent event) { + process(event); + } + + public void processTimeout(TimeoutEvent event) { + process(event); + } + + public void processTransactionTerminated(TransactionTerminatedEvent event) { + process(event); + } + + public void processDialogTerminated(DialogTerminatedEvent event) { + process(event); + } + + private synchronized void process(EventObject event) { + SipSessionImpl session = getSipSession(event); + try { + if ((session != null) && session.process(event)) { + Log.d(TAG, " ~~~~~ new state: " + session.mState); + } else { + Log.d(TAG, "event not processed: " + event); + } + } catch (Throwable e) { + Log.e(TAG, "event process error: " + event, e); + session.onError(e); + } + } + + private class SipSessionCallReceiverImpl extends SipSessionImpl { + public SipSessionCallReceiverImpl(ISipSessionListener listener) { + super(listener); + } + + public boolean process(EventObject evt) throws SipException { + Log.d(TAG, " ~~~~~ " + this + ": " + mState + ": processing " + + log(evt)); + if (isRequestEvent(Request.INVITE, evt)) { + RequestEvent event = (RequestEvent) evt; + SipSessionImpl newSession = new SipSessionImpl(mProxy); + newSession.mServerTransaction = mSipHelper.sendRinging(event, + generateTag()); + newSession.mDialog = newSession.mServerTransaction.getDialog(); + newSession.mInviteReceived = event; + newSession.mPeerProfile = createPeerProfile(event.getRequest()); + newSession.mState = SipSessionState.INCOMING_CALL; + newSession.mPeerSessionDescription = + event.getRequest().getRawContent(); + addSipSession(newSession); + mProxy.onRinging(newSession, newSession.mPeerProfile, + newSession.mPeerSessionDescription); + return true; + } else { + return false; + } + } + } + + class SipSessionImpl extends ISipSession.Stub { + SipProfile mPeerProfile; + SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); + SipSessionState mState = SipSessionState.READY_TO_CALL; + RequestEvent mInviteReceived; + Dialog mDialog; + ServerTransaction mServerTransaction; + ClientTransaction mClientTransaction; + byte[] mPeerSessionDescription; + boolean mInCall; + boolean mReRegisterFlag = false; + + public SipSessionImpl(ISipSessionListener listener) { + setListener(listener); + } + + SipSessionImpl duplicate() { + return new SipSessionImpl(mProxy.getListener()); + } + + private void reset() { + mInCall = false; + removeSipSession(this); + mPeerProfile = null; + mState = SipSessionState.READY_TO_CALL; + mInviteReceived = null; + mDialog = null; + mServerTransaction = null; + mClientTransaction = null; + mPeerSessionDescription = null; + } + + public boolean isInCall() { + return mInCall; + } + + public String getLocalIp() { + return mLocalIp; + } + + public SipProfile getLocalProfile() { + return mLocalProfile; + } + + public SipProfile getPeerProfile() { + return mPeerProfile; + } + + public String getCallId() { + return SipHelper.getCallId(getTransaction()); + } + + private Transaction getTransaction() { + if (mClientTransaction != null) return mClientTransaction; + if (mServerTransaction != null) return mServerTransaction; + return null; + } + + public String getState() { + return mState.toString(); + } + + public void setListener(ISipSessionListener listener) { + mProxy.setListener((listener instanceof SipSessionListenerProxy) + ? ((SipSessionListenerProxy) listener).getListener() + : listener); + } + + public void makeCall(SipProfile peerProfile, + SessionDescription sessionDescription) { + try { + processCommand( + new MakeCallCommand(peerProfile, sessionDescription)); + } catch (SipException e) { + onError(e); + } + } + + public void answerCall(SessionDescription sessionDescription) { + try { + processCommand( + new MakeCallCommand(mPeerProfile, sessionDescription)); + } catch (SipException e) { + onError(e); + } + } + + public void endCall() { + try { + processCommand(END_CALL); + } catch (SipException e) { + onError(e); + } + } + + public void changeCall(SessionDescription sessionDescription) { + try { + processCommand( + new MakeCallCommand(mPeerProfile, sessionDescription)); + } catch (SipException e) { + onError(e); + } + } + + public void register(int duration) { + try { + processCommand(new RegisterCommand(duration)); + } catch (SipException e) { + onRegistrationFailed(e); + } + } + + public void unregister() { + try { + processCommand(DEREGISTER); + } catch (SipException e) { + onRegistrationFailed(e); + } + } + + public boolean isReRegisterRequired() { + return mReRegisterFlag; + } + + public void clearReRegisterRequired() { + mReRegisterFlag = false; + } + + public void sendKeepAlive() { + mState = SipSessionState.PINGING; + try { + processCommand(new OptionsCommand()); + while (SipSessionState.PINGING.equals(mState)) { + Thread.sleep(1000); + } + } catch (SipException e) { + Log.e(TAG, "sendKeepAlive failed", e); + } catch (InterruptedException e) { + Log.e(TAG, "sendKeepAlive interrupted", e); + } + } + + private void processCommand(EventObject command) throws SipException { + if (!process(command)) { + throw new SipException("wrong state to execute: " + command); + } + } + + protected String generateTag() { + // 32-bit randomness + return String.valueOf((long) (Math.random() * 0x100000000L)); + } + + public String toString() { + try { + String s = super.toString(); + return s.substring(s.indexOf("@")) + ":" + mState; + } catch (Throwable e) { + return super.toString(); + } + } + + public boolean process(EventObject evt) throws SipException { + Log.d(TAG, " ~~~~~ " + this + ": " + mState + ": processing " + + log(evt)); + synchronized (SipSessionGroup.this) { + if (isClosed()) return false; + + Dialog dialog = null; + if (evt instanceof RequestEvent) { + dialog = ((RequestEvent) evt).getDialog(); + } else if (evt instanceof ResponseEvent) { + dialog = ((ResponseEvent) evt).getDialog(); + } + if (dialog != null) mDialog = dialog; + + boolean processed; + + switch (mState) { + case REGISTERING: + case DEREGISTERING: + processed = registeringToReady(evt); + break; + case PINGING: + processed = keepAliveProcess(evt); + break; + case READY_TO_CALL: + processed = readyForCall(evt); + break; + case INCOMING_CALL: + processed = incomingCall(evt); + break; + case INCOMING_CALL_ANSWERING: + processed = incomingCallToInCall(evt); + break; + case OUTGOING_CALL: + case OUTGOING_CALL_RING_BACK: + processed = outgoingCall(evt); + break; + case OUTGOING_CALL_CANCELING: + processed = outgoingCallToReady(evt); + break; + case IN_CALL: + processed = inCall(evt); + break; + default: + processed = false; + } + return (processed || processExceptions(evt)); + } + } + + private boolean processExceptions(EventObject evt) throws SipException { + if (isRequestEvent(Request.BYE, evt)) { + // terminate the call whenever a BYE is received + mSipHelper.sendResponse((RequestEvent) evt, Response.OK); + endCallNormally(); + return true; + } else if (isRequestEvent(Request.CANCEL, evt)) { + mSipHelper.sendResponse((RequestEvent) evt, + Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST); + return true; + } else if (evt instanceof TransactionTerminatedEvent) { + if (evt instanceof TimeoutEvent) { + processTimeout((TimeoutEvent) evt); + } else { + Log.d(TAG, "Transaction terminated:" + this); + if (!SipSessionState.IN_CALL.equals(mState)) { + removeSipSession(this); + } + return true; + } + return true; + } else if (evt instanceof DialogTerminatedEvent) { + processDialogTerminated((DialogTerminatedEvent) evt); + return true; + } + return false; + } + + private void processDialogTerminated(DialogTerminatedEvent event) { + if (mDialog == event.getDialog()) { + onError(new SipException("dialog terminated")); + } else { + Log.d(TAG, "not the current dialog; current=" + mDialog + + ", terminated=" + event.getDialog()); + } + } + + private void processTimeout(TimeoutEvent event) { + Log.d(TAG, "processing Timeout..." + event); + Transaction current = event.isServerTransaction() + ? mServerTransaction + : mClientTransaction; + Transaction target = event.isServerTransaction() + ? event.getServerTransaction() + : event.getClientTransaction(); + + if ((current != target) && (mState != SipSessionState.PINGING)) { + Log.d(TAG, "not the current transaction; current=" + current + + ", timed out=" + target); + return; + } + switch (mState) { + case REGISTERING: + case DEREGISTERING: + reset(); + mProxy.onRegistrationTimeout(this); + break; + case INCOMING_CALL: + case INCOMING_CALL_ANSWERING: + case OUTGOING_CALL_CANCELING: + endCallOnError(new SipException("timed out")); + break; + case PINGING: + reset(); + mReRegisterFlag = true; + mState = SipSessionState.READY_TO_CALL; + break; + + default: + // do nothing + break; + } + } + + private int getExpiryTime(Response response) { + int expires = EXPIRY_TIME; + ExpiresHeader expiresHeader = (ExpiresHeader) + response.getHeader(ExpiresHeader.NAME); + if (expiresHeader != null) expires = expiresHeader.getExpires(); + expiresHeader = (ExpiresHeader) + response.getHeader(MinExpiresHeader.NAME); + if (expiresHeader != null) { + expires = Math.max(expires, expiresHeader.getExpires()); + } + return expires; + } + + private boolean keepAliveProcess(EventObject evt) throws SipException { + if (evt instanceof OptionsCommand) { + mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, + generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + return true; + } else if (evt instanceof ResponseEvent) { + return parseOptionsResult(evt); + } + return false; + } + + private boolean parseOptionsResult(EventObject evt) { + if (expectResponse(Request.OPTIONS, evt)) { + ResponseEvent event = (ResponseEvent) evt; + int rPort = getRPortFromResponse(event.getResponse()); + if (rPort != -1) { + if (mRPort == 0) mRPort = rPort; + if (mRPort != rPort) { + mReRegisterFlag = true; + Log.w(TAG, String.format("rport is changed: %d <> %d", + mRPort, rPort)); + mRPort = rPort; + } else { + Log.w(TAG, "rport is the same: " + rPort); + } + } else { + Log.w(TAG, "peer did not respect our rport request"); + } + mState = SipSessionState.READY_TO_CALL; + return true; + } + return false; + } + + private int getRPortFromResponse(Response response) { + ViaHeader viaHeader = (ViaHeader)(response.getHeader( + SIPHeaderNames.VIA)); + return (viaHeader == null) ? -1 : viaHeader.getRPort(); + } + + private boolean registeringToReady(EventObject evt) + throws SipException { + if (expectResponse(Request.REGISTER, evt)) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + + int statusCode = response.getStatusCode(); + switch (statusCode) { + case Response.OK: + SipSessionState state = mState; + reset(); + onRegistrationDone((state == SipSessionState.REGISTERING) + ? getExpiryTime(((ResponseEvent) evt).getResponse()) + : -1); + mLastNonce = null; + mRPort = 0; + return true; + case Response.UNAUTHORIZED: + case Response.PROXY_AUTHENTICATION_REQUIRED: + String nonce = getNonceFromResponse(response); + if (((nonce != null) && nonce.equals(mLastNonce)) || + (nonce == mLastNonce)) { + Log.v(TAG, "Incorrect username/password"); + reset(); + onRegistrationFailed(createCallbackException(response)); + } else { + mSipHelper.handleChallenge(event, getAccountManager()); + mLastNonce = nonce; + } + return true; + default: + if (statusCode >= 500) { + reset(); + onRegistrationFailed(createCallbackException(response)); + return true; + } + } + } + return false; + } + + private AccountManager getAccountManager() { + return new AccountManager() { + public UserCredentials getCredentials(ClientTransaction + challengedTransaction, String realm) { + return new UserCredentials() { + public String getUserName() { + return mLocalProfile.getUserName(); + } + + public String getPassword() { + return mPassword; + } + + public String getSipDomain() { + return mLocalProfile.getSipDomain(); + } + }; + } + }; + } + + private String getNonceFromResponse(Response response) { + WWWAuthenticate authHeader = (WWWAuthenticate)(response.getHeader( + SIPHeaderNames.WWW_AUTHENTICATE)); + return (authHeader == null) ? null : authHeader.getNonce(); + } + + private boolean readyForCall(EventObject evt) throws SipException { + // expect MakeCallCommand, RegisterCommand, DEREGISTER + if (evt instanceof MakeCallCommand) { + MakeCallCommand cmd = (MakeCallCommand) evt; + mPeerProfile = cmd.getPeerProfile(); + SessionDescription sessionDescription = + cmd.getSessionDescription(); + mClientTransaction = mSipHelper.sendInvite(mLocalProfile, + mPeerProfile, sessionDescription, generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + mState = SipSessionState.OUTGOING_CALL; + mProxy.onCalling(this); + return true; + } else if (evt instanceof RegisterCommand) { + int duration = ((RegisterCommand) evt).getDuration(); + mClientTransaction = mSipHelper.sendRegister(mLocalProfile, + generateTag(), duration); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + mState = SipSessionState.REGISTERING; + mProxy.onRegistering(this); + return true; + } else if (DEREGISTER == evt) { + mClientTransaction = mSipHelper.sendRegister(mLocalProfile, + generateTag(), 0); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + mState = SipSessionState.DEREGISTERING; + mProxy.onRegistering(this); + return true; + } + return false; + } + + private boolean incomingCall(EventObject evt) throws SipException { + // expect MakeCallCommand(answering) , END_CALL cmd , Cancel + if (evt instanceof MakeCallCommand) { + // answer call + mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, + mLocalProfile, + ((MakeCallCommand) evt).getSessionDescription(), + mServerTransaction); + mState = SipSessionState.INCOMING_CALL_ANSWERING; + return true; + } else if (END_CALL == evt) { + mSipHelper.sendInviteBusyHere(mInviteReceived, + mServerTransaction); + endCallNormally(); + return true; + } else if (isRequestEvent(Request.CANCEL, evt)) { + RequestEvent event = (RequestEvent) evt; + mSipHelper.sendResponse(event, Response.OK); + mSipHelper.sendInviteRequestTerminated( + mInviteReceived.getRequest(), mServerTransaction); + endCallNormally(); + return true; + } + return false; + } + + private boolean incomingCallToInCall(EventObject evt) + throws SipException { + // expect ACK, CANCEL request + if (isRequestEvent(Request.ACK, evt)) { + establishCall(); + return true; + } else if (isRequestEvent(Request.CANCEL, evt)) { + // http://tools.ietf.org/html/rfc3261#section-9.2 + // Final response has been sent; do nothing here. + return true; + } + return false; + } + + private boolean outgoingCall(EventObject evt) throws SipException { + if (expectResponse(Request.INVITE, evt)) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + + int statusCode = response.getStatusCode(); + switch (statusCode) { + case Response.RINGING: + if (mState == SipSessionState.OUTGOING_CALL) { + mState = SipSessionState.OUTGOING_CALL_RING_BACK; + mProxy.onRingingBack(this); + } + return true; + case Response.OK: + mSipHelper.sendInviteAck(event, mDialog); + mPeerSessionDescription = response.getRawContent(); + establishCall(); + return true; + case Response.PROXY_AUTHENTICATION_REQUIRED: + mClientTransaction = mSipHelper.handleChallenge( + (ResponseEvent) evt, getAccountManager()); + mDialog = mClientTransaction.getDialog(); + addSipSession(this); + return true; + case Response.BUSY_HERE: + reset(); + mProxy.onCallBusy(this); + return true; + case Response.REQUEST_PENDING: + // TODO: + // rfc3261#section-14.1; re-schedule invite + return true; + default: + if (statusCode >= 400) { + // error: an ack is sent automatically by the stack + onError(createCallbackException(response)); + return true; + } else if (statusCode >= 300) { + // TODO: handle 3xx (redirect) + } else { + return true; + } + } + return false; + } else if (END_CALL == evt) { + // RFC says that UA should not send out cancel when no + // response comes back yet. We are cheating for not checking + // response. + mSipHelper.sendCancel(mClientTransaction); + mState = SipSessionState.OUTGOING_CALL_CANCELING; + return true; + } + return false; + } + + private boolean outgoingCallToReady(EventObject evt) + throws SipException { + if (evt instanceof ResponseEvent) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + int statusCode = response.getStatusCode(); + if (expectResponse(Request.CANCEL, evt)) { + if (statusCode == Response.OK) { + // do nothing; wait for REQUEST_TERMINATED + return true; + } + } else if (expectResponse(Request.INVITE, evt)) { + if (statusCode == Response.OK) { + outgoingCall(evt); // abort Cancel + return true; + } + } else { + return false; + } + + if (statusCode >= 400) { + onError(createCallbackException(response)); + return true; + } + } else if (evt instanceof TransactionTerminatedEvent) { + // rfc3261#section-14.1: + // if re-invite gets timed out, terminate the dialog; but + // re-invite is not reliable, just let it go and pretend + // nothing happened. + onError(new SipException("timed out")); + } + return false; + } + + private boolean inCall(EventObject evt) throws SipException { + // expect END_CALL cmd, BYE request, hold call (MakeCallCommand) + // OK retransmission is handled in SipStack + if (END_CALL == evt) { + // rfc3261#section-15.1.1 + mSipHelper.sendBye(mDialog); + endCallNormally(); + return true; + } else if (isRequestEvent(Request.INVITE, evt)) { + // got Re-INVITE + RequestEvent event = mInviteReceived = (RequestEvent) evt; + mState = SipSessionState.INCOMING_CALL; + mPeerSessionDescription = event.getRequest().getRawContent(); + mServerTransaction = null; + mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); + return true; + } else if (isRequestEvent(Request.BYE, evt)) { + mSipHelper.sendResponse((RequestEvent) evt, Response.OK); + endCallNormally(); + return true; + } else if (evt instanceof MakeCallCommand) { + // to change call + mClientTransaction = mSipHelper.sendReinvite(mDialog, + ((MakeCallCommand) evt).getSessionDescription()); + mState = SipSessionState.OUTGOING_CALL; + return true; + } + return false; + } + + private Exception createCallbackException(Response response) { + return new SipException(String.format("Response: %s (%d)", + response.getReasonPhrase(), response.getStatusCode())); + } + + private void establishCall() { + mState = SipSessionState.IN_CALL; + mInCall = true; + mProxy.onCallEstablished(this, mPeerSessionDescription); + } + + private void fallbackToPreviousInCall(Throwable exception) { + mState = SipSessionState.IN_CALL; + mProxy.onCallChangeFailed(this, exception.getClass().getName(), + exception.getMessage()); + } + + private void endCallNormally() { + reset(); + mProxy.onCallEnded(this); + } + + private void endCallOnError(Throwable exception) { + reset(); + mProxy.onError(this, exception.getClass().getName(), + exception.getMessage()); + } + + private void onError(Throwable exception) { + if (mInCall) { + fallbackToPreviousInCall(exception); + } else { + endCallOnError(exception); + } + } + + private void onRegistrationDone(int duration) { + mProxy.onRegistrationDone(this, duration); + } + + private void onRegistrationFailed(Throwable exception) { + mProxy.onRegistrationFailed(this, exception.getClass().getName(), + exception.getMessage()); + } + } + + /** + * @return true if the event is a request event matching the specified + * method; false otherwise + */ + private static boolean isRequestEvent(String method, EventObject event) { + try { + if (event instanceof RequestEvent) { + RequestEvent requestEvent = (RequestEvent) event; + return method.equals(requestEvent.getRequest().getMethod()); + } + } catch (Throwable e) { + } + return false; + } + + private static String getCseqMethod(Message message) { + return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod(); + } + + /** + * @return true if the event is a response event and the CSeqHeader method + * match the given arguments; false otherwise + */ + private static boolean expectResponse( + String expectedMethod, EventObject evt) { + if (evt instanceof ResponseEvent) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); + } + return false; + } + + /** + * @return true if the event is a response event and the response code and + * CSeqHeader method match the given arguments; false otherwise + */ + private static boolean expectResponse( + int responseCode, String expectedMethod, EventObject evt) { + if (evt instanceof ResponseEvent) { + ResponseEvent event = (ResponseEvent) evt; + Response response = event.getResponse(); + if (response.getStatusCode() == responseCode) { + return expectedMethod.equalsIgnoreCase(getCseqMethod(response)); + } + } + return false; + } + + private static SipProfile createPeerProfile(Request request) + throws SipException { + try { + FromHeader fromHeader = + (FromHeader) request.getHeader(FromHeader.NAME); + Address address = fromHeader.getAddress(); + SipURI uri = (SipURI) address.getURI(); + String username = uri.getUser(); + if (username == null) username = ANONYMOUS; + return new SipProfile.Builder(username, uri.getHost()) + .setPort(uri.getPort()) + .setDisplayName(address.getDisplayName()) + .build(); + } catch (InvalidArgumentException e) { + throw new SipException("createPeerProfile()", e); + } catch (ParseException e) { + throw new SipException("createPeerProfile()", e); + } + } + + private static String log(EventObject evt) { + if (evt instanceof RequestEvent) { + return ((RequestEvent) evt).getRequest().toString(); + } else if (evt instanceof ResponseEvent) { + return ((ResponseEvent) evt).getResponse().toString(); + } else { + return evt.toString(); + } + } + + private class OptionsCommand extends EventObject { + public OptionsCommand() { + super(SipSessionGroup.this); + } + } + + private class RegisterCommand extends EventObject { + private int mDuration; + + public RegisterCommand(int duration) { + super(SipSessionGroup.this); + mDuration = duration; + } + + public int getDuration() { + return mDuration; + } + } + + private class MakeCallCommand extends EventObject { + private SessionDescription mSessionDescription; + + public MakeCallCommand(SipProfile peerProfile, + SessionDescription sessionDescription) { + super(peerProfile); + mSessionDescription = sessionDescription; + } + + public SipProfile getPeerProfile() { + return (SipProfile) getSource(); + } + + public SessionDescription getSessionDescription() { + return mSessionDescription; + } + } + +} diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java new file mode 100644 index 0000000..fd49fd8 --- /dev/null +++ b/services/java/com/android/server/sip/SipSessionListenerProxy.java @@ -0,0 +1,206 @@ +/* + * 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.sip; + +import android.net.sip.ISipSession; +import android.net.sip.ISipSessionListener; +import android.net.sip.SipProfile; +import android.util.Log; + +/** Class to help safely run a callback in a different thread. */ +class SipSessionListenerProxy extends ISipSessionListener.Stub { + private static final String TAG = "SipSession"; + + private ISipSessionListener mListener; + + public void setListener(ISipSessionListener listener) { + mListener = listener; + } + + public ISipSessionListener getListener() { + return mListener; + } + + private void proxy(Runnable runnable) { + // One thread for each calling back. + // Note: Guarantee ordering if the issue becomes important. Currently, + // the chance of handling two callback events at a time is none. + new Thread(runnable).start(); + } + + public void onCalling(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCalling(session); + } catch (Throwable t) { + Log.w(TAG, "onCalling()", t); + } + } + }); + } + + public void onRinging(final ISipSession session, final SipProfile caller, + final byte[] sessionDescription) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRinging(session, caller, sessionDescription); + } catch (Throwable t) { + Log.w(TAG, "onRinging()", t); + } + } + }); + } + + public void onRingingBack(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRingingBack(session); + } catch (Throwable t) { + Log.w(TAG, "onRingingBack()", t); + } + } + }); + } + + public void onCallEstablished(final ISipSession session, + final byte[] sessionDescription) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallEstablished(session, sessionDescription); + } catch (Throwable t) { + Log.w(TAG, "onCallEstablished()", t); + } + } + }); + } + + public void onCallEnded(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallEnded(session); + } catch (Throwable t) { + Log.w(TAG, "onCallEnded()", t); + } + } + }); + } + + public void onCallBusy(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallBusy(session); + } catch (Throwable t) { + Log.w(TAG, "onCallBusy()", t); + } + } + }); + } + + public void onCallChangeFailed(final ISipSession session, + final String className, final String message) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onCallChangeFailed(session, className, message); + } catch (Throwable t) { + Log.w(TAG, "onCallChangeFailed()", t); + } + } + }); + } + + public void onError(final ISipSession session, final String className, + final String message) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onError(session, className, message); + } catch (Throwable t) { + Log.w(TAG, "onError()", t); + } + } + }); + } + + public void onRegistering(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistering(session); + } catch (Throwable t) { + Log.w(TAG, "onRegistering()", t); + } + } + }); + } + + public void onRegistrationDone(final ISipSession session, + final int duration) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistrationDone(session, duration); + } catch (Throwable t) { + Log.w(TAG, "onRegistrationDone()", t); + } + } + }); + } + + public void onRegistrationFailed(final ISipSession session, + final String className, final String message) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistrationFailed(session, className, message); + } catch (Throwable t) { + Log.w(TAG, "onRegistrationFailed()", t); + } + } + }); + } + + public void onRegistrationTimeout(final ISipSession session) { + if (mListener == null) return; + proxy(new Runnable() { + public void run() { + try { + mListener.onRegistrationTimeout(session); + } catch (Throwable t) { + Log.w(TAG, "onRegistrationTimeout()", t); + } + } + }); + } +} diff --git a/services/java/com/android/server/status/AnimatedImageView.java b/services/java/com/android/server/status/AnimatedImageView.java deleted file mode 100644 index 97df065..0000000 --- a/services/java/com/android/server/status/AnimatedImageView.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.graphics.drawable.AnimationDrawable; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.ImageView; -import android.widget.RemoteViews.RemoteView; - -@RemoteView -public class AnimatedImageView extends ImageView { - AnimationDrawable mAnim; - boolean mAttached; - - public AnimatedImageView(Context context) { - super(context); - } - - public AnimatedImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - private void updateAnim() { - Drawable drawable = getDrawable(); - if (mAttached && mAnim != null) { - mAnim.stop(); - } - if (drawable instanceof AnimationDrawable) { - mAnim = (AnimationDrawable)drawable; - if (mAttached) { - mAnim.start(); - } - } else { - mAnim = null; - } - } - - @Override - public void setImageDrawable(Drawable drawable) { - super.setImageDrawable(drawable); - updateAnim(); - } - - @Override - @android.view.RemotableViewMethod - public void setImageResource(int resid) { - super.setImageResource(resid); - updateAnim(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mAnim != null) { - mAnim.start(); - } - mAttached = true; - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mAnim != null) { - mAnim.stop(); - } - mAttached = false; - } -} - diff --git a/services/java/com/android/server/status/CloseDragHandle.java b/services/java/com/android/server/status/CloseDragHandle.java deleted file mode 100644 index ad1ac4d..0000000 --- a/services/java/com/android/server/status/CloseDragHandle.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.LinearLayout; - - -public class CloseDragHandle extends LinearLayout { - StatusBarService mService; - - public CloseDragHandle(Context context, AttributeSet attrs) { - super(context, attrs); - } - - /** - * Ensure that, if there is no target under us to receive the touch, - * that we process it ourself. This makes sure that onInterceptTouchEvent() - * is always called for the entire gesture. - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() != MotionEvent.ACTION_DOWN) { - mService.interceptTouchEvent(event); - } - return true; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return mService.interceptTouchEvent(event) - ? true : super.onInterceptTouchEvent(event); - } -} - diff --git a/services/java/com/android/server/status/DateView.java b/services/java/com/android/server/status/DateView.java deleted file mode 100644 index c04fb45..0000000 --- a/services/java/com/android/server/status/DateView.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.status; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.AttributeSet; -import android.util.Slog; -import android.widget.TextView; -import android.view.MotionEvent; - -import java.text.DateFormat; -import java.util.Date; - -public final class DateView extends TextView { - private static final String TAG = "DateView"; - - private boolean mUpdating = false; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Intent.ACTION_TIME_TICK) - || action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { - updateClock(); - } - } - }; - - public DateView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - setUpdates(false); - } - - @Override - protected int getSuggestedMinimumWidth() { - // makes the large background bitmap not force us to full width - return 0; - } - - private final void updateClock() { - Date now = new Date(); - setText(DateFormat.getDateInstance(DateFormat.LONG).format(now)); - } - - void setUpdates(boolean update) { - if (update != mUpdating) { - mUpdating = update; - if (update) { - // Register for Intent broadcasts for the clock and battery - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mIntentReceiver, filter, null, null); - updateClock(); - } else { - mContext.unregisterReceiver(mIntentReceiver); - } - } - } -} - diff --git a/services/java/com/android/server/status/ExpandedView.java b/services/java/com/android/server/status/ExpandedView.java deleted file mode 100644 index cb37f90..0000000 --- a/services/java/com/android/server/status/ExpandedView.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Display; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.WindowManager; -import android.widget.LinearLayout; -import android.util.Slog; - - -public class ExpandedView extends LinearLayout { - StatusBarService mService; - int mPrevHeight = -1; - - public ExpandedView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - } - - /** We want to shrink down to 0, and ignore the background. */ - @Override - public int getSuggestedMinimumHeight() { - return 0; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - int height = bottom - top; - if (height != mPrevHeight) { - //Slog.d(StatusBarService.TAG, "height changed old=" + mPrevHeight + " new=" + height); - mPrevHeight = height; - mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE); - } - } -} diff --git a/services/java/com/android/server/status/FixedSizeDrawable.java b/services/java/com/android/server/status/FixedSizeDrawable.java deleted file mode 100644 index dbfcb2c..0000000 --- a/services/java/com/android/server/status/FixedSizeDrawable.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.status; - -import android.graphics.drawable.Drawable; -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.util.Slog; - -class FixedSizeDrawable extends Drawable { - Drawable mDrawable; - int mLeft; - int mTop; - int mRight; - int mBottom; - - FixedSizeDrawable(Drawable that) { - mDrawable = that; - } - - public void setFixedBounds(int l, int t, int r, int b) { - mLeft = l; - mTop = t; - mRight = r; - mBottom = b; - } - - public void setBounds(Rect bounds) { - mDrawable.setBounds(mLeft, mTop, mRight, mBottom); - } - - public void setBounds(int l, int t, int r, int b) { - mDrawable.setBounds(mLeft, mTop, mRight, mBottom); - } - - public void draw(Canvas canvas) { - mDrawable.draw(canvas); - } - - public int getOpacity() { - return mDrawable.getOpacity(); - } - - public void setAlpha(int alpha) { - mDrawable.setAlpha(alpha); - } - - public void setColorFilter(ColorFilter cf) { - mDrawable.setColorFilter(cf); - } -} diff --git a/services/java/com/android/server/status/IconData.java b/services/java/com/android/server/status/IconData.java deleted file mode 100644 index fd226f9..0000000 --- a/services/java/com/android/server/status/IconData.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.status; - -import android.util.Slog; - -public class IconData { - /** - * Indicates ths item represents a piece of text. - */ - public static final int TEXT = 1; - - /** - * Indicates ths item represents an icon. - */ - public static final int ICON = 2; - - /** - * The type of this item. One of TEXT, ICON, or LEVEL_ICON. - */ - public int type; - - /** - * The slot that this icon will be in if it is not a notification - */ - public String slot; - - /** - * The package containting the icon to draw for this item. Valid if this is - * an ICON type. - */ - public String iconPackage; - - /** - * The icon to draw for this item. Valid if this is an ICON type. - */ - public int iconId; - - /** - * The level associated with the icon. Valid if this is a LEVEL_ICON type. - */ - public int iconLevel; - - /** - * The "count" number. - */ - public int number; - - /** - * The text associated with the icon. Valid if this is a TEXT type. - */ - public CharSequence text; - - private IconData() { - } - - public static IconData makeIcon(String slot, - String iconPackage, int iconId, int iconLevel, int number) { - IconData data = new IconData(); - data.type = ICON; - data.slot = slot; - data.iconPackage = iconPackage; - data.iconId = iconId; - data.iconLevel = iconLevel; - data.number = number; - return data; - } - - public static IconData makeText(String slot, CharSequence text) { - IconData data = new IconData(); - data.type = TEXT; - data.slot = slot; - data.text = text; - return data; - } - - public void copyFrom(IconData that) { - this.type = that.type; - this.slot = that.slot; - this.iconPackage = that.iconPackage; - this.iconId = that.iconId; - this.iconLevel = that.iconLevel; - this.number = that.number; - this.text = that.text; // should we clone this? - } - - public IconData clone() { - IconData that = new IconData(); - that.copyFrom(this); - return that; - } - - public String toString() { - if (this.type == TEXT) { - return "IconData(slot=" + (this.slot != null ? "'" + this.slot + "'" : "null") - + " text='" + this.text + "')"; - } - else if (this.type == ICON) { - return "IconData(slot=" + (this.slot != null ? "'" + this.slot + "'" : "null") - + " package=" + this.iconPackage - + " iconId=" + Integer.toHexString(this.iconId) - + " iconLevel=" + this.iconLevel + ")"; - } - else { - return "IconData(type=" + type + ")"; - } - } -} diff --git a/services/java/com/android/server/status/IconMerger.java b/services/java/com/android/server/status/IconMerger.java deleted file mode 100644 index aa702ae..0000000 --- a/services/java/com/android/server/status/IconMerger.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - - -public class IconMerger extends LinearLayout { - StatusBarService service; - StatusBarIcon moreIcon; - - public IconMerger(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - final int maxWidth = r - l; - final int N = getChildCount(); - int i; - - // get the rightmost one, and see if we even need to do anything - int fitRight = -1; - for (i=N-1; i>=0; i--) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - fitRight = child.getRight(); - break; - } - } - - // find the first visible one that isn't the more icon - View moreView = null; - int fitLeft = -1; - int startIndex = -1; - for (i=0; i<N; i++) { - final View child = getChildAt(i); - if (com.android.internal.R.drawable.stat_notify_more == child.getId()) { - moreView = child; - startIndex = i+1; - } - else if (child.getVisibility() != GONE) { - fitLeft = child.getLeft(); - break; - } - } - - if (moreView == null || startIndex < 0) { - throw new RuntimeException("Status Bar / IconMerger moreView == null"); - } - - // if it fits without the more icon, then hide the more icon and update fitLeft - // so everything gets pushed left - int adjust = 0; - if (fitRight - fitLeft <= maxWidth) { - adjust = fitLeft - moreView.getLeft(); - fitLeft -= adjust; - fitRight -= adjust; - moreView.layout(0, moreView.getTop(), 0, moreView.getBottom()); - } - int extra = fitRight - r; - int shift = -1; - - int breakingPoint = fitLeft + extra + adjust; - int number = 0; - for (i=startIndex; i<N; i++) { - final View child = getChildAt(i); - if (child.getVisibility() != GONE) { - int childLeft = child.getLeft(); - int childRight = child.getRight(); - if (childLeft < breakingPoint) { - // hide this one - child.layout(0, child.getTop(), 0, child.getBottom()); - int n = this.service.getIconNumberForView(child); - if (n == 0) { - number += 1; - } else if (n > 0) { - number += n; - } - } else { - // decide how much to shift by - if (shift < 0) { - shift = childLeft - fitLeft; - } - // shift this left by shift - child.layout(childLeft-shift, child.getTop(), - childRight-shift, child.getBottom()); - } - } - } - - // BUG: Updating the text during the layout here doesn't seem to cause - // the view to be redrawn fully. The text view gets resized correctly, but the - // text contents aren't drawn properly. To work around this, we post a message - // and provide the value later. We're the only one changing this value show it - // should be ordered correctly. - if (false) { - this.moreIcon.update(number); - } else { - mBugWorkaroundNumber = number; - mBugWorkaroundHandler.post(mBugWorkaroundRunnable); - } - } - - private int mBugWorkaroundNumber; - private Handler mBugWorkaroundHandler = new Handler(); - private Runnable mBugWorkaroundRunnable = new Runnable() { - public void run() { - IconMerger.this.moreIcon.update(mBugWorkaroundNumber); - IconMerger.this.moreIcon.view.invalidate(); - } - }; -} diff --git a/services/java/com/android/server/status/LatestItemView.java b/services/java/com/android/server/status/LatestItemView.java deleted file mode 100644 index fe8d164..0000000 --- a/services/java/com/android/server/status/LatestItemView.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.util.Slog; -import android.view.MotionEvent; -import android.widget.FrameLayout; - -public class LatestItemView extends FrameLayout { - - public LatestItemView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public boolean dispatchTouchEvent(MotionEvent ev) { - return onTouchEvent(ev); - } -} diff --git a/services/java/com/android/server/status/NotificationData.java b/services/java/com/android/server/status/NotificationData.java deleted file mode 100644 index 71f01ca..0000000 --- a/services/java/com/android/server/status/NotificationData.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.status; - -import android.app.PendingIntent; -import android.widget.RemoteViews; - -public class NotificationData { - public String pkg; - public String tag; - public int id; - public CharSequence tickerText; - - public long when; - public boolean ongoingEvent; - public boolean clearable; - - public RemoteViews contentView; - public PendingIntent contentIntent; - - public PendingIntent deleteIntent; - - public String toString() { - return "NotificationData(package=" + pkg + " id=" + id + " tickerText=" + tickerText - + " ongoingEvent=" + ongoingEvent + " contentIntent=" + contentIntent - + " deleteIntent=" + deleteIntent - + " clearable=" + clearable - + " contentView=" + contentView + " when=" + when + ")"; - } -} diff --git a/services/java/com/android/server/status/NotificationLinearLayout.java b/services/java/com/android/server/status/NotificationLinearLayout.java deleted file mode 100644 index 2fdf956..0000000 --- a/services/java/com/android/server/status/NotificationLinearLayout.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.LinearLayout; - - -public class NotificationLinearLayout extends LinearLayout { - public NotificationLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } -} - diff --git a/services/java/com/android/server/status/NotificationViewList.java b/services/java/com/android/server/status/NotificationViewList.java deleted file mode 100644 index 1bb56a7..0000000 --- a/services/java/com/android/server/status/NotificationViewList.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * 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.status; - -import android.os.IBinder; -import android.util.Slog; -import android.view.View; -import java.util.ArrayList; - -class NotificationViewList { - private ArrayList<StatusBarNotification> mOngoing = new ArrayList(); - private ArrayList<StatusBarNotification> mLatest = new ArrayList(); - - NotificationViewList() { - } - - private static final int indexInList(ArrayList<StatusBarNotification> list, NotificationData n){ - final int N = list.size(); - for (int i=0; i<N; i++) { - StatusBarNotification that = list.get(i); - if (that.data == n) { - return i; - } - } - return -1; - } - - int getIconIndex(NotificationData n) { - final int ongoingSize = mOngoing.size(); - final int latestSize = mLatest.size(); - if (n.ongoingEvent) { - int index = indexInList(mOngoing, n); - if (index >= 0) { - return latestSize + index + 1; - } else { - return -1; - } - } else { - return indexInList(mLatest, n) + 1; - } - } - - void remove(StatusBarNotification notification) { - NotificationData n = notification.data; - int index; - index = indexInList(mOngoing, n); - if (index >= 0) { - mOngoing.remove(index); - return; - } - index = indexInList(mLatest, n); - if (index >= 0) { - mLatest.remove(index); - return; - } - } - - ArrayList<StatusBarNotification> notificationsForPackage(String packageName) { - ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(); - int N = mOngoing.size(); - for (int i=0; i<N; i++) { - if (matchPackage(mOngoing.get(i), packageName)) { - list.add(mOngoing.get(i)); - } - } - N = mLatest.size(); - for (int i=0; i<N; i++) { - if (matchPackage(mLatest.get(i), packageName)) { - list.add(mLatest.get(i)); - } - } - return list; - } - - private final boolean matchPackage(StatusBarNotification snb, String packageName) { - if (snb.data.contentIntent != null) { - if (snb.data.contentIntent.getTargetPackage().equals(packageName)) { - return true; - } - } else if (snb.data.pkg != null && snb.data.pkg.equals(packageName)) { - return true; - } - return false; - } - - private static final int indexForKey(ArrayList<StatusBarNotification> list, IBinder key) { - final int N = list.size(); - for (int i=0; i<N; i++) { - if (list.get(i).key == key) { - return i; - } - } - return -1; - } - - StatusBarNotification get(IBinder key) { - int index; - index = indexForKey(mOngoing, key); - if (index >= 0) { - return mOngoing.get(index); - } - index = indexForKey(mLatest, key); - if (index >= 0) { - return mLatest.get(index); - } - return null; - } - - // gets the index of the notification's view in its expanded parent view - int getExpandedIndex(StatusBarNotification notification) { - ArrayList<StatusBarNotification> list = notification.data.ongoingEvent ? mOngoing : mLatest; - final IBinder key = notification.key; - int index = 0; - // (the view order is backwards from this list order) - for (int i=list.size()-1; i>=0; i--) { - StatusBarNotification item = list.get(i); - if (item.key == key) { - return index; - } - if (item.view != null) { - index++; - } - } - Slog.e(StatusBarService.TAG, "Couldn't find notification in NotificationViewList."); - Slog.e(StatusBarService.TAG, "notification=" + notification); - dump(notification); - return 0; - } - - void clearViews() { - int N = mOngoing.size(); - for (int i=0; i<N; i++) { - mOngoing.get(i).view = null; - } - N = mLatest.size(); - for (int i=0; i<N; i++) { - mLatest.get(i).view = null; - } - } - - int ongoingCount() { - return mOngoing.size(); - } - - int latestCount() { - return mLatest.size(); - } - - StatusBarNotification getOngoing(int index) { - return mOngoing.get(index); - } - - StatusBarNotification getLatest(int index) { - return mLatest.get(index); - } - - int size() { - return mOngoing.size() + mLatest.size(); - } - - void add(StatusBarNotification notification) { - if (StatusBarService.SPEW) { - Slog.d(StatusBarService.TAG, "before add NotificationViewList" - + " notification.data.ongoingEvent=" + notification.data.ongoingEvent); - dump(notification); - } - - ArrayList<StatusBarNotification> list = notification.data.ongoingEvent ? mOngoing : mLatest; - long when = notification.data.when; - final int N = list.size(); - int index = N; - for (int i=0; i<N; i++) { - StatusBarNotification that = list.get(i); - if (that.data.when > when) { - index = i; - break; - } - } - list.add(index, notification); - - if (StatusBarService.SPEW) { - Slog.d(StatusBarService.TAG, "after add NotificationViewList index=" + index); - dump(notification); - } - } - - void dump(StatusBarNotification notification) { - if (StatusBarService.SPEW) { - boolean showTime = false; - String s = ""; - for (int i=0; i<mOngoing.size(); i++) { - StatusBarNotification that = mOngoing.get(i); - if (that.key == notification.key) { - s += "["; - } - if (showTime) { - s += that.data.when; - } else { - s += that.data.pkg + "/" + that.data.id + "/" + that.view; - } - if (that.key == notification.key) { - s += "]"; - } - s += " "; - } - Slog.d(StatusBarService.TAG, "NotificationViewList ongoing: " + s); - - s = ""; - for (int i=0; i<mLatest.size(); i++) { - StatusBarNotification that = mLatest.get(i); - if (that.key == notification.key) { - s += "["; - } - if (showTime) { - s += that.data.when; - } else { - s += that.data.pkg + "/" + that.data.id + "/" + that.view; - } - if (that.key == notification.key) { - s += "]"; - } - s += " "; - } - Slog.d(StatusBarService.TAG, "NotificationViewList latest: " + s); - } - } - - StatusBarNotification get(View view) { - int N = mOngoing.size(); - for (int i=0; i<N; i++) { - StatusBarNotification notification = mOngoing.get(i); - View v = notification.view; - if (v == view) { - return notification; - } - } - N = mLatest.size(); - for (int i=0; i<N; i++) { - StatusBarNotification notification = mLatest.get(i); - View v = notification.view; - if (v == view) { - return notification; - } - } - return null; - } - - void update(StatusBarNotification notification) { - remove(notification); - add(notification); - } - - boolean hasClearableItems() { - int N = mLatest.size(); - for (int i=0; i<N; i++) { - if (mLatest.get(i).data.clearable) { - return true; - } - } - return false; - } -} diff --git a/services/java/com/android/server/status/StatusBarIcon.java b/services/java/com/android/server/status/StatusBarIcon.java deleted file mode 100644 index 6f8b8a8..0000000 --- a/services/java/com/android/server/status/StatusBarIcon.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; -import android.util.Slog; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -class StatusBarIcon { - // TODO: get this from a resource - private static final int ICON_GAP = 8; - private static final int ICON_WIDTH = 25; - private static final int ICON_HEIGHT = 25; - - public View view; - - IconData mData; - - private TextView mTextView; - private AnimatedImageView mImageView; - private TextView mNumberView; - - public StatusBarIcon(Context context, IconData data, ViewGroup parent) { - mData = data.clone(); - - switch (data.type) { - case IconData.TEXT: { - TextView t; - t = new TextView(context); - mTextView = t; - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT); - t.setTextSize(16); - t.setTextColor(0xff000000); - t.setTypeface(Typeface.DEFAULT_BOLD); - t.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); - t.setPadding(6, 0, 0, 0); - t.setLayoutParams(layoutParams); - t.setText(data.text); - this.view = t; - break; - } - - case IconData.ICON: { - // container - LayoutInflater inflater = (LayoutInflater)context.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View v = inflater.inflate(com.android.internal.R.layout.status_bar_icon, parent, false); - this.view = v; - - // icon - AnimatedImageView im = (AnimatedImageView)v.findViewById(com.android.internal.R.id.image); - im.setImageDrawable(getIcon(context, data)); - im.setImageLevel(data.iconLevel); - mImageView = im; - - // number - TextView nv = (TextView)v.findViewById(com.android.internal.R.id.number); - mNumberView = nv; - if (data.number > 0) { - nv.setText("" + data.number); - nv.setVisibility(View.VISIBLE); - } else { - nv.setVisibility(View.GONE); - } - break; - } - } - } - - public void update(Context context, IconData data) throws StatusBarException { - if (mData.type != data.type) { - throw new StatusBarException("status bar entry type can't change"); - } - switch (data.type) { - case IconData.TEXT: - if (!TextUtils.equals(mData.text, data.text)) { - TextView tv = mTextView; - tv.setText(data.text); - } - break; - case IconData.ICON: - if (((mData.iconPackage != null && data.iconPackage != null) - && !mData.iconPackage.equals(data.iconPackage)) - || mData.iconId != data.iconId - || mData.iconLevel != data.iconLevel) { - ImageView im = mImageView; - im.setImageDrawable(getIcon(context, data)); - im.setImageLevel(data.iconLevel); - } - if (mData.number != data.number) { - TextView nv = mNumberView; - if (data.number > 0) { - nv.setText("" + data.number); - } else { - nv.setText(""); - } - } - break; - } - mData.copyFrom(data); - } - - public void update(int number) { - if (mData.number != number) { - TextView nv = mNumberView; - if (number > 0) { - nv.setText("" + number); - } else { - nv.setText(""); - } - } - mData.number = number; - } - - - /** - * Returns the right icon to use for this item, respecting the iconId and - * iconPackage (if set) - * - * @param context Context to use to get resources if iconPackage is not set - * @return Drawable for this item, or null if the package or item could not - * be found - */ - static Drawable getIcon(Context context, IconData data) { - - Resources r = null; - - if (data.iconPackage != null) { - try { - r = context.getPackageManager().getResourcesForApplication(data.iconPackage); - } catch (PackageManager.NameNotFoundException ex) { - Slog.e(StatusBarService.TAG, "Icon package not found: " + data.iconPackage, ex); - return null; - } - } else { - r = context.getResources(); - } - - if (data.iconId == 0) { - Slog.w(StatusBarService.TAG, "No icon ID for slot " + data.slot); - return null; - } - - try { - return r.getDrawable(data.iconId); - } catch (RuntimeException e) { - Slog.w(StatusBarService.TAG, "Icon not found in " - + (data.iconPackage != null ? data.iconId : "<system>") - + ": " + Integer.toHexString(data.iconId)); - } - - return null; - } - - int getNumber() { - return mData.number; - } -} - diff --git a/services/java/com/android/server/status/StatusBarNotification.java b/services/java/com/android/server/status/StatusBarNotification.java deleted file mode 100644 index e5773f7..0000000 --- a/services/java/com/android/server/status/StatusBarNotification.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.status; - -import android.os.IBinder; -import android.view.View; - -class StatusBarNotification { - IBinder key; - NotificationData data; - View view; - View contentView; -} diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java deleted file mode 100644 index 3b0c436..0000000 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ /dev/null @@ -1,1390 +0,0 @@ -/* - * 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.status; - -import android.app.AlertDialog; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothPbap; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.TypedArray; -import android.graphics.PixelFormat; -import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.net.wifi.WifiManager; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.RemoteException; -import android.os.storage.StorageManager; -import android.provider.Settings; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.SignalStrength; -import android.telephony.TelephonyManager; -import android.text.format.DateFormat; -import android.text.style.RelativeSizeSpan; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.util.Slog; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.WindowManagerImpl; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.internal.R; -import com.android.internal.app.IBatteryStats; -import com.android.internal.location.GpsLocationProvider; -import com.android.internal.telephony.IccCard; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.cdma.EriInfo; -import com.android.internal.telephony.cdma.TtyIntent; -import com.android.server.am.BatteryStatsService; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.TimeZone; - -/** - * This class contains all of the policy about which icons are installed in the status - * bar at boot time. In reality, it should go into the android.policy package, but - * putting it here is the first step from extracting it. - */ -public class StatusBarPolicy { - private static final String TAG = "StatusBarPolicy"; - - private static StatusBarPolicy sInstance; - - // message codes for the handler - private static final int EVENT_BATTERY_CLOSE = 4; - - private final Context mContext; - private final StatusBarService mService; - private final Handler mHandler = new StatusBarHandler(); - private final IBatteryStats mBatteryStats; - - // clock - private Calendar mCalendar; - private String mClockFormatString; - private SimpleDateFormat mClockFormat; - private IBinder mClockIcon; - private IconData mClockData; - - // storage - private StorageManager mStorageManager; - - // battery - private IBinder mBatteryIcon; - private IconData mBatteryData; - private boolean mBatteryFirst = true; - private boolean mBatteryPlugged; - private int mBatteryLevel; - private AlertDialog mLowBatteryDialog; - private TextView mBatteryLevelTextView; - private View mBatteryView; - private int mBatteryViewSequence; - private boolean mBatteryShowLowOnEndCall = false; - private static final boolean SHOW_LOW_BATTERY_WARNING = true; - private static final boolean SHOW_BATTERY_WARNINGS_IN_CALL = true; - - // phone - private TelephonyManager mPhone; - private IBinder mPhoneIcon; - - //***** Signal strength icons - private IconData mPhoneData; - //GSM/UMTS - private static final int[] sSignalImages = new int[] { - com.android.internal.R.drawable.stat_sys_signal_0, - com.android.internal.R.drawable.stat_sys_signal_1, - com.android.internal.R.drawable.stat_sys_signal_2, - com.android.internal.R.drawable.stat_sys_signal_3, - com.android.internal.R.drawable.stat_sys_signal_4 - }; - private static final int[] sSignalImages_r = new int[] { - com.android.internal.R.drawable.stat_sys_r_signal_0, - com.android.internal.R.drawable.stat_sys_r_signal_1, - com.android.internal.R.drawable.stat_sys_r_signal_2, - com.android.internal.R.drawable.stat_sys_r_signal_3, - com.android.internal.R.drawable.stat_sys_r_signal_4 - }; - private static final int[] sRoamingIndicatorImages_cdma = new int[] { - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //Standard Roaming Indicator - // 1 is Standard Roaming Indicator OFF - // TODO T: image never used, remove and put 0 instead? - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 2 is Standard Roaming Indicator FLASHING - // TODO T: image never used, remove and put 0 instead? - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 3-12 Standard ERI - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //3 - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 13-63 Reserved for Standard ERI - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //13 - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - - // 64-127 Reserved for Non Standard (Operator Specific) ERI - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, //64 - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0, - com.android.internal.R.drawable.stat_sys_roaming_cdma_0 //83 - - // 128-255 Reserved - }; - - //***** Data connection icons - private int[] mDataIconList = sDataNetType_g; - //GSM/UMTS - private static final int[] sDataNetType_g = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_g, - com.android.internal.R.drawable.stat_sys_data_in_g, - com.android.internal.R.drawable.stat_sys_data_out_g, - com.android.internal.R.drawable.stat_sys_data_inandout_g, - }; - private static final int[] sDataNetType_3g = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_3g, - com.android.internal.R.drawable.stat_sys_data_in_3g, - com.android.internal.R.drawable.stat_sys_data_out_3g, - com.android.internal.R.drawable.stat_sys_data_inandout_3g, - }; - private static final int[] sDataNetType_e = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_e, - com.android.internal.R.drawable.stat_sys_data_in_e, - com.android.internal.R.drawable.stat_sys_data_out_e, - com.android.internal.R.drawable.stat_sys_data_inandout_e, - }; - //3.5G - private static final int[] sDataNetType_h = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_h, - com.android.internal.R.drawable.stat_sys_data_in_h, - com.android.internal.R.drawable.stat_sys_data_out_h, - com.android.internal.R.drawable.stat_sys_data_inandout_h, - }; - - //CDMA - // Use 3G icons for EVDO data and 1x icons for 1XRTT data - private static final int[] sDataNetType_1x = new int[] { - com.android.internal.R.drawable.stat_sys_data_connected_1x, - com.android.internal.R.drawable.stat_sys_data_in_1x, - com.android.internal.R.drawable.stat_sys_data_out_1x, - com.android.internal.R.drawable.stat_sys_data_inandout_1x, - }; - - // Assume it's all good unless we hear otherwise. We don't always seem - // to get broadcasts that it *is* there. - IccCard.State mSimState = IccCard.State.READY; - int mPhoneState = TelephonyManager.CALL_STATE_IDLE; - int mDataState = TelephonyManager.DATA_DISCONNECTED; - int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE; - ServiceState mServiceState; - SignalStrength mSignalStrength; - - // data connection - private IBinder mDataIcon; - private IconData mDataData; - private boolean mDataIconVisible; - private boolean mHspaDataDistinguishable; - - // ringer volume - private IBinder mVolumeIcon; - private IconData mVolumeData; - private boolean mVolumeVisible; - - // bluetooth device status - private IBinder mBluetoothIcon; - private IconData mBluetoothData; - private int mBluetoothHeadsetState; - private boolean mBluetoothA2dpConnected; - private int mBluetoothPbapState; - private boolean mBluetoothEnabled; - - // wifi - private static final int[] sWifiSignalImages = new int[] { - com.android.internal.R.drawable.stat_sys_wifi_signal_1, - com.android.internal.R.drawable.stat_sys_wifi_signal_2, - com.android.internal.R.drawable.stat_sys_wifi_signal_3, - com.android.internal.R.drawable.stat_sys_wifi_signal_4, - }; - private static final int sWifiTemporarilyNotConnectedImage = - com.android.internal.R.drawable.stat_sys_wifi_signal_0; - - private int mLastWifiSignalLevel = -1; - private boolean mIsWifiConnected = false; - private IBinder mWifiIcon; - private IconData mWifiData; - - // gps - private IBinder mGpsIcon; - private IconData mGpsEnabledIconData; - private IconData mGpsFixIconData; - - // alarm clock - // Icon lit when clock is set - private IBinder mAlarmClockIcon; - private IconData mAlarmClockIconData; - - // sync state - // If sync is active the SyncActive icon is displayed. If sync is not active but - // sync is failing the SyncFailing icon is displayed. Otherwise neither are displayed. - private IBinder mSyncActiveIcon; - private IBinder mSyncFailingIcon; - - // TTY mode - // Icon lit when TTY mode is enabled - private IBinder mTTYModeIcon; - private IconData mTTYModeEnableIconData; - - // Cdma Roaming Indicator, ERI - private IBinder mCdmaRoamingIndicatorIcon; - private IconData mCdmaRoamingIndicatorIconData; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Intent.ACTION_TIME_TICK)) { - updateClock(); - } - else if (action.equals(Intent.ACTION_TIME_CHANGED)) { - updateClock(); - } - else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - updateBattery(intent); - } - else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { - updateClock(); - } - else if (action.equals(Intent.ACTION_TIMEZONE_CHANGED)) { - String tz = intent.getStringExtra("time-zone"); - mCalendar = Calendar.getInstance(TimeZone.getTimeZone(tz)); - if (mClockFormat != null) { - mClockFormat.setTimeZone(mCalendar.getTimeZone()); - } - updateClock(); - } - else if (action.equals(Intent.ACTION_ALARM_CHANGED)) { - updateAlarm(intent); - } - else if (action.equals(Intent.ACTION_SYNC_STATE_CHANGED)) { - updateSyncState(intent); - } - else if (action.equals(Intent.ACTION_BATTERY_LOW)) { - onBatteryLow(intent); - } - else if (action.equals(Intent.ACTION_BATTERY_OKAY) - || action.equals(Intent.ACTION_POWER_CONNECTED)) { - onBatteryOkay(intent); - } - else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) || - action.equals(BluetoothHeadset.ACTION_STATE_CHANGED) || - action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED) || - action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { - updateBluetooth(intent); - } - else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) || - action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) || - action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - updateWifi(intent); - } - else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) || - action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION)) { - updateGps(intent); - } - else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) || - action.equals(AudioManager.VIBRATE_SETTING_CHANGED_ACTION)) { - updateVolume(); - } - else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) { - updateSimState(intent); - } - else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) { - updateTTY(intent); - } - } - }; - - private StatusBarPolicy(Context context, StatusBarService service) { - mContext = context; - mService = service; - mSignalStrength = new SignalStrength(); - mBatteryStats = BatteryStatsService.getService(); - - // clock - mCalendar = Calendar.getInstance(TimeZone.getDefault()); - mClockData = IconData.makeText("clock", ""); - mClockIcon = service.addIcon(mClockData, null); - updateClock(); - - // storage - mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - mStorageManager.registerListener( - new com.android.server.status.StorageNotification(context)); - - // battery - mBatteryData = IconData.makeIcon("battery", - null, com.android.internal.R.drawable.stat_sys_battery_unknown, 0, 0); - mBatteryIcon = service.addIcon(mBatteryData, null); - - // phone_signal - mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); - mPhoneData = IconData.makeIcon("phone_signal", - null, com.android.internal.R.drawable.stat_sys_signal_null, 0, 0); - mPhoneIcon = service.addIcon(mPhoneData, null); - - // register for phone state notifications. - ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .listen(mPhoneStateListener, - PhoneStateListener.LISTEN_SERVICE_STATE - | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS - | PhoneStateListener.LISTEN_CALL_STATE - | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_DATA_ACTIVITY); - - // data_connection - mDataData = IconData.makeIcon("data_connection", - null, com.android.internal.R.drawable.stat_sys_data_connected_g, 0, 0); - mDataIcon = service.addIcon(mDataData, null); - service.setIconVisibility(mDataIcon, false); - - // wifi - mWifiData = IconData.makeIcon("wifi", null, sWifiSignalImages[0], 0, 0); - mWifiIcon = service.addIcon(mWifiData, null); - service.setIconVisibility(mWifiIcon, false); - // wifi will get updated by the sticky intents - - // TTY status - mTTYModeEnableIconData = IconData.makeIcon("tty", - null, com.android.internal.R.drawable.stat_sys_tty_mode, 0, 0); - mTTYModeIcon = service.addIcon(mTTYModeEnableIconData, null); - service.setIconVisibility(mTTYModeIcon, false); - - // Cdma Roaming Indicator, ERI - mCdmaRoamingIndicatorIconData = IconData.makeIcon("cdma_eri", - null, com.android.internal.R.drawable.stat_sys_roaming_cdma_0, 0, 0); - mCdmaRoamingIndicatorIcon = service.addIcon(mCdmaRoamingIndicatorIconData, null); - service.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - - // bluetooth status - mBluetoothData = IconData.makeIcon("bluetooth", - null, com.android.internal.R.drawable.stat_sys_data_bluetooth, 0, 0); - mBluetoothIcon = service.addIcon(mBluetoothData, null); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - mBluetoothEnabled = adapter.isEnabled(); - } else { - mBluetoothEnabled = false; - } - mBluetoothA2dpConnected = false; - mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED; - mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED; - mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled); - - // Gps status - mGpsEnabledIconData = IconData.makeIcon("gps", - null, com.android.internal.R.drawable.stat_sys_gps_acquiring_anim, 0, 0); - mGpsFixIconData = IconData.makeIcon("gps", - null, com.android.internal.R.drawable.stat_sys_gps_on, 0, 0); - mGpsIcon = service.addIcon(mGpsEnabledIconData, null); - service.setIconVisibility(mGpsIcon, false); - - // Alarm clock - mAlarmClockIconData = IconData.makeIcon( - "alarm_clock", - null, com.android.internal.R.drawable.stat_notify_alarm, 0, 0); - mAlarmClockIcon = service.addIcon(mAlarmClockIconData, null); - service.setIconVisibility(mAlarmClockIcon, false); - - // Sync state - mSyncActiveIcon = service.addIcon(IconData.makeIcon("sync_active", - null, R.drawable.stat_notify_sync_anim0, 0, 0), null); - mSyncFailingIcon = service.addIcon(IconData.makeIcon("sync_failing", - null, R.drawable.stat_notify_sync_error, 0, 0), null); - service.setIconVisibility(mSyncActiveIcon, false); - service.setIconVisibility(mSyncFailingIcon, false); - - // volume - mVolumeData = IconData.makeIcon("volume", - null, com.android.internal.R.drawable.stat_sys_ringer_silent, 0, 0); - mVolumeIcon = service.addIcon(mVolumeData, null); - service.setIconVisibility(mVolumeIcon, false); - updateVolume(); - - IntentFilter filter = new IntentFilter(); - - // Register for Intent broadcasts for... - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_BATTERY_LOW); - filter.addAction(Intent.ACTION_BATTERY_OKAY); - filter.addAction(Intent.ACTION_POWER_CONNECTED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_ALARM_CHANGED); - filter.addAction(Intent.ACTION_SYNC_STATE_CHANGED); - filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - filter.addAction(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); - filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION); - filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - filter.addAction(WifiManager.RSSI_CHANGED_ACTION); - filter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION); - filter.addAction(GpsLocationProvider.GPS_FIX_CHANGE_ACTION); - filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); - filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION); - mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); - - // load config to determine if to distinguish Hspa data icon - try { - mHspaDataDistinguishable = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_hspa_data_distinguishable); - } catch (Exception e) { - mHspaDataDistinguishable = false; - } - } - - public static void installIcons(Context context, StatusBarService service) { - sInstance = new StatusBarPolicy(context, service); - } - - private final CharSequence getSmallTime() { - boolean b24 = DateFormat.is24HourFormat(mContext); - int res; - - if (b24) { - res = R.string.twenty_four_hour_time_format; - } else { - res = R.string.twelve_hour_time_format; - } - - final char MAGIC1 = '\uEF00'; - final char MAGIC2 = '\uEF01'; - - SimpleDateFormat sdf; - String format = mContext.getString(res); - if (!format.equals(mClockFormatString)) { - /* - * Search for an unquoted "a" in the format string, so we can - * add dummy characters around it to let us find it again after - * formatting and change its size. - */ - int a = -1; - boolean quoted = false; - for (int i = 0; i < format.length(); i++) { - char c = format.charAt(i); - - if (c == '\'') { - quoted = !quoted; - } - - if (!quoted && c == 'a') { - a = i; - break; - } - } - - if (a >= 0) { - // Move a back so any whitespace before the AM/PM is also in the alternate size. - final int b = a; - while (a > 0 && Character.isWhitespace(format.charAt(a-1))) { - a--; - } - format = format.substring(0, a) + MAGIC1 + format.substring(a, b) - + "a" + MAGIC2 + format.substring(b + 1); - } - - mClockFormat = sdf = new SimpleDateFormat(format); - mClockFormatString = format; - } else { - sdf = mClockFormat; - } - String result = sdf.format(mCalendar.getTime()); - - int magic1 = result.indexOf(MAGIC1); - int magic2 = result.indexOf(MAGIC2); - - if (magic1 >= 0 && magic2 > magic1) { - SpannableStringBuilder formatted = new SpannableStringBuilder(result); - - formatted.setSpan(new RelativeSizeSpan(0.7f), magic1, magic2, - Spannable.SPAN_EXCLUSIVE_INCLUSIVE); - - formatted.delete(magic2, magic2 + 1); - formatted.delete(magic1, magic1 + 1); - - return formatted; - } else { - return result; - } - } - - private final void updateClock() { - mCalendar.setTimeInMillis(System.currentTimeMillis()); - mClockData.text = getSmallTime(); - mService.updateIcon(mClockIcon, mClockData, null); - } - - private final void updateAlarm(Intent intent) { - boolean alarmSet = intent.getBooleanExtra("alarmSet", false); - mService.setIconVisibility(mAlarmClockIcon, alarmSet); - } - - private final void updateSyncState(Intent intent) { - boolean isActive = intent.getBooleanExtra("active", false); - boolean isFailing = intent.getBooleanExtra("failing", false); - mService.setIconVisibility(mSyncActiveIcon, isActive); - // Don't display sync failing icon: BUG 1297963 Set sync error timeout to "never" - //mService.setIconVisibility(mSyncFailingIcon, isFailing && !isActive); - } - - private final void updateBattery(Intent intent) { - mBatteryData.iconId = intent.getIntExtra("icon-small", 0); - mBatteryData.iconLevel = intent.getIntExtra("level", 0); - mService.updateIcon(mBatteryIcon, mBatteryData, null); - - boolean plugged = intent.getIntExtra("plugged", 0) != 0; - int level = intent.getIntExtra("level", -1); - if (false) { - Slog.d(TAG, "updateBattery level=" + level - + " plugged=" + plugged - + " mBatteryPlugged=" + mBatteryPlugged - + " mBatteryLevel=" + mBatteryLevel - + " mBatteryFirst=" + mBatteryFirst); - } - - boolean oldPlugged = mBatteryPlugged; - - mBatteryPlugged = plugged; - mBatteryLevel = level; - - if (mBatteryFirst) { - mBatteryFirst = false; - } - /* - * No longer showing the battery view because it draws attention away - * from the USB storage notification. We could still show it when - * connected to a brick, but that could lead to the user into thinking - * the device does not charge when plugged into USB (since he/she would - * not see the same battery screen on USB as he sees on brick). - */ - /* else { - if (plugged && !oldPlugged) { - showBatteryView(); - } - } - */ - if (false) { - Slog.d(TAG, "plugged=" + plugged + " oldPlugged=" + oldPlugged + " level=" + level); - } - } - - private void onBatteryLow(Intent intent) { - if (SHOW_LOW_BATTERY_WARNING) { - if (false) { - Slog.d(TAG, "mPhoneState=" + mPhoneState - + " mLowBatteryDialog=" + mLowBatteryDialog - + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); - } - - if (SHOW_BATTERY_WARNINGS_IN_CALL || mPhoneState == TelephonyManager.CALL_STATE_IDLE) { - showLowBatteryWarning(); - } else { - mBatteryShowLowOnEndCall = true; - } - } - } - - private void onBatteryOkay(Intent intent) { - if (mLowBatteryDialog != null - && SHOW_LOW_BATTERY_WARNING) { - mLowBatteryDialog.dismiss(); - mBatteryShowLowOnEndCall = false; - } - } - - private void showBatteryView() { - closeLastBatteryView(); - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - } - - int level = mBatteryLevel; - - View v = View.inflate(mContext, com.android.internal.R.layout.battery_status, null); - mBatteryView = v; - int pixelFormat = PixelFormat.TRANSLUCENT; - Drawable bg = v.getBackground(); - if (bg != null) { - pixelFormat = bg.getOpacity(); - } - - int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_DIM_BEHIND; - - if (!mContext.getResources().getBoolean( - com.android.internal.R.bool.config_sf_slowBlur)) { - flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND; - } - - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_TOAST, - flags, pixelFormat); - - // Get the dim amount from the theme - TypedArray a = mContext.obtainStyledAttributes( - com.android.internal.R.styleable.Theme); - lp.dimAmount = a.getFloat(android.R.styleable.Theme_backgroundDimAmount, 0.5f); - a.recycle(); - - lp.setTitle("Battery"); - - TextView levelTextView = (TextView)v.findViewById(com.android.internal.R.id.level_percent); - levelTextView.setText(mContext.getString( - com.android.internal.R.string.battery_status_text_percent_format, level)); - - setBatteryLevel(v, com.android.internal.R.id.spacer, 100-level, 0, 0); - setBatteryLevel(v, com.android.internal.R.id.level, level, - com.android.internal.R.drawable.battery_charge_fill, level); - - WindowManagerImpl.getDefault().addView(v, lp); - - scheduleCloseBatteryView(); - } - - private void setBatteryLevel(View parent, int id, int height, int background, int level) { - ImageView v = (ImageView)parent.findViewById(id); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)v.getLayoutParams(); - lp.weight = height; - if (background != 0) { - v.setBackgroundResource(background); - Drawable bkg = v.getBackground(); - bkg.setLevel(level); - } - } - - private void showLowBatteryWarning() { - closeLastBatteryView(); - - // Show exact battery level. - CharSequence levelText = mContext.getString( - com.android.internal.R.string.battery_low_percent_format, mBatteryLevel); - - if (mBatteryLevelTextView != null) { - mBatteryLevelTextView.setText(levelText); - } else { - View v = View.inflate(mContext, com.android.internal.R.layout.battery_low, null); - mBatteryLevelTextView=(TextView)v.findViewById(com.android.internal.R.id.level_percent); - - mBatteryLevelTextView.setText(levelText); - - AlertDialog.Builder b = new AlertDialog.Builder(mContext); - b.setCancelable(true); - b.setTitle(com.android.internal.R.string.battery_low_title); - b.setView(v); - b.setIcon(android.R.drawable.ic_dialog_alert); - b.setPositiveButton(android.R.string.ok, null); - - final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_MULTIPLE_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_NO_HISTORY); - if (intent.resolveActivity(mContext.getPackageManager()) != null) { - b.setNegativeButton(com.android.internal.R.string.battery_low_why, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - mContext.startActivity(intent); - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - } - } - }); - } - - AlertDialog d = b.create(); - d.setOnDismissListener(mLowBatteryListener); - d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - d.show(); - mLowBatteryDialog = d; - } - - final ContentResolver cr = mContext.getContentResolver(); - if (Settings.System.getInt(cr, - Settings.System.POWER_SOUNDS_ENABLED, 1) == 1) - { - final String soundPath = Settings.System.getString(cr, - Settings.System.LOW_BATTERY_SOUND); - if (soundPath != null) { - final Uri soundUri = Uri.parse("file://" + soundPath); - if (soundUri != null) { - final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); - if (sfx != null) { - sfx.setStreamType(AudioManager.STREAM_SYSTEM); - sfx.play(); - } - } - } - } - } - - private final void updateCallState(int state) { - mPhoneState = state; - if (false) { - Slog.d(TAG, "mPhoneState=" + mPhoneState - + " mLowBatteryDialog=" + mLowBatteryDialog - + " mBatteryShowLowOnEndCall=" + mBatteryShowLowOnEndCall); - } - if (mPhoneState == TelephonyManager.CALL_STATE_IDLE) { - if (mBatteryShowLowOnEndCall) { - if (!mBatteryPlugged) { - showLowBatteryWarning(); - } - mBatteryShowLowOnEndCall = false; - } - } else { - if (mLowBatteryDialog != null) { - mLowBatteryDialog.dismiss(); - mBatteryShowLowOnEndCall = true; - } - } - } - - private DialogInterface.OnDismissListener mLowBatteryListener - = new DialogInterface.OnDismissListener() { - public void onDismiss(DialogInterface dialog) { - mLowBatteryDialog = null; - mBatteryLevelTextView = null; - } - }; - - private void scheduleCloseBatteryView() { - Message m = mHandler.obtainMessage(EVENT_BATTERY_CLOSE); - m.arg1 = (++mBatteryViewSequence); - mHandler.sendMessageDelayed(m, 3000); - } - - private void closeLastBatteryView() { - if (mBatteryView != null) { - //mBatteryView.debug(); - WindowManagerImpl.getDefault().removeView(mBatteryView); - mBatteryView = null; - } - } - - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onSignalStrengthsChanged(SignalStrength signalStrength) { - mSignalStrength = signalStrength; - updateSignalStrength(); - } - - @Override - public void onServiceStateChanged(ServiceState state) { - mServiceState = state; - updateSignalStrength(); - updateCdmaRoamingIcon(state); - updateDataIcon(); - } - - @Override - public void onCallStateChanged(int state, String incomingNumber) { - updateCallState(state); - // In cdma, if a voice call is made, RSSI should switch to 1x. - if (isCdma()) { - updateSignalStrength(); - } - } - - @Override - public void onDataConnectionStateChanged(int state, int networkType) { - mDataState = state; - updateDataNetType(networkType); - updateDataIcon(); - } - - @Override - public void onDataActivity(int direction) { - mDataActivity = direction; - updateDataIcon(); - } - }; - - private final void updateSimState(Intent intent) { - String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE); - if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { - mSimState = IccCard.State.ABSENT; - } - else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) { - mSimState = IccCard.State.READY; - } - else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { - final String lockedReason = intent.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON); - if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { - mSimState = IccCard.State.PIN_REQUIRED; - } - else if (IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { - mSimState = IccCard.State.PUK_REQUIRED; - } - else { - mSimState = IccCard.State.NETWORK_LOCKED; - } - } else { - mSimState = IccCard.State.UNKNOWN; - } - updateDataIcon(); - } - - private boolean isCdma() { - return (mSignalStrength != null) && !mSignalStrength.isGsm(); - } - - private boolean isEvdo() { - return ( (mServiceState != null) - && ((mServiceState.getRadioTechnology() - == ServiceState.RADIO_TECHNOLOGY_EVDO_0) - || (mServiceState.getRadioTechnology() - == ServiceState.RADIO_TECHNOLOGY_EVDO_A) - || (mServiceState.getRadioTechnology() - == ServiceState.RADIO_TECHNOLOGY_EVDO_B))); - } - - private boolean hasService() { - if (mServiceState != null) { - switch (mServiceState.getState()) { - case ServiceState.STATE_OUT_OF_SERVICE: - case ServiceState.STATE_POWER_OFF: - return false; - default: - return true; - } - } else { - return false; - } - } - - private final void updateSignalStrength() { - int iconLevel = -1; - int[] iconList; - - // Display signal strength while in "emergency calls only" mode - if (!hasService() && !mServiceState.isEmergencyOnly()) { - //Slog.d(TAG, "updateSignalStrength: no service"); - if (Settings.System.getInt(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) == 1) { - mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_flightmode; - } else { - mPhoneData.iconId = com.android.internal.R.drawable.stat_sys_signal_null; - } - mService.updateIcon(mPhoneIcon, mPhoneData, null); - return; - } - - if (!isCdma()) { - int asu = mSignalStrength.getGsmSignalStrength(); - - // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 - // asu = 0 (-113dB or less) is very weak - // signal, its better to show 0 bars to the user in such cases. - // asu = 99 is a special case, where the signal strength is unknown. - if (asu <= 2 || asu == 99) iconLevel = 0; - else if (asu >= 12) iconLevel = 4; - else if (asu >= 8) iconLevel = 3; - else if (asu >= 5) iconLevel = 2; - else iconLevel = 1; - - // Though mPhone is a Manager, this call is not an IPC - if (mPhone.isNetworkRoaming()) { - iconList = sSignalImages_r; - } else { - iconList = sSignalImages; - } - } else { - iconList = this.sSignalImages; - - // If 3G(EV) and 1x network are available than 3G should be - // displayed, displayed RSSI should be from the EV side. - // If a voice call is made then RSSI should switch to 1x. - if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){ - iconLevel = getEvdoLevel(); - if (false) { - Slog.d(TAG, "use Evdo level=" + iconLevel + " to replace Cdma Level=" + getCdmaLevel()); - } - } else { - iconLevel = getCdmaLevel(); - } - } - mPhoneData.iconId = iconList[iconLevel]; - mService.updateIcon(mPhoneIcon, mPhoneData, null); - } - - private int getCdmaLevel() { - final int cdmaDbm = mSignalStrength.getCdmaDbm(); - final int cdmaEcio = mSignalStrength.getCdmaEcio(); - int levelDbm = 0; - int levelEcio = 0; - - if (cdmaDbm >= -75) levelDbm = 4; - else if (cdmaDbm >= -85) levelDbm = 3; - else if (cdmaDbm >= -95) levelDbm = 2; - else if (cdmaDbm >= -100) levelDbm = 1; - else levelDbm = 0; - - // Ec/Io are in dB*10 - if (cdmaEcio >= -90) levelEcio = 4; - else if (cdmaEcio >= -110) levelEcio = 3; - else if (cdmaEcio >= -130) levelEcio = 2; - else if (cdmaEcio >= -150) levelEcio = 1; - else levelEcio = 0; - - return (levelDbm < levelEcio) ? levelDbm : levelEcio; - } - - private int getEvdoLevel() { - int evdoDbm = mSignalStrength.getEvdoDbm(); - int evdoSnr = mSignalStrength.getEvdoSnr(); - int levelEvdoDbm = 0; - int levelEvdoSnr = 0; - - if (evdoDbm >= -65) levelEvdoDbm = 4; - else if (evdoDbm >= -75) levelEvdoDbm = 3; - else if (evdoDbm >= -90) levelEvdoDbm = 2; - else if (evdoDbm >= -105) levelEvdoDbm = 1; - else levelEvdoDbm = 0; - - if (evdoSnr >= 7) levelEvdoSnr = 4; - else if (evdoSnr >= 5) levelEvdoSnr = 3; - else if (evdoSnr >= 3) levelEvdoSnr = 2; - else if (evdoSnr >= 1) levelEvdoSnr = 1; - else levelEvdoSnr = 0; - - return (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; - } - - private final void updateDataNetType(int net) { - switch (net) { - case TelephonyManager.NETWORK_TYPE_EDGE: - mDataIconList = sDataNetType_e; - break; - case TelephonyManager.NETWORK_TYPE_UMTS: - mDataIconList = sDataNetType_3g; - break; - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - if (mHspaDataDistinguishable) { - mDataIconList = sDataNetType_h; - } else { - mDataIconList = sDataNetType_3g; - } - break; - case TelephonyManager.NETWORK_TYPE_CDMA: - // display 1xRTT for IS95A/B - mDataIconList = this.sDataNetType_1x; - break; - case TelephonyManager.NETWORK_TYPE_1xRTT: - mDataIconList = this.sDataNetType_1x; - break; - case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - mDataIconList = sDataNetType_3g; - break; - default: - mDataIconList = sDataNetType_g; - break; - } - } - - private final void updateDataIcon() { - int iconId; - boolean visible = true; - - if (!isCdma()) { - // GSM case, we have to check also the sim state - if (mSimState == IccCard.State.READY || mSimState == IccCard.State.UNKNOWN) { - if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { - switch (mDataActivity) { - case TelephonyManager.DATA_ACTIVITY_IN: - iconId = mDataIconList[1]; - break; - case TelephonyManager.DATA_ACTIVITY_OUT: - iconId = mDataIconList[2]; - break; - case TelephonyManager.DATA_ACTIVITY_INOUT: - iconId = mDataIconList[3]; - break; - default: - iconId = mDataIconList[0]; - break; - } - mDataData.iconId = iconId; - mService.updateIcon(mDataIcon, mDataData, null); - } else { - visible = false; - } - } else { - mDataData.iconId = com.android.internal.R.drawable.stat_sys_no_sim; - mService.updateIcon(mDataIcon, mDataData, null); - } - } else { - // CDMA case, mDataActivity can be also DATA_ACTIVITY_DORMANT - if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { - switch (mDataActivity) { - case TelephonyManager.DATA_ACTIVITY_IN: - iconId = mDataIconList[1]; - break; - case TelephonyManager.DATA_ACTIVITY_OUT: - iconId = mDataIconList[2]; - break; - case TelephonyManager.DATA_ACTIVITY_INOUT: - iconId = mDataIconList[3]; - break; - case TelephonyManager.DATA_ACTIVITY_DORMANT: - default: - iconId = mDataIconList[0]; - break; - } - mDataData.iconId = iconId; - mService.updateIcon(mDataIcon, mDataData, null); - } else { - visible = false; - } - } - - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.notePhoneDataConnectionState(mPhone.getNetworkType(), visible); - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - if (mDataIconVisible != visible) { - mService.setIconVisibility(mDataIcon, visible); - mDataIconVisible = visible; - } - } - - private final void updateVolume() { - AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - final int ringerMode = audioManager.getRingerMode(); - final boolean visible = ringerMode == AudioManager.RINGER_MODE_SILENT || - ringerMode == AudioManager.RINGER_MODE_VIBRATE; - final int iconId = (ringerMode == AudioManager.RINGER_MODE_VIBRATE) - ? com.android.internal.R.drawable.stat_sys_ringer_vibrate - : com.android.internal.R.drawable.stat_sys_ringer_silent; - - if (visible) { - mVolumeData.iconId = iconId; - mService.updateIcon(mVolumeIcon, mVolumeData, null); - } - if (visible != mVolumeVisible) { - mService.setIconVisibility(mVolumeIcon, visible); - mVolumeVisible = visible; - } - } - - private final void updateBluetooth(Intent intent) { - int iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth; - String action = intent.getAction(); - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); - mBluetoothEnabled = state == BluetoothAdapter.STATE_ON; - } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { - mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, - BluetoothHeadset.STATE_ERROR); - } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { - BluetoothA2dp a2dp = new BluetoothA2dp(mContext); - if (a2dp.getConnectedSinks().size() != 0) { - mBluetoothA2dpConnected = true; - } else { - mBluetoothA2dpConnected = false; - } - } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) { - mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE, - BluetoothPbap.STATE_DISCONNECTED); - } else { - return; - } - - if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED || mBluetoothA2dpConnected || - mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) { - iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth_connected; - } - - mBluetoothData.iconId = iconId; - mService.updateIcon(mBluetoothIcon, mBluetoothData, null); - mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled); - } - - private final void updateWifi(Intent intent) { - final String action = intent.getAction(); - if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - - final boolean enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; - - if (!enabled) { - // If disabled, hide the icon. (We show icon when connected.) - mService.setIconVisibility(mWifiIcon, false); - } - - } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) { - final boolean enabled = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, - false); - if (!enabled) { - mService.setIconVisibility(mWifiIcon, false); - } - } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - - final NetworkInfo networkInfo = (NetworkInfo) - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - - int iconId; - if (networkInfo != null && networkInfo.isConnected()) { - mIsWifiConnected = true; - if (mLastWifiSignalLevel == -1) { - iconId = sWifiSignalImages[0]; - } else { - iconId = sWifiSignalImages[mLastWifiSignalLevel]; - } - - // Show the icon since wi-fi is connected - mService.setIconVisibility(mWifiIcon, true); - - } else { - mLastWifiSignalLevel = -1; - mIsWifiConnected = false; - iconId = sWifiSignalImages[0]; - - // Hide the icon since we're not connected - mService.setIconVisibility(mWifiIcon, false); - } - - mWifiData.iconId = iconId; - mService.updateIcon(mWifiIcon, mWifiData, null); - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { - final int newRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); - int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, - sWifiSignalImages.length); - if (newSignalLevel != mLastWifiSignalLevel) { - mLastWifiSignalLevel = newSignalLevel; - if (mIsWifiConnected) { - mWifiData.iconId = sWifiSignalImages[newSignalLevel]; - } else { - mWifiData.iconId = sWifiTemporarilyNotConnectedImage; - } - mService.updateIcon(mWifiIcon, mWifiData, null); - } - } - } - - private final void updateGps(Intent intent) { - final String action = intent.getAction(); - final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false); - - if (action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION) && enabled) { - // GPS is getting fixes - mService.updateIcon(mGpsIcon, mGpsFixIconData, null); - mService.setIconVisibility(mGpsIcon, true); - } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) && !enabled) { - // GPS is off - mService.setIconVisibility(mGpsIcon, false); - } else { - // GPS is on, but not receiving fixes - mService.updateIcon(mGpsIcon, mGpsEnabledIconData, null); - mService.setIconVisibility(mGpsIcon, true); - } - } - - private final void updateTTY(Intent intent) { - final String action = intent.getAction(); - final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false); - - if (false) Slog.v(TAG, "updateTTY: enabled: " + enabled); - - if (enabled) { - // TTY is on - if (false) Slog.v(TAG, "updateTTY: set TTY on"); - mService.updateIcon(mTTYModeIcon, mTTYModeEnableIconData, null); - mService.setIconVisibility(mTTYModeIcon, true); - } else { - // TTY is off - if (false) Slog.v(TAG, "updateTTY: set TTY off"); - mService.setIconVisibility(mTTYModeIcon, false); - } - } - - private final void updateCdmaRoamingIcon(ServiceState state) { - if (!hasService()) { - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - return; - } - - if (!isCdma()) { - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - return; - } - - int[] iconList = sRoamingIndicatorImages_cdma; - int iconIndex = state.getCdmaEriIconIndex(); - int iconMode = state.getCdmaEriIconMode(); - - if (iconIndex == -1) { - Slog.e(TAG, "getCdmaEriIconIndex returned null, skipping ERI icon update"); - return; - } - - if (iconMode == -1) { - Slog.e(TAG, "getCdmeEriIconMode returned null, skipping ERI icon update"); - return; - } - - if (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) { - if (false) Slog.v(TAG, "Cdma ROAMING_INDICATOR_OFF, removing ERI icon"); - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); - return; - } - - switch (iconMode) { - case EriInfo.ROAMING_ICON_MODE_NORMAL: - mCdmaRoamingIndicatorIconData.iconId = iconList[iconIndex]; - mService.updateIcon(mCdmaRoamingIndicatorIcon, mCdmaRoamingIndicatorIconData, null); - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, true); - break; - case EriInfo.ROAMING_ICON_MODE_FLASH: - mCdmaRoamingIndicatorIconData.iconId = - com.android.internal.R.drawable.stat_sys_roaming_cdma_flash; - mService.updateIcon(mCdmaRoamingIndicatorIcon, mCdmaRoamingIndicatorIconData, null); - mService.setIconVisibility(mCdmaRoamingIndicatorIcon, true); - break; - - } - mService.updateIcon(mPhoneIcon, mPhoneData, null); - } - - - private class StatusBarHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case EVENT_BATTERY_CLOSE: - if (msg.arg1 == mBatteryViewSequence) { - closeLastBatteryView(); - } - break; - } - } - } -} diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java deleted file mode 100644 index 93c8d34..0000000 --- a/services/java/com/android/server/status/StatusBarService.java +++ /dev/null @@ -1,1881 +0,0 @@ -/* - * Copyright (C) 2007 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.status; - -import com.android.internal.R; -import com.android.internal.util.CharSequences; - -import android.app.ActivityManagerNative; -import android.app.Dialog; -import android.app.IStatusBar; -import android.app.PendingIntent; -import android.app.StatusBarManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.Binder; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.provider.Telephony; -import android.util.Slog; -import android.view.Display; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.WindowManagerImpl; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.LinearLayout; -import android.widget.RemoteViews; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.FrameLayout; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Set; - - -/** - * The public (ok, semi-public) service for the status bar. - * <p> - * This interesting thing to note about this class is that most of the methods that - * are called from other classes just post a message, and everything else is batched - * and coalesced into a series of calls to methods that all start with "perform." - * There are two reasons for this. The first is that some of the methods (activate/deactivate) - * are on IStatusBar, so they're called from the thread pool and they need to make their - * way onto the UI thread. The second is that the message queue is stopped while animations - * are happening in order to make for smoother transitions. - * <p> - * Each icon is either an icon or an icon and a notification. They're treated mostly - * separately throughout the code, although they both use the same key, which is assigned - * when they are created. - */ -public class StatusBarService extends IStatusBar.Stub -{ - static final String TAG = "StatusBar"; - static final boolean SPEW = false; - - static final int EXPANDED_LEAVE_ALONE = -10000; - static final int EXPANDED_FULL_OPEN = -10001; - - private static final int MSG_ANIMATE = 1000; - private static final int MSG_ANIMATE_REVEAL = 1001; - - private static final int OP_ADD_ICON = 1; - private static final int OP_UPDATE_ICON = 2; - private static final int OP_REMOVE_ICON = 3; - private static final int OP_SET_VISIBLE = 4; - private static final int OP_EXPAND = 5; - private static final int OP_TOGGLE = 6; - private static final int OP_DISABLE = 7; - private class PendingOp { - IBinder key; - int code; - IconData iconData; - NotificationData notificationData; - boolean visible; - int integer; - } - - private class DisableRecord implements IBinder.DeathRecipient { - String pkg; - int what; - IBinder token; - - public void binderDied() { - Slog.i(TAG, "binder died for pkg=" + pkg); - disable(0, token, pkg); - token.unlinkToDeath(this, 0); - } - } - - public interface NotificationCallbacks { - void onSetDisabled(int status); - void onClearAll(); - void onNotificationClick(String pkg, String tag, int id); - void onPanelRevealed(); - } - - private class ExpandedDialog extends Dialog { - ExpandedDialog(Context context) { - super(context, com.android.internal.R.style.Theme_Light_NoTitleBar); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean down = event.getAction() == KeyEvent.ACTION_DOWN; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BACK: - if (!down) { - StatusBarService.this.deactivate(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - } - - final Context mContext; - final Display mDisplay; - StatusBarView mStatusBarView; - int mPixelFormat; - H mHandler = new H(); - Object mQueueLock = new Object(); - ArrayList<PendingOp> mQueue = new ArrayList<PendingOp>(); - NotificationCallbacks mNotificationCallbacks; - - // All accesses to mIconMap and mNotificationData are syncronized on those objects, - // but this is only so dump() can work correctly. Modifying these outside of the UI - // thread will not work, there are places in the code that unlock and reaquire between - // reads and require them to not be modified. - - // icons - HashMap<IBinder,StatusBarIcon> mIconMap = new HashMap<IBinder,StatusBarIcon>(); - ArrayList<StatusBarIcon> mIconList = new ArrayList<StatusBarIcon>(); - String[] mRightIconSlots; - StatusBarIcon[] mRightIcons; - LinearLayout mIcons; - IconMerger mNotificationIcons; - LinearLayout mStatusIcons; - StatusBarIcon mMoreIcon; - private UninstallReceiver mUninstallReceiver; - - // expanded notifications - NotificationViewList mNotificationData = new NotificationViewList(); - Dialog mExpandedDialog; - ExpandedView mExpandedView; - WindowManager.LayoutParams mExpandedParams; - ScrollView mScrollView; - View mNotificationLinearLayout; - TextView mOngoingTitle; - LinearLayout mOngoingItems; - TextView mLatestTitle; - LinearLayout mLatestItems; - TextView mNoNotificationsTitle; - TextView mSpnLabel; - TextView mPlmnLabel; - TextView mClearButton; - View mExpandedContents; - CloseDragHandle mCloseView; - int[] mPositionTmp = new int[2]; - boolean mExpanded; - boolean mExpandedVisible; - - // the date view - DateView mDateView; - - // the tracker view - TrackingView mTrackingView; - WindowManager.LayoutParams mTrackingParams; - int mTrackingPosition; // the position of the top of the tracking view. - - // ticker - private Ticker mTicker; - private View mTickerView; - private boolean mTicking; - - // Tracking finger for opening/closing. - int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore - boolean mTracking; - VelocityTracker mVelocityTracker; - - static final int ANIM_FRAME_DURATION = (1000/60); - - boolean mAnimating; - long mCurAnimationTime; - float mDisplayHeight; - float mAnimY; - float mAnimVel; - float mAnimAccel; - long mAnimLastTime; - boolean mAnimatingReveal = false; - int mViewDelta; - int[] mAbsPos = new int[2]; - - // for disabling the status bar - ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); - int mDisabled = 0; - - /** - * Construct the service, add the status bar view to the window manager - */ - public StatusBarService(Context context) { - mContext = context; - mDisplay = ((WindowManager)context.getSystemService( - Context.WINDOW_SERVICE)).getDefaultDisplay(); - makeStatusBarView(context); - mUninstallReceiver = new UninstallReceiver(); - } - - public void setNotificationCallbacks(NotificationCallbacks listener) { - mNotificationCallbacks = listener; - } - - // ================================================================================ - // Constructing the view - // ================================================================================ - private void makeStatusBarView(Context context) { - Resources res = context.getResources(); - mRightIconSlots = res.getStringArray(com.android.internal.R.array.status_bar_icon_order); - mRightIcons = new StatusBarIcon[mRightIconSlots.length]; - - ExpandedView expanded = (ExpandedView)View.inflate(context, - com.android.internal.R.layout.status_bar_expanded, null); - expanded.mService = this; - StatusBarView sb = (StatusBarView)View.inflate(context, - com.android.internal.R.layout.status_bar, null); - sb.mService = this; - - // figure out which pixel-format to use for the status bar. - mPixelFormat = PixelFormat.TRANSLUCENT; - Drawable bg = sb.getBackground(); - if (bg != null) { - mPixelFormat = bg.getOpacity(); - } - - mStatusBarView = sb; - mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons); - mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons); - mNotificationIcons.service = this; - mIcons = (LinearLayout)sb.findViewById(R.id.icons); - mTickerView = sb.findViewById(R.id.ticker); - mDateView = (DateView)sb.findViewById(R.id.date); - - mExpandedDialog = new ExpandedDialog(context); - mExpandedView = expanded; - mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout); - mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); - mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); - mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); - mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems); - mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); - mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button); - mClearButton.setOnClickListener(mClearButtonListener); - mSpnLabel = (TextView)expanded.findViewById(R.id.spnLabel); - mPlmnLabel = (TextView)expanded.findViewById(R.id.plmnLabel); - mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); - mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout); - - mOngoingTitle.setVisibility(View.GONE); - mLatestTitle.setVisibility(View.GONE); - - mTicker = new MyTicker(context, sb); - - TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); - tickerView.mTicker = mTicker; - - mTrackingView = (TrackingView)View.inflate(context, - com.android.internal.R.layout.status_bar_tracking, null); - mTrackingView.mService = this; - mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); - mCloseView.mService = this; - - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); - - // add the more icon for the notifications - IconData moreData = IconData.makeIcon(null, context.getPackageName(), - R.drawable.stat_notify_more, 0, 42); - mMoreIcon = new StatusBarIcon(context, moreData, mNotificationIcons); - mMoreIcon.view.setId(R.drawable.stat_notify_more); - mNotificationIcons.moreIcon = mMoreIcon; - mNotificationIcons.addView(mMoreIcon.view); - - // set the inital view visibility - setAreThereNotifications(); - mDateView.setVisibility(View.INVISIBLE); - - // before we register for broadcasts - mPlmnLabel.setText(R.string.lockscreen_carrier_default); - mPlmnLabel.setVisibility(View.VISIBLE); - mSpnLabel.setText(""); - mSpnLabel.setVisibility(View.GONE); - - // receive broadcasts - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Telephony.Intents.SPN_STRINGS_UPDATED_ACTION); - context.registerReceiver(mBroadcastReceiver, filter); - } - - public void systemReady() { - final StatusBarView view = mStatusBarView; - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - view.getContext().getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height), - WindowManager.LayoutParams.TYPE_STATUS_BAR, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| - WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING, - mPixelFormat); - lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; - lp.setTitle("StatusBar"); - lp.windowAnimations = R.style.Animation_StatusBar; - - WindowManagerImpl.getDefault().addView(view, lp); - } - - // ================================================================================ - // From IStatusBar - // ================================================================================ - public void activate() { - enforceExpandStatusBar(); - addPendingOp(OP_EXPAND, null, true); - } - - public void deactivate() { - enforceExpandStatusBar(); - addPendingOp(OP_EXPAND, null, false); - } - - public void toggle() { - enforceExpandStatusBar(); - addPendingOp(OP_TOGGLE, null, false); - } - - public void disable(int what, IBinder token, String pkg) { - enforceStatusBar(); - synchronized (mNotificationCallbacks) { - // This is a little gross, but I think it's safe as long as nobody else - // synchronizes on mNotificationCallbacks. It's important that the the callback - // and the pending op get done in the correct order and not interleaved with - // other calls, otherwise they'll get out of sync. - int net; - synchronized (mDisableRecords) { - manageDisableListLocked(what, token, pkg); - net = gatherDisableActionsLocked(); - mNotificationCallbacks.onSetDisabled(net); - } - addPendingOp(OP_DISABLE, net); - } - } - - public IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel) { - enforceStatusBar(); - return addIcon(IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); - } - - public void updateIcon(IBinder key, - String slot, String iconPackage, int iconId, int iconLevel) { - enforceStatusBar(); - updateIcon(key, IconData.makeIcon(slot, iconPackage, iconId, iconLevel, 0), null); - } - - public void removeIcon(IBinder key) { - enforceStatusBar(); - addPendingOp(OP_REMOVE_ICON, key, null, null, -1); - } - - private void enforceStatusBar() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.STATUS_BAR, - "StatusBarService"); - } - - private void enforceExpandStatusBar() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.EXPAND_STATUS_BAR, - "StatusBarService"); - } - - // ================================================================================ - // Can be called from any thread - // ================================================================================ - public IBinder addIcon(IconData data, NotificationData n) { - int slot; - // assert early-on if they using a slot that doesn't exist. - if (data != null && n == null) { - slot = getRightIconIndex(data.slot); - if (slot < 0) { - throw new SecurityException("invalid status bar icon slot: " - + (data.slot != null ? "'" + data.slot + "'" : "null")); - } - } else { - slot = -1; - } - IBinder key = new Binder(); - addPendingOp(OP_ADD_ICON, key, data, n, -1); - return key; - } - - public void updateIcon(IBinder key, IconData data, NotificationData n) { - addPendingOp(OP_UPDATE_ICON, key, data, n, -1); - } - - public void setIconVisibility(IBinder key, boolean visible) { - addPendingOp(OP_SET_VISIBLE, key, visible); - } - - private void addPendingOp(int code, IBinder key, IconData data, NotificationData n, int i) { - synchronized (mQueueLock) { - PendingOp op = new PendingOp(); - op.key = key; - op.code = code; - op.iconData = data == null ? null : data.clone(); - op.notificationData = n; - op.integer = i; - mQueue.add(op); - if (mQueue.size() == 1) { - mHandler.sendEmptyMessage(2); - } - } - } - - private void addPendingOp(int code, IBinder key, boolean visible) { - synchronized (mQueueLock) { - PendingOp op = new PendingOp(); - op.key = key; - op.code = code; - op.visible = visible; - mQueue.add(op); - if (mQueue.size() == 1) { - mHandler.sendEmptyMessage(1); - } - } - } - - private void addPendingOp(int code, int integer) { - synchronized (mQueueLock) { - PendingOp op = new PendingOp(); - op.code = code; - op.integer = integer; - mQueue.add(op); - if (mQueue.size() == 1) { - mHandler.sendEmptyMessage(1); - } - } - } - - // lock on mDisableRecords - void manageDisableListLocked(int what, IBinder token, String pkg) { - if (SPEW) { - Slog.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) - + " pkg=" + pkg); - } - // update the list - synchronized (mDisableRecords) { - final int N = mDisableRecords.size(); - DisableRecord tok = null; - int i; - for (i=0; i<N; i++) { - DisableRecord t = mDisableRecords.get(i); - if (t.token == token) { - tok = t; - break; - } - } - if (what == 0 || !token.isBinderAlive()) { - if (tok != null) { - mDisableRecords.remove(i); - tok.token.unlinkToDeath(tok, 0); - } - } else { - if (tok == null) { - tok = new DisableRecord(); - try { - token.linkToDeath(tok, 0); - } - catch (RemoteException ex) { - return; // give up - } - mDisableRecords.add(tok); - } - tok.what = what; - tok.token = token; - tok.pkg = pkg; - } - } - } - - // lock on mDisableRecords - int gatherDisableActionsLocked() { - final int N = mDisableRecords.size(); - // gather the new net flags - int net = 0; - for (int i=0; i<N; i++) { - net |= mDisableRecords.get(i).what; - } - return net; - } - - private int getRightIconIndex(String slot) { - final int N = mRightIconSlots.length; - for (int i=0; i<N; i++) { - if (mRightIconSlots[i].equals(slot)) { - return i; - } - } - return -1; - } - - // ================================================================================ - // Always called from UI thread - // ================================================================================ - /** - * All changes to the status bar and notifications funnel through here and are batched. - */ - private class H extends Handler { - public void handleMessage(Message m) { - if (m.what == MSG_ANIMATE) { - doAnimation(); - return; - } - if (m.what == MSG_ANIMATE_REVEAL) { - doRevealAnimation(); - return; - } - - ArrayList<PendingOp> queue; - synchronized (mQueueLock) { - queue = mQueue; - mQueue = new ArrayList<PendingOp>(); - } - - boolean wasExpanded = mExpanded; - - // for each one in the queue, find all of the ones with the same key - // and collapse that down into a final op and/or call to setVisibility, etc - boolean expand = wasExpanded; - boolean doExpand = false; - boolean doDisable = false; - int disableWhat = 0; - int N = queue.size(); - while (N > 0) { - PendingOp op = queue.get(0); - boolean doOp = false; - boolean visible = false; - boolean doVisibility = false; - if (op.code == OP_SET_VISIBLE) { - doVisibility = true; - visible = op.visible; - } - else if (op.code == OP_EXPAND) { - doExpand = true; - expand = op.visible; - } - else if (op.code == OP_TOGGLE) { - doExpand = true; - expand = !expand; - } - else { - doOp = true; - } - - if (alwaysHandle(op.code)) { - // coalesce these - for (int i=1; i<N; i++) { - PendingOp o = queue.get(i); - if (!alwaysHandle(o.code) && o.key == op.key) { - if (o.code == OP_SET_VISIBLE) { - visible = o.visible; - doVisibility = true; - } - else if (o.code == OP_EXPAND) { - expand = o.visible; - doExpand = true; - } - else { - op.code = o.code; - op.iconData = o.iconData; - op.notificationData = o.notificationData; - } - queue.remove(i); - i--; - N--; - } - } - } - - queue.remove(0); - N--; - - if (doOp) { - switch (op.code) { - case OP_ADD_ICON: - case OP_UPDATE_ICON: - performAddUpdateIcon(op.key, op.iconData, op.notificationData); - break; - case OP_REMOVE_ICON: - performRemoveIcon(op.key); - break; - case OP_DISABLE: - doDisable = true; - disableWhat = op.integer; - break; - } - } - if (doVisibility && op.code != OP_REMOVE_ICON) { - performSetIconVisibility(op.key, visible); - } - } - - if (queue.size() != 0) { - throw new RuntimeException("Assertion failed: queue.size=" + queue.size()); - } - if (doExpand) { - // this is last so that we capture all of the pending changes before doing it - if (expand) { - animateExpand(); - } else { - animateCollapse(); - } - } - if (doDisable) { - performDisableActions(disableWhat); - } - } - } - - private boolean alwaysHandle(int code) { - return code == OP_DISABLE; - } - - /* private */ void performAddUpdateIcon(IBinder key, IconData data, NotificationData n) - throws StatusBarException { - if (SPEW) { - Slog.d(TAG, "performAddUpdateIcon icon=" + data + " notification=" + n + " key=" + key); - } - // notification - if (n != null) { - StatusBarNotification notification = getNotification(key); - NotificationData oldData = null; - if (notification == null) { - // add - notification = new StatusBarNotification(); - notification.key = key; - notification.data = n; - synchronized (mNotificationData) { - mNotificationData.add(notification); - } - addNotificationView(notification); - setAreThereNotifications(); - } else { - // update - oldData = notification.data; - notification.data = n; - updateNotificationView(notification, oldData); - } - // Show the ticker if one is requested, and the text is different - // than the currently displayed ticker. Also don't do this - // until status bar window is attached to the window manager, - // because... well, what's the point otherwise? And trying to - // run a ticker without being attached will crash! - if (n.tickerText != null && mStatusBarView.getWindowToken() != null - && (oldData == null - || oldData.tickerText == null - || !CharSequences.equals(oldData.tickerText, n.tickerText))) { - if (0 == (mDisabled & - (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { - mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText); - } - } - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - } - - // icon - synchronized (mIconMap) { - StatusBarIcon icon = mIconMap.get(key); - if (icon == null) { - // add - LinearLayout v = n == null ? mStatusIcons : mNotificationIcons; - - icon = new StatusBarIcon(mContext, data, v); - mIconMap.put(key, icon); - mIconList.add(icon); - - if (n == null) { - int slotIndex = getRightIconIndex(data.slot); - StatusBarIcon[] rightIcons = mRightIcons; - if (rightIcons[slotIndex] == null) { - int pos = 0; - for (int i=mRightIcons.length-1; i>slotIndex; i--) { - StatusBarIcon ic = rightIcons[i]; - if (ic != null) { - pos++; - } - } - rightIcons[slotIndex] = icon; - mStatusIcons.addView(icon.view, pos); - } else { - Slog.e(TAG, "duplicate icon in slot " + slotIndex + "/" + data.slot); - mIconMap.remove(key); - mIconList.remove(icon); - return ; - } - } else { - int iconIndex = mNotificationData.getIconIndex(n); - mNotificationIcons.addView(icon.view, iconIndex); - } - } else { - if (n == null) { - // right hand side icons -- these don't reorder - icon.update(mContext, data); - } else { - // remove old - ViewGroup parent = (ViewGroup)icon.view.getParent(); - parent.removeView(icon.view); - // add new - icon.update(mContext, data); - int iconIndex = mNotificationData.getIconIndex(n); - mNotificationIcons.addView(icon.view, iconIndex); - } - } - } - } - - /* private */ void performSetIconVisibility(IBinder key, boolean visible) { - synchronized (mIconMap) { - if (SPEW) { - Slog.d(TAG, "performSetIconVisibility key=" + key + " visible=" + visible); - } - StatusBarIcon icon = mIconMap.get(key); - icon.view.setVisibility(visible ? View.VISIBLE : View.GONE); - } - } - - /* private */ void performRemoveIcon(IBinder key) { - synchronized (this) { - if (SPEW) { - Slog.d(TAG, "performRemoveIcon key=" + key); - } - StatusBarIcon icon = mIconMap.remove(key); - mIconList.remove(icon); - if (icon != null) { - ViewGroup parent = (ViewGroup)icon.view.getParent(); - parent.removeView(icon.view); - int slotIndex = getRightIconIndex(icon.mData.slot); - if (slotIndex >= 0) { - mRightIcons[slotIndex] = null; - } - } - StatusBarNotification notification = getNotification(key); - if (notification != null) { - removeNotificationView(notification); - synchronized (mNotificationData) { - mNotificationData.remove(notification); - } - setAreThereNotifications(); - } - } - } - - int getIconNumberForView(View v) { - synchronized (mIconMap) { - StatusBarIcon icon = null; - final int N = mIconList.size(); - for (int i=0; i<N; i++) { - StatusBarIcon ic = mIconList.get(i); - if (ic.view == v) { - icon = ic; - break; - } - } - if (icon != null) { - return icon.getNumber(); - } else { - return -1; - } - } - } - - - StatusBarNotification getNotification(IBinder key) { - synchronized (mNotificationData) { - return mNotificationData.get(key); - } - } - - View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { - public void onFocusChange(View v, boolean hasFocus) { - // Because 'v' is a ViewGroup, all its children will be (un)selected - // too, which allows marqueeing to work. - v.setSelected(hasFocus); - } - }; - - View makeNotificationView(StatusBarNotification notification, ViewGroup parent) { - NotificationData n = notification.data; - RemoteViews remoteViews = n.contentView; - if (remoteViews == null) { - return null; - } - - // create the row view - LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View row = inflater.inflate(com.android.internal.R.layout.status_bar_latest_event, parent, false); - - // bind the click event to the content area - ViewGroup content = (ViewGroup)row.findViewById(com.android.internal.R.id.content); - content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - content.setOnFocusChangeListener(mFocusChangeListener); - PendingIntent contentIntent = n.contentIntent; - if (contentIntent != null) { - content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); - } - - View child = null; - Exception exception = null; - try { - child = remoteViews.apply(mContext, content); - } - catch (RuntimeException e) { - exception = e; - } - if (child == null) { - Slog.e(TAG, "couldn't inflate view for package " + n.pkg, exception); - return null; - } - content.addView(child); - - row.setDrawingCacheEnabled(true); - - notification.view = row; - notification.contentView = child; - - return row; - } - - void addNotificationView(StatusBarNotification notification) { - if (notification.view != null) { - throw new RuntimeException("Assertion failed: notification.view=" - + notification.view); - } - - LinearLayout parent = notification.data.ongoingEvent ? mOngoingItems : mLatestItems; - - View child = makeNotificationView(notification, parent); - if (child == null) { - return ; - } - - int index = mNotificationData.getExpandedIndex(notification); - parent.addView(child, index); - } - - /** - * Remove the old one and put the new one in its place. - * @param notification the notification - */ - void updateNotificationView(StatusBarNotification notification, NotificationData oldData) { - NotificationData n = notification.data; - if (oldData != null && n != null - && n.when == oldData.when - && n.ongoingEvent == oldData.ongoingEvent - && n.contentView != null && oldData.contentView != null - && n.contentView.getPackage() != null - && oldData.contentView.getPackage() != null - && oldData.contentView.getPackage().equals(n.contentView.getPackage()) - && oldData.contentView.getLayoutId() == n.contentView.getLayoutId() - && notification.view != null) { - mNotificationData.update(notification); - try { - n.contentView.reapply(mContext, notification.contentView); - - // update the contentIntent - ViewGroup content = (ViewGroup)notification.view.findViewById( - com.android.internal.R.id.content); - PendingIntent contentIntent = n.contentIntent; - if (contentIntent != null) { - content.setOnClickListener(new Launcher(contentIntent, n.pkg, n.tag, n.id)); - } - } - catch (RuntimeException e) { - // It failed to add cleanly. Log, and remove the view from the panel. - Slog.w(TAG, "couldn't reapply views for package " + n.contentView.getPackage(), e); - removeNotificationView(notification); - } - } else { - mNotificationData.update(notification); - removeNotificationView(notification); - addNotificationView(notification); - } - setAreThereNotifications(); - } - - void removeNotificationView(StatusBarNotification notification) { - View v = notification.view; - if (v != null) { - ViewGroup parent = (ViewGroup)v.getParent(); - parent.removeView(v); - notification.view = null; - } - } - - private void setAreThereNotifications() { - boolean ongoing = mOngoingItems.getChildCount() != 0; - boolean latest = mLatestItems.getChildCount() != 0; - - if (mNotificationData.hasClearableItems()) { - mClearButton.setVisibility(View.VISIBLE); - } else { - mClearButton.setVisibility(View.INVISIBLE); - } - - mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); - mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); - - if (ongoing || latest) { - mNoNotificationsTitle.setVisibility(View.GONE); - } else { - mNoNotificationsTitle.setVisibility(View.VISIBLE); - } - } - - private void makeExpandedVisible() { - if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); - if (mExpandedVisible) { - return; - } - mExpandedVisible = true; - panelSlightlyVisible(true); - - updateExpandedViewPos(EXPANDED_LEAVE_ALONE); - mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - mExpandedView.requestFocus(View.FOCUS_FORWARD); - mTrackingView.setVisibility(View.VISIBLE); - - if (!mTicking) { - setDateViewVisibility(true, com.android.internal.R.anim.fade_in); - } - } - - void animateExpand() { - if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded); - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return ; - } - if (mExpanded) { - return; - } - - prepareTracking(0, true); - performFling(0, 2000.0f, true); - } - - void animateCollapse() { - if (SPEW) { - Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded - + " mExpandedVisible=" + mExpandedVisible - + " mExpanded=" + mExpanded - + " mAnimating=" + mAnimating - + " mAnimY=" + mAnimY - + " mAnimVel=" + mAnimVel); - } - - if (!mExpandedVisible) { - return; - } - - int y; - if (mAnimating) { - y = (int)mAnimY; - } else { - y = mDisplay.getHeight()-1; - } - // Let the fling think that we're open so it goes in the right direction - // and doesn't try to re-open the windowshade. - mExpanded = true; - prepareTracking(y, false); - performFling(y, -2000.0f, true); - } - - void performExpand() { - if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded); - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return ; - } - if (mExpanded) { - return; - } - - // It seems strange to sometimes not expand... - if (false) { - synchronized (mNotificationData) { - if (mNotificationData.size() == 0) { - return; - } - } - } - - mExpanded = true; - makeExpandedVisible(); - updateExpandedViewPos(EXPANDED_FULL_OPEN); - - if (false) postStartTracing(); - } - - void performCollapse() { - if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded - + " mExpandedVisible=" + mExpandedVisible); - - if (!mExpandedVisible) { - return; - } - mExpandedVisible = false; - panelSlightlyVisible(false); - mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - mTrackingView.setVisibility(View.GONE); - - if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { - setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); - } - setDateViewVisibility(false, com.android.internal.R.anim.fade_out); - - if (!mExpanded) { - return; - } - mExpanded = false; - } - - void doAnimation() { - if (mAnimating) { - if (SPEW) Slog.d(TAG, "doAnimation"); - if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY); - incrementAnim(); - if (SPEW) Slog.d(TAG, "doAnimation after mAnimY=" + mAnimY); - if (mAnimY >= mDisplay.getHeight()-1) { - if (SPEW) Slog.d(TAG, "Animation completed to expanded state."); - mAnimating = false; - updateExpandedViewPos(EXPANDED_FULL_OPEN); - performExpand(); - } - else if (mAnimY < mStatusBarView.getHeight()) { - if (SPEW) Slog.d(TAG, "Animation completed to collapsed state."); - mAnimating = false; - updateExpandedViewPos(0); - performCollapse(); - } - else { - updateExpandedViewPos((int)mAnimY); - mCurAnimationTime += ANIM_FRAME_DURATION; - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); - } - } - } - - void stopTracking() { - mTracking = false; - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - - void incrementAnim() { - long now = SystemClock.uptimeMillis(); - float t = ((float)(now - mAnimLastTime)) / 1000; // ms -> s - final float y = mAnimY; - final float v = mAnimVel; // px/s - final float a = mAnimAccel; // px/s/s - mAnimY = y + (v*t) + (0.5f*a*t*t); // px - mAnimVel = v + (a*t); // px/s - mAnimLastTime = now; // ms - //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY - // + " mAnimAccel=" + mAnimAccel); - } - - void doRevealAnimation() { - final int h = mCloseView.getHeight() + mStatusBarView.getHeight(); - if (mAnimatingReveal && mAnimating && mAnimY < h) { - incrementAnim(); - if (mAnimY >= h) { - mAnimY = h; - updateExpandedViewPos((int)mAnimY); - } else { - updateExpandedViewPos((int)mAnimY); - mCurAnimationTime += ANIM_FRAME_DURATION; - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), - mCurAnimationTime); - } - } - } - - void prepareTracking(int y, boolean opening) { - mTracking = true; - mVelocityTracker = VelocityTracker.obtain(); - if (opening) { - mAnimAccel = 2000.0f; - mAnimVel = 200; - mAnimY = mStatusBarView.getHeight(); - updateExpandedViewPos((int)mAnimY); - mAnimating = true; - mAnimatingReveal = true; - mHandler.removeMessages(MSG_ANIMATE); - mHandler.removeMessages(MSG_ANIMATE_REVEAL); - long now = SystemClock.uptimeMillis(); - mAnimLastTime = now; - mCurAnimationTime = now + ANIM_FRAME_DURATION; - mAnimating = true; - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), - mCurAnimationTime); - makeExpandedVisible(); - } else { - // it's open, close it? - if (mAnimating) { - mAnimating = false; - mHandler.removeMessages(MSG_ANIMATE); - } - updateExpandedViewPos(y + mViewDelta); - } - } - - void performFling(int y, float vel, boolean always) { - mAnimatingReveal = false; - mDisplayHeight = mDisplay.getHeight(); - - mAnimY = y; - mAnimVel = vel; - - //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); - - if (mExpanded) { - if (!always && ( - vel > 200.0f - || (y > (mDisplayHeight-25) && vel > -200.0f))) { - // We are expanded, but they didn't move sufficiently to cause - // us to retract. Animate back to the expanded position. - mAnimAccel = 2000.0f; - if (vel < 0) { - mAnimVel = 0; - } - } - else { - // We are expanded and are now going to animate away. - mAnimAccel = -2000.0f; - if (vel > 0) { - mAnimVel = 0; - } - } - } else { - if (always || ( - vel > 200.0f - || (y > (mDisplayHeight/2) && vel > -200.0f))) { - // We are collapsed, and they moved enough to allow us to - // expand. Animate in the notifications. - mAnimAccel = 2000.0f; - if (vel < 0) { - mAnimVel = 0; - } - } - else { - // We are collapsed, but they didn't move sufficiently to cause - // us to retract. Animate back to the collapsed position. - mAnimAccel = -2000.0f; - if (vel > 0) { - mAnimVel = 0; - } - } - } - //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel - // + " mAnimAccel=" + mAnimAccel); - - long now = SystemClock.uptimeMillis(); - mAnimLastTime = now; - mCurAnimationTime = now + ANIM_FRAME_DURATION; - mAnimating = true; - mHandler.removeMessages(MSG_ANIMATE); - mHandler.removeMessages(MSG_ANIMATE_REVEAL); - mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); - stopTracking(); - } - - boolean interceptTouchEvent(MotionEvent event) { - if (SPEW) { - Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" - + mDisabled); - } - - if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { - return false; - } - - final int statusBarSize = mStatusBarView.getHeight(); - final int hitSize = statusBarSize*2; - if (event.getAction() == MotionEvent.ACTION_DOWN) { - final int y = (int)event.getRawY(); - - if (!mExpanded) { - mViewDelta = statusBarSize - y; - } else { - mTrackingView.getLocationOnScreen(mAbsPos); - mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y; - } - if ((!mExpanded && y < hitSize) || - (mExpanded && y > (mDisplay.getHeight()-hitSize))) { - - // We drop events at the edge of the screen to make the windowshade come - // down by accident less, especially when pushing open a device with a keyboard - // that rotates (like g1 and droid) - int x = (int)event.getRawX(); - final int edgeBorder = mEdgeBorder; - if (x >= edgeBorder && x < mDisplay.getWidth() - edgeBorder) { - prepareTracking(y, !mExpanded);// opening if we're not already fully visible - mVelocityTracker.addMovement(event); - } - } - } else if (mTracking) { - mVelocityTracker.addMovement(event); - final int minY = statusBarSize + mCloseView.getHeight(); - if (event.getAction() == MotionEvent.ACTION_MOVE) { - int y = (int)event.getRawY(); - if (mAnimatingReveal && y < minY) { - // nothing - } else { - mAnimatingReveal = false; - updateExpandedViewPos(y + mViewDelta); - } - } else if (event.getAction() == MotionEvent.ACTION_UP) { - mVelocityTracker.computeCurrentVelocity(1000); - - float yVel = mVelocityTracker.getYVelocity(); - boolean negative = yVel < 0; - - float xVel = mVelocityTracker.getXVelocity(); - if (xVel < 0) { - xVel = -xVel; - } - if (xVel > 150.0f) { - xVel = 150.0f; // limit how much we care about the x axis - } - - float vel = (float)Math.hypot(yVel, xVel); - if (negative) { - vel = -vel; - } - - performFling((int)event.getRawY(), vel, false); - } - - } - return false; - } - - private class Launcher implements View.OnClickListener { - private PendingIntent mIntent; - private String mPkg; - private String mTag; - private int mId; - - Launcher(PendingIntent intent, String pkg, String tag, int id) { - mIntent = intent; - mPkg = pkg; - mTag = tag; - mId = id; - } - - public void onClick(View v) { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManagerNative.getDefault().resumeAppSwitches(); - } catch (RemoteException e) { - } - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - Intent overlay = new Intent(); - overlay.setSourceBounds( - new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); - try { - mIntent.send(mContext, 0, overlay); - mNotificationCallbacks.onNotificationClick(mPkg, mTag, mId); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. Just log the exception message. - Slog.w(TAG, "Sending contentIntent failed: " + e); - } - deactivate(); - } - } - - private class MyTicker extends Ticker { - MyTicker(Context context, StatusBarView sb) { - super(context, sb); - } - - @Override - void tickerStarting() { - mTicking = true; - mIcons.setVisibility(View.GONE); - mTickerView.setVisibility(View.VISIBLE); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); - mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); - if (mExpandedVisible) { - setDateViewVisibility(false, com.android.internal.R.anim.push_up_out); - } - } - - @Override - void tickerDone() { - mIcons.setVisibility(View.VISIBLE); - mTickerView.setVisibility(View.GONE); - mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, - mTickingDoneListener)); - if (mExpandedVisible) { - setDateViewVisibility(true, com.android.internal.R.anim.push_down_in); - } - } - - void tickerHalting() { - mIcons.setVisibility(View.VISIBLE); - mTickerView.setVisibility(View.GONE); - mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); - mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out, - mTickingDoneListener)); - if (mExpandedVisible) { - setDateViewVisibility(true, com.android.internal.R.anim.fade_in); - } - } - } - - Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; - public void onAnimationEnd(Animation animation) { - mTicking = false; - } - public void onAnimationRepeat(Animation animation) { - } - public void onAnimationStart(Animation animation) { - } - }; - - private Animation loadAnim(int id, Animation.AnimationListener listener) { - Animation anim = AnimationUtils.loadAnimation(mContext, id); - if (listener != null) { - anim.setAnimationListener(listener); - } - return anim; - } - - public String viewInfo(View v) { - return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() - + " " + v.getWidth() + "x" + v.getHeight() + ")"; - } - - 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 StatusBar from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - synchronized (mQueueLock) { - pw.println("Current Status Bar state:"); - pw.println(" mExpanded=" + mExpanded - + ", mExpandedVisible=" + mExpandedVisible); - pw.println(" mTicking=" + mTicking); - pw.println(" mTracking=" + mTracking); - pw.println(" mAnimating=" + mAnimating - + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel - + ", mAnimAccel=" + mAnimAccel); - pw.println(" mCurAnimationTime=" + mCurAnimationTime - + " mAnimLastTime=" + mAnimLastTime); - pw.println(" mDisplayHeight=" + mDisplayHeight - + " mAnimatingReveal=" + mAnimatingReveal - + " mViewDelta=" + mViewDelta); - pw.println(" mDisplayHeight=" + mDisplayHeight); - final int N = mQueue.size(); - pw.println(" mQueue.size=" + N); - for (int i=0; i<N; i++) { - PendingOp op = mQueue.get(i); - pw.println(" [" + i + "] key=" + op.key + " code=" + op.code + " visible=" - + op.visible); - pw.println(" iconData=" + op.iconData); - pw.println(" notificationData=" + op.notificationData); - } - pw.println(" mExpandedParams: " + mExpandedParams); - pw.println(" mExpandedView: " + viewInfo(mExpandedView)); - pw.println(" mExpandedDialog: " + mExpandedDialog); - pw.println(" mTrackingParams: " + mTrackingParams); - pw.println(" mTrackingView: " + viewInfo(mTrackingView)); - pw.println(" mOngoingTitle: " + viewInfo(mOngoingTitle)); - pw.println(" mOngoingItems: " + viewInfo(mOngoingItems)); - pw.println(" mLatestTitle: " + viewInfo(mLatestTitle)); - pw.println(" mLatestItems: " + viewInfo(mLatestItems)); - pw.println(" mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle)); - pw.println(" mCloseView: " + viewInfo(mCloseView)); - pw.println(" mTickerView: " + viewInfo(mTickerView)); - pw.println(" mScrollView: " + viewInfo(mScrollView) - + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); - pw.println("mNotificationLinearLayout: " + viewInfo(mNotificationLinearLayout)); - } - synchronized (mIconMap) { - final int N = mIconMap.size(); - pw.println(" mIconMap.size=" + N); - Set<IBinder> keys = mIconMap.keySet(); - int i=0; - for (IBinder key: keys) { - StatusBarIcon icon = mIconMap.get(key); - pw.println(" [" + i + "] key=" + key); - pw.println(" data=" + icon.mData); - i++; - } - } - synchronized (mNotificationData) { - int N = mNotificationData.ongoingCount(); - pw.println(" ongoingCount.size=" + N); - for (int i=0; i<N; i++) { - StatusBarNotification n = mNotificationData.getOngoing(i); - pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); - pw.println(" data=" + n.data); - } - N = mNotificationData.latestCount(); - pw.println(" ongoingCount.size=" + N); - for (int i=0; i<N; i++) { - StatusBarNotification n = mNotificationData.getLatest(i); - pw.println(" [" + i + "] key=" + n.key + " view=" + n.view); - pw.println(" data=" + n.data); - } - } - synchronized (mDisableRecords) { - final int N = mDisableRecords.size(); - pw.println(" mDisableRecords.size=" + N - + " mDisabled=0x" + Integer.toHexString(mDisabled)); - for (int i=0; i<N; i++) { - DisableRecord tok = mDisableRecords.get(i); - pw.println(" [" + i + "] what=0x" + Integer.toHexString(tok.what) - + " pkg=" + tok.pkg + " token=" + tok.token); - } - } - - if (false) { - pw.println("see the logcat for a dump of the views we have created."); - // must happen on ui thread - mHandler.post(new Runnable() { - public void run() { - mStatusBarView.getLocationOnScreen(mAbsPos); - Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mStatusBarView.getWidth() + "x" - + mStatusBarView.getHeight()); - mStatusBarView.debug(); - - mExpandedView.getLocationOnScreen(mAbsPos); - Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mExpandedView.getWidth() + "x" - + mExpandedView.getHeight()); - mExpandedView.debug(); - - mTrackingView.getLocationOnScreen(mAbsPos); - Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] - + ") " + mTrackingView.getWidth() + "x" - + mTrackingView.getHeight()); - mTrackingView.debug(); - } - }); - } - } - - void onBarViewAttached() { - WindowManager.LayoutParams lp; - int pixelFormat; - Drawable bg; - - /// ---------- Tracking View -------------- - pixelFormat = PixelFormat.RGBX_8888; - bg = mTrackingView.getBackground(); - if (bg != null) { - pixelFormat = bg.getOpacity(); - } - - lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, - pixelFormat); -// lp.token = mStatusBarView.getWindowToken(); - lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; - lp.setTitle("TrackingView"); - lp.y = mTrackingPosition; - mTrackingParams = lp; - - WindowManagerImpl.getDefault().addView(mTrackingView, lp); - } - - void onTrackingViewAttached() { - WindowManager.LayoutParams lp; - int pixelFormat; - Drawable bg; - - /// ---------- Expanded View -------------- - pixelFormat = PixelFormat.TRANSLUCENT; - - final int disph = mDisplay.getHeight(); - lp = mExpandedDialog.getWindow().getAttributes(); - lp.width = ViewGroup.LayoutParams.MATCH_PARENT; - lp.height = getExpandedHeight(); - lp.x = 0; - mTrackingPosition = lp.y = -disph; // sufficiently large negative - lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; - lp.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_DITHER - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - lp.format = pixelFormat; - lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; - lp.setTitle("StatusBarExpanded"); - mExpandedDialog.getWindow().setAttributes(lp); - mExpandedDialog.getWindow().setFormat(pixelFormat); - mExpandedParams = lp; - - mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - mExpandedDialog.setContentView(mExpandedView, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); - mExpandedDialog.getWindow().setBackgroundDrawable(null); - mExpandedDialog.show(); - FrameLayout hack = (FrameLayout)mExpandedView.getParent(); - } - - void setDateViewVisibility(boolean visible, int anim) { - mDateView.setUpdates(visible); - mDateView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - mDateView.startAnimation(loadAnim(anim, null)); - } - - void setNotificationIconVisibility(boolean visible, int anim) { - int old = mNotificationIcons.getVisibility(); - int v = visible ? View.VISIBLE : View.INVISIBLE; - if (old != v) { - mNotificationIcons.setVisibility(v); - mNotificationIcons.startAnimation(loadAnim(anim, null)); - } - } - - void updateExpandedViewPos(int expandedPosition) { - if (SPEW) { - Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition - + " mTrackingParams.y=" + mTrackingParams.y - + " mTrackingPosition=" + mTrackingPosition); - } - - int h = mStatusBarView.getHeight(); - int disph = mDisplay.getHeight(); - - // If the expanded view is not visible, make sure they're still off screen. - // Maybe the view was resized. - if (!mExpandedVisible) { - if (mTrackingView != null) { - mTrackingPosition = -disph; - if (mTrackingParams != null) { - mTrackingParams.y = mTrackingPosition; - WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); - } - } - if (mExpandedParams != null) { - mExpandedParams.y = -disph; - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - } - return; - } - - // tracking view... - int pos; - if (expandedPosition == EXPANDED_FULL_OPEN) { - pos = h; - } - else if (expandedPosition == EXPANDED_LEAVE_ALONE) { - pos = mTrackingPosition; - } - else { - if (expandedPosition <= disph) { - pos = expandedPosition; - } else { - pos = disph; - } - pos -= disph-h; - } - mTrackingPosition = mTrackingParams.y = pos; - mTrackingParams.height = disph-h; - WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); - - if (mExpandedParams != null) { - mCloseView.getLocationInWindow(mPositionTmp); - final int closePos = mPositionTmp[1]; - - mExpandedContents.getLocationInWindow(mPositionTmp); - final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight(); - - mExpandedParams.y = pos + mTrackingView.getHeight() - - (mTrackingParams.height-closePos) - contentsBottom; - int max = h; - if (mExpandedParams.y > max) { - mExpandedParams.y = max; - } - int min = mTrackingPosition; - if (mExpandedParams.y < min) { - mExpandedParams.y = min; - } - - boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h; - if (!visible) { - // if the contents aren't visible, move the expanded view way off screen - // because the window itself extends below the content view. - mExpandedParams.y = -disph; - } - panelSlightlyVisible(visible); - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - } - - if (SPEW) { - Slog.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition - + " mTrackingParams.y=" + mTrackingParams.y - + " mTrackingPosition=" + mTrackingPosition - + " mExpandedParams.y=" + mExpandedParams.y - + " mExpandedParams.height=" + mExpandedParams.height); - } - } - - int getExpandedHeight() { - return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight(); - } - - void updateExpandedHeight() { - if (mExpandedView != null) { - mExpandedParams.height = getExpandedHeight(); - mExpandedDialog.getWindow().setAttributes(mExpandedParams); - } - } - - /** - * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. - * This was added last-minute and is inconsistent with the way the rest of the notifications - * are handled, because the notification isn't really cancelled. The lights are just - * turned off. If any other notifications happen, the lights will turn back on. Steve says - * this is what he wants. (see bug 1131461) - */ - private boolean mPanelSlightlyVisible; - void panelSlightlyVisible(boolean visible) { - if (mPanelSlightlyVisible != visible) { - mPanelSlightlyVisible = visible; - if (visible) { - // tell the notification manager to turn off the lights. - mNotificationCallbacks.onPanelRevealed(); - } - } - } - - void performDisableActions(int net) { - int old = mDisabled; - int diff = net ^ old; - mDisabled = net; - - // act accordingly - if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { - if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { - Slog.d(TAG, "DISABLE_EXPAND: yes"); - animateCollapse(); - } - } - if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { - Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); - if (mTicking) { - mNotificationIcons.setVisibility(View.INVISIBLE); - mTicker.halt(); - } else { - setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); - } - } else { - Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); - if (!mExpandedVisible) { - setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); - } - } - } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { - mTicker.halt(); - } - } - } - - private View.OnClickListener mClearButtonListener = new View.OnClickListener() { - public void onClick(View v) { - mNotificationCallbacks.onClearAll(); - addPendingOp(OP_EXPAND, null, false); - } - }; - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - || Intent.ACTION_SCREEN_OFF.equals(action)) { - deactivate(); - } - else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { - updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), - intent.getStringExtra(Telephony.Intents.EXTRA_SPN), - intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), - intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); - } - else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - updateResources(); - } - } - }; - - void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { - if (false) { - Slog.d(TAG, "updateNetworkName showSpn=" + showSpn + " spn=" + spn - + " showPlmn=" + showPlmn + " plmn=" + plmn); - } - boolean something = false; - if (showPlmn) { - mPlmnLabel.setVisibility(View.VISIBLE); - if (plmn != null) { - mPlmnLabel.setText(plmn); - } else { - mPlmnLabel.setText(R.string.lockscreen_carrier_default); - } - } else { - mPlmnLabel.setText(""); - mPlmnLabel.setVisibility(View.GONE); - } - if (showSpn && spn != null) { - mSpnLabel.setText(spn); - mSpnLabel.setVisibility(View.VISIBLE); - something = true; - } else { - mSpnLabel.setText(""); - mSpnLabel.setVisibility(View.GONE); - } - } - - /** - * Reload some of our resources when the configuration changes. - * - * We don't reload everything when the configuration changes -- we probably - * should, but getting that smooth is tough. Someday we'll fix that. In the - * meantime, just update the things that we know change. - */ - void updateResources() { - Resources res = mContext.getResources(); - - mClearButton.setText(mContext.getText(R.string.status_bar_clear_all_button)); - mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title)); - mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title)); - mNoNotificationsTitle.setText(mContext.getText(R.string.status_bar_no_notifications_title)); - - mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); - - if (false) Slog.v(TAG, "updateResources"); - } - - // - // tracing - // - - void postStartTracing() { - mHandler.postDelayed(mStartTracing, 3000); - } - - void vibrate() { - android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( - Context.VIBRATOR_SERVICE); - vib.vibrate(250); - } - - Runnable mStartTracing = new Runnable() { - public void run() { - vibrate(); - SystemClock.sleep(250); - Slog.d(TAG, "startTracing"); - android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); - mHandler.postDelayed(mStopTracing, 10000); - } - }; - - Runnable mStopTracing = new Runnable() { - public void run() { - android.os.Debug.stopMethodTracing(); - Slog.d(TAG, "stopTracing"); - vibrate(); - } - }; - - class UninstallReceiver extends BroadcastReceiver { - public UninstallReceiver() { - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - filter.addDataScheme("package"); - mContext.registerReceiver(this, filter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(this, sdFilter); - } - - @Override - public void onReceive(Context context, Intent intent) { - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - } else { - Uri data = intent.getData(); - if (data != null) { - String pkg = data.getSchemeSpecificPart(); - if (pkg != null) { - pkgList = new String[]{pkg}; - } - } - } - ArrayList<StatusBarNotification> list = null; - if (pkgList != null) { - synchronized (StatusBarService.this) { - for (String pkg : pkgList) { - list = mNotificationData.notificationsForPackage(pkg); - } - } - } - - if (list != null) { - final int N = list.size(); - for (int i=0; i<N; i++) { - removeIcon(list.get(i).key); - } - } - } - } -} diff --git a/services/java/com/android/server/status/StatusBarView.java b/services/java/com/android/server/status/StatusBarView.java deleted file mode 100644 index 5e1f572..0000000 --- a/services/java/com/android/server/status/StatusBarView.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.FrameLayout; - -import com.android.internal.R; - -public class StatusBarView extends FrameLayout { - private static final String TAG = "StatusBarView"; - - static final int DIM_ANIM_TIME = 400; - - StatusBarService mService; - boolean mTracking; - int mStartX, mStartY; - ViewGroup mNotificationIcons; - ViewGroup mStatusIcons; - View mDate; - FixedSizeDrawable mBackground; - - boolean mNightMode = false; - int mStartAlpha = 0, mEndAlpha = 0; - long mEndTime = 0; - - public StatusBarView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mNotificationIcons = (ViewGroup)findViewById(R.id.notificationIcons); - mStatusIcons = (ViewGroup)findViewById(R.id.statusIcons); - mDate = findViewById(R.id.date); - - mBackground = new FixedSizeDrawable(mDate.getBackground()); - mBackground.setFixedBounds(0, 0, 0, 0); - mDate.setBackgroundDrawable(mBackground); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mService.onBarViewAttached(); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) - == Configuration.UI_MODE_NIGHT_YES; - if (mNightMode != nightMode) { - mNightMode = nightMode; - mStartAlpha = getCurAlpha(); - mEndAlpha = mNightMode ? 0x80 : 0x00; - mEndTime = SystemClock.uptimeMillis() + DIM_ANIM_TIME; - invalidate(); - } - } - - int getCurAlpha() { - long time = SystemClock.uptimeMillis(); - if (time > mEndTime) { - return mEndAlpha; - } - return mEndAlpha - - (int)(((mEndAlpha-mStartAlpha) * (mEndTime-time) / DIM_ANIM_TIME)); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - // put the date date view quantized to the icons - int oldDateRight = mDate.getRight(); - int newDateRight; - - newDateRight = getDateSize(mNotificationIcons, oldDateRight, - getViewOffset(mNotificationIcons)); - if (newDateRight < 0) { - int offset = getViewOffset(mStatusIcons); - if (oldDateRight < offset) { - newDateRight = oldDateRight; - } else { - newDateRight = getDateSize(mStatusIcons, oldDateRight, offset); - if (newDateRight < 0) { - newDateRight = r; - } - } - } - int max = r - getPaddingRight(); - if (newDateRight > max) { - newDateRight = max; - } - - mDate.layout(mDate.getLeft(), mDate.getTop(), newDateRight, mDate.getBottom()); - mBackground.setFixedBounds(-mDate.getLeft(), -mDate.getTop(), (r-l), (b-t)); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - int alpha = getCurAlpha(); - if (alpha != 0) { - canvas.drawARGB(alpha, 0, 0, 0); - } - if (alpha != mEndAlpha) { - invalidate(); - } - } - - /** - * Gets the left position of v in this view. Throws if v is not - * a child of this. - */ - private int getViewOffset(View v) { - int offset = 0; - while (v != this) { - offset += v.getLeft(); - ViewParent p = v.getParent(); - if (v instanceof View) { - v = (View)p; - } else { - throw new RuntimeException(v + " is not a child of " + this); - } - } - return offset; - } - - private int getDateSize(ViewGroup g, int w, int offset) { - final int N = g.getChildCount(); - for (int i=0; i<N; i++) { - View v = g.getChildAt(i); - int l = v.getLeft() + offset; - int r = v.getRight() + offset; - if (w >= l && w <= r) { - return r; - } - } - return -1; - } - - /** - * Ensure that, if there is no target under us to receive the touch, - * that we process it ourself. This makes sure that onInterceptTouchEvent() - * is always called for the entire gesture. - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - if (event.getAction() != MotionEvent.ACTION_DOWN) { - mService.interceptTouchEvent(event); - } - return true; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent event) { - return mService.interceptTouchEvent(event) - ? true : super.onInterceptTouchEvent(event); - } -} - diff --git a/services/java/com/android/server/status/StorageNotification.java b/services/java/com/android/server/status/StorageNotification.java deleted file mode 100644 index 8da8cd3..0000000 --- a/services/java/com/android/server/status/StorageNotification.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2010 Google Inc. - * - * 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.status; - -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.storage.IMountService; -import android.os.Message; -import android.os.ServiceManager; -import android.os.storage.StorageEventListener; -import android.os.storage.StorageManager; -import android.os.storage.StorageResultCode; -import android.provider.Settings; -import android.util.Slog; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -public class StorageNotification extends StorageEventListener { - private static final String TAG = "StorageNotification"; - - private static final boolean POP_UMS_ACTIVITY_ON_CONNECT = true; - - /** - * Binder context for this service - */ - private Context mContext; - - /** - * The notification that is shown when a USB mass storage host - * is connected. - * <p> - * This is lazily created, so use {@link #setUsbStorageNotification()}. - */ - private Notification mUsbStorageNotification; - - /** - * The notification that is shown when the following media events occur: - * - Media is being checked - * - Media is blank (or unknown filesystem) - * - Media is corrupt - * - Media is safe to unmount - * - Media is missing - * <p> - * This is lazily created, so use {@link #setMediaStorageNotification()}. - */ - private Notification mMediaStorageNotification; - private boolean mUmsAvailable; - private StorageManager mStorageManager; - - public StorageNotification(Context context) { - mContext = context; - - mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - mUmsAvailable = mStorageManager.isUsbMassStorageConnected(); - Slog.d(TAG, String.format( "Startup with UMS connection %s (media state %s)", mUmsAvailable, - Environment.getExternalStorageState())); - } - - /* - * @override com.android.os.storage.StorageEventListener - */ - @Override - public void onUsbMassStorageConnectionChanged(boolean connected) { - mUmsAvailable = connected; - /* - * Even though we may have a UMS host connected, we the SD card - * may not be in a state for export. - */ - String st = Environment.getExternalStorageState(); - - Slog.i(TAG, String.format("UMS connection changed to %s (media state %s)", connected, st)); - - if (connected && (st.equals( - Environment.MEDIA_REMOVED) || st.equals(Environment.MEDIA_CHECKING))) { - /* - * No card or card being checked = don't display - */ - connected = false; - } - updateUsbMassStorageNotification(connected); - } - - /* - * @override com.android.os.storage.StorageEventListener - */ - @Override - public void onStorageStateChanged(String path, String oldState, String newState) { - Slog.i(TAG, String.format( - "Media {%s} state changed from {%s} -> {%s}", path, oldState, newState)); - if (newState.equals(Environment.MEDIA_SHARED)) { - /* - * Storage is now shared. Modify the UMS notification - * for stopping UMS. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - setUsbStorageNotification( - com.android.internal.R.string.usb_storage_stop_notification_title, - com.android.internal.R.string.usb_storage_stop_notification_message, - com.android.internal.R.drawable.stat_sys_warning, false, true, pi); - } else if (newState.equals(Environment.MEDIA_CHECKING)) { - /* - * Storage is now checking. Update media notification and disable - * UMS notification. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_checking_notification_title, - com.android.internal.R.string.ext_media_checking_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_prepare, true, false, null); - updateUsbMassStorageNotification(false); - } else if (newState.equals(Environment.MEDIA_MOUNTED)) { - /* - * Storage is now mounted. Dismiss any media notifications, - * and enable UMS notification if connected. - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_UNMOUNTED)) { - /* - * Storage is now unmounted. We may have been unmounted - * because the user is enabling/disabling UMS, in which case we don't - * want to display the 'safe to unmount' notification. - */ - if (!mStorageManager.isUsbMassStorageEnabled()) { - if (oldState.equals(Environment.MEDIA_SHARED)) { - /* - * The unmount was due to UMS being enabled. Dismiss any - * media notifications, and enable UMS notification if connected - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(mUmsAvailable); - } else { - /* - * Show safe to unmount media notification, and enable UMS - * notification if connected. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_safe_unmount_notification_title, - com.android.internal.R.string.ext_media_safe_unmount_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard, true, true, null); - updateUsbMassStorageNotification(mUmsAvailable); - } - } else { - /* - * The unmount was due to UMS being enabled. Dismiss any - * media notifications, and disable the UMS notification - */ - setMediaStorageNotification(0, 0, 0, false, false, null); - updateUsbMassStorageNotification(false); - } - } else if (newState.equals(Environment.MEDIA_NOFS)) { - /* - * Storage has no filesystem. Show blank media notification, - * and enable UMS notification if connected. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - - setMediaStorageNotification( - com.android.internal.R.string.ext_media_nofs_notification_title, - com.android.internal.R.string.ext_media_nofs_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_UNMOUNTABLE)) { - /* - * Storage is corrupt. Show corrupt media notification, - * and enable UMS notification if connected. - */ - Intent intent = new Intent(); - intent.setClass(mContext, com.android.internal.app.ExternalMediaFormatActivity.class); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - - setMediaStorageNotification( - com.android.internal.R.string.ext_media_unmountable_notification_title, - com.android.internal.R.string.ext_media_unmountable_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, true, false, pi); - updateUsbMassStorageNotification(mUmsAvailable); - } else if (newState.equals(Environment.MEDIA_REMOVED)) { - /* - * Storage has been removed. Show nomedia media notification, - * and disable UMS notification regardless of connection state. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_nomedia_notification_title, - com.android.internal.R.string.ext_media_nomedia_notification_message, - com.android.internal.R.drawable.stat_notify_sdcard_usb, - true, false, null); - updateUsbMassStorageNotification(false); - } else if (newState.equals(Environment.MEDIA_BAD_REMOVAL)) { - /* - * Storage has been removed unsafely. Show bad removal media notification, - * and disable UMS notification regardless of connection state. - */ - setMediaStorageNotification( - com.android.internal.R.string.ext_media_badremoval_notification_title, - com.android.internal.R.string.ext_media_badremoval_notification_message, - com.android.internal.R.drawable.stat_sys_warning, - true, true, null); - updateUsbMassStorageNotification(false); - } else { - Slog.w(TAG, String.format("Ignoring unknown state {%s}", newState)); - } - } - - /** - * Update the state of the USB mass storage notification - */ - void updateUsbMassStorageNotification(boolean available) { - - if (available) { - Intent intent = new Intent(); - intent.setClass(mContext, com.android.server.status.UsbStorageActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - final boolean adbOn = 1 == Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.ADB_ENABLED, - 0); - - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); - setUsbStorageNotification( - com.android.internal.R.string.usb_storage_notification_title, - com.android.internal.R.string.usb_storage_notification_message, - com.android.internal.R.drawable.stat_sys_data_usb, - false, true, pi); - - if (POP_UMS_ACTIVITY_ON_CONNECT && !adbOn) { - // We assume that developers don't want to enable UMS every - // time they attach a device to a USB host. The average user, - // however, is looking to charge the phone (in which case this - // is harmless) or transfer files (in which case this coaches - // the user about how to complete that task and saves several - // steps). - mContext.startActivity(intent); - } - } else { - setUsbStorageNotification(0, 0, 0, false, false, null); - } - } - - /** - * Sets the USB storage notification. - */ - private synchronized void setUsbStorageNotification(int titleId, int messageId, int icon, - boolean sound, boolean visible, PendingIntent pi) { - - if (!visible && mUsbStorageNotification == null) { - return; - } - - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (notificationManager == null) { - return; - } - - if (visible) { - Resources r = Resources.getSystem(); - CharSequence title = r.getText(titleId); - CharSequence message = r.getText(messageId); - - if (mUsbStorageNotification == null) { - mUsbStorageNotification = new Notification(); - mUsbStorageNotification.icon = icon; - mUsbStorageNotification.when = 0; - } - - if (sound) { - mUsbStorageNotification.defaults |= Notification.DEFAULT_SOUND; - } else { - mUsbStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; - } - - mUsbStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; - - mUsbStorageNotification.tickerText = title; - if (pi == null) { - Intent intent = new Intent(); - pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); - } - - mUsbStorageNotification.setLatestEventInfo(mContext, title, message, pi); - } - - final int notificationId = mUsbStorageNotification.icon; - if (visible) { - notificationManager.notify(notificationId, mUsbStorageNotification); - } else { - notificationManager.cancel(notificationId); - } - } - - private synchronized boolean getMediaStorageNotificationDismissable() { - if ((mMediaStorageNotification != null) && - ((mMediaStorageNotification.flags & Notification.FLAG_AUTO_CANCEL) == - Notification.FLAG_AUTO_CANCEL)) - return true; - - return false; - } - - /** - * Sets the media storage notification. - */ - private synchronized void setMediaStorageNotification(int titleId, int messageId, int icon, boolean visible, - boolean dismissable, PendingIntent pi) { - - if (!visible && mMediaStorageNotification == null) { - return; - } - - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (notificationManager == null) { - return; - } - - if (mMediaStorageNotification != null && visible) { - /* - * Dismiss the previous notification - we're about to - * re-use it. - */ - final int notificationId = mMediaStorageNotification.icon; - notificationManager.cancel(notificationId); - } - - if (visible) { - Resources r = Resources.getSystem(); - CharSequence title = r.getText(titleId); - CharSequence message = r.getText(messageId); - - if (mMediaStorageNotification == null) { - mMediaStorageNotification = new Notification(); - mMediaStorageNotification.when = 0; - } - - mMediaStorageNotification.defaults &= ~Notification.DEFAULT_SOUND; - - if (dismissable) { - mMediaStorageNotification.flags = Notification.FLAG_AUTO_CANCEL; - } else { - mMediaStorageNotification.flags = Notification.FLAG_ONGOING_EVENT; - } - - mMediaStorageNotification.tickerText = title; - if (pi == null) { - Intent intent = new Intent(); - pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); - } - - mMediaStorageNotification.icon = icon; - mMediaStorageNotification.setLatestEventInfo(mContext, title, message, pi); - } - - final int notificationId = mMediaStorageNotification.icon; - if (visible) { - notificationManager.notify(notificationId, mMediaStorageNotification); - } else { - notificationManager.cancel(notificationId); - } - } -} diff --git a/services/java/com/android/server/status/Ticker.java b/services/java/com/android/server/status/Ticker.java deleted file mode 100644 index e47185b..0000000 --- a/services/java/com/android/server/status/Ticker.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.status; - -import com.android.internal.R; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.text.StaticLayout; -import android.text.Layout.Alignment; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.Slog; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.TextSwitcher; -import android.widget.TextView; -import android.widget.ImageSwitcher; - -import java.util.ArrayList; - - -abstract class Ticker { - private static final int TICKER_SEGMENT_DELAY = 3000; - - private final class Segment { - NotificationData notificationData; - Drawable icon; - CharSequence text; - int current; - int next; - boolean first; - - StaticLayout getLayout(CharSequence substr) { - int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() - - mTextSwitcher.getPaddingRight(); - return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); - } - - CharSequence rtrim(CharSequence substr, int start, int end) { - while (end > start && !TextUtils.isGraphic(substr.charAt(end-1))) { - end--; - } - if (end > start) { - return substr.subSequence(start, end); - } - return null; - } - - /** returns null if there is no more text */ - CharSequence getText() { - if (this.current > this.text.length()) { - return null; - } - CharSequence substr = this.text.subSequence(this.current, this.text.length()); - StaticLayout l = getLayout(substr); - int lineCount = l.getLineCount(); - if (lineCount > 0) { - int start = l.getLineStart(0); - int end = l.getLineEnd(0); - this.next = this.current + end; - return rtrim(substr, start, end); - } else { - throw new RuntimeException("lineCount=" + lineCount + " current=" + current + - " text=" + text); - } - } - - /** returns null if there is no more text */ - CharSequence advance() { - this.first = false; - int index = this.next; - final int len = this.text.length(); - while (index < len && !TextUtils.isGraphic(this.text.charAt(index))) { - index++; - } - if (index >= len) { - return null; - } - - CharSequence substr = this.text.subSequence(index, this.text.length()); - StaticLayout l = getLayout(substr); - final int lineCount = l.getLineCount(); - int i; - for (i=0; i<lineCount; i++) { - int start = l.getLineStart(i); - int end = l.getLineEnd(i); - if (i == lineCount-1) { - this.next = len; - } else { - this.next = index + l.getLineStart(i+1); - } - CharSequence result = rtrim(substr, start, end); - if (result != null) { - this.current = index + start; - return result; - } - } - this.current = len; - return null; - } - - Segment(NotificationData n, Drawable icon, CharSequence text) { - this.notificationData = n; - this.icon = icon; - this.text = text; - int index = 0; - final int len = text.length(); - while (index < len && !TextUtils.isGraphic(text.charAt(index))) { - index++; - } - this.current = index; - this.next = index; - this.first = true; - } - }; - - private Handler mHandler = new Handler(); - private ArrayList<Segment> mSegments = new ArrayList(); - private TextPaint mPaint; - private View mTickerView; - private ImageSwitcher mIconSwitcher; - private TextSwitcher mTextSwitcher; - - Ticker(Context context, StatusBarView sb) { - mTickerView = sb.findViewById(R.id.ticker); - - mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); - mIconSwitcher.setInAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); - mIconSwitcher.setOutAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); - - mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); - mTextSwitcher.setInAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); - mTextSwitcher.setOutAnimation( - AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); - - // Copy the paint style of one of the TextSwitchers children to use later for measuring - TextView text = (TextView)mTextSwitcher.getChildAt(0); - mPaint = text.getPaint(); - } - - void addEntry(NotificationData n, Drawable icon, CharSequence text) { - int initialCount = mSegments.size(); - - Segment newSegment = new Segment(n, icon, text); - - // prune out any preexisting ones for this notification, but not the current one. - // let that finish, even if it's the same id - for (int i=1; i<initialCount; i++) { - Segment seg = mSegments.get(i); - if (n.id == seg.notificationData.id && n.pkg.equals(seg.notificationData.pkg)) { - // just update that one to use this new data instead - mSegments.set(i, newSegment); - // and since we know initialCount != 0, just return - return ; - } - } - - mSegments.add(newSegment); - - if (initialCount == 0 && mSegments.size() > 0) { - Segment seg = mSegments.get(0); - seg.first = false; - - mIconSwitcher.setAnimateFirstView(false); - mIconSwitcher.reset(); - mIconSwitcher.setImageDrawable(seg.icon); - - mTextSwitcher.setAnimateFirstView(false); - mTextSwitcher.reset(); - mTextSwitcher.setText(seg.getText()); - - tickerStarting(); - scheduleAdvance(); - } - } - - void halt() { - mHandler.removeCallbacks(mAdvanceTicker); - mSegments.clear(); - tickerHalting(); - } - - void reflowText() { - if (mSegments.size() > 0) { - Segment seg = mSegments.get(0); - CharSequence text = seg.getText(); - mTextSwitcher.setCurrentText(text); - } - } - - private Runnable mAdvanceTicker = new Runnable() { - public void run() { - while (mSegments.size() > 0) { - Segment seg = mSegments.get(0); - - if (seg.first) { - // this makes the icon slide in for the first one for a given - // notification even if there are two notifications with the - // same icon in a row - mIconSwitcher.setImageDrawable(seg.icon); - } - CharSequence text = seg.advance(); - if (text == null) { - mSegments.remove(0); - continue; - } - mTextSwitcher.setText(text); - - scheduleAdvance(); - break; - } - if (mSegments.size() == 0) { - tickerDone(); - } - } - }; - - private void scheduleAdvance() { - mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); - } - - abstract void tickerStarting(); - abstract void tickerDone(); - abstract void tickerHalting(); -} - diff --git a/services/java/com/android/server/status/TickerView.java b/services/java/com/android/server/status/TickerView.java deleted file mode 100644 index 099dffb..0000000 --- a/services/java/com/android/server/status/TickerView.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.TextSwitcher; - - -public class TickerView extends TextSwitcher -{ - Ticker mTicker; - - public TickerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mTicker.reflowText(); - } -} - diff --git a/services/java/com/android/server/status/TrackingPatternView.java b/services/java/com/android/server/status/TrackingPatternView.java deleted file mode 100644 index 2c91aa4..0000000 --- a/services/java/com/android/server/status/TrackingPatternView.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.util.Slog; -import android.view.View; -import android.graphics.BitmapFactory; -import android.graphics.Bitmap; -import android.graphics.Paint; -import android.graphics.Canvas; - -public class TrackingPatternView extends View { - private Bitmap mTexture; - private Paint mPaint; - private int mTextureWidth; - private int mTextureHeight; - - public TrackingPatternView(Context context, AttributeSet attrs) { - super(context, attrs); - - mTexture = BitmapFactory.decodeResource(getResources(), - com.android.internal.R.drawable.status_bar_background); - mTextureWidth = mTexture.getWidth(); - mTextureHeight = mTexture.getHeight(); - - mPaint = new Paint(); - mPaint.setDither(false); - } - - @Override - public void onDraw(Canvas canvas) { - final Bitmap texture = mTexture; - final Paint paint = mPaint; - - final int width = getWidth(); - final int height = getHeight(); - - final int textureWidth = mTextureWidth; - final int textureHeight = mTextureHeight; - - int x = 0; - int y; - - while (x < width) { - y = 0; - while (y < height) { - canvas.drawBitmap(texture, x, y, paint); - y += textureHeight; - } - x += textureWidth; - } - } -} diff --git a/services/java/com/android/server/status/TrackingView.java b/services/java/com/android/server/status/TrackingView.java deleted file mode 100644 index 8ec39c0..0000000 --- a/services/java/com/android/server/status/TrackingView.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.status; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.Display; -import android.view.KeyEvent; -import android.view.WindowManager; -import android.widget.LinearLayout; - - -public class TrackingView extends LinearLayout { - final Display mDisplay; - StatusBarService mService; - boolean mTracking; - int mStartX, mStartY; - - public TrackingView(Context context, AttributeSet attrs) { - super(context, attrs); - mDisplay = ((WindowManager)context.getSystemService( - Context.WINDOW_SERVICE)).getDefaultDisplay(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mService.updateExpandedHeight(); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - boolean down = event.getAction() == KeyEvent.ACTION_DOWN; - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_BACK: - if (down) { - mService.deactivate(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mService.onTrackingViewAttached(); - } -} diff --git a/services/java/com/android/server/status/UsbStorageActivity.java b/services/java/com/android/server/status/UsbStorageActivity.java deleted file mode 100644 index e8631c5..0000000 --- a/services/java/com/android/server/status/UsbStorageActivity.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2007 Google Inc. - * - * 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.status; - -import com.android.internal.R; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.DialogInterface.OnCancelListener; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.IBinder; -import android.os.storage.IMountService; -import android.os.storage.StorageManager; -import android.os.storage.StorageEventListener; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.widget.ImageView; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.view.View; -import android.view.Window; -import android.util.Log; - -import java.util.List; - -/** - * This activity is shown to the user for him/her to enable USB mass storage - * on-demand (that is, when the USB cable is connected). It uses the alert - * dialog style. It will be launched from a notification. - */ -public class UsbStorageActivity extends Activity - implements View.OnClickListener, OnCancelListener { - private static final String TAG = "UsbStorageActivity"; - - private Button mMountButton; - private Button mUnmountButton; - private ProgressBar mProgressBar; - private TextView mBanner; - private TextView mMessage; - private ImageView mIcon; - private StorageManager mStorageManager = null; - private static final int DLG_CONFIRM_KILL_STORAGE_USERS = 1; - private static final int DLG_ERROR_SHARING = 2; - static final boolean localLOGV = false; - - /** Used to detect when the USB cable is unplugged, so we can call finish() */ - private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) { - handleBatteryChanged(intent); - } - } - }; - - private StorageEventListener mStorageListener = new StorageEventListener() { - @Override - public void onStorageStateChanged(String path, String oldState, String newState) { - final boolean on = newState.equals(Environment.MEDIA_SHARED); - switchDisplay(on); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (mStorageManager == null) { - mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE); - if (mStorageManager == null) { - Log.w(TAG, "Failed to get StorageManager"); - } - } - - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setProgressBarIndeterminateVisibility(true); - - setTitle(getString(com.android.internal.R.string.usb_storage_activity_title)); - - setContentView(com.android.internal.R.layout.usb_storage_activity); - - mIcon = (ImageView) findViewById(com.android.internal.R.id.icon); - mBanner = (TextView) findViewById(com.android.internal.R.id.banner); - mMessage = (TextView) findViewById(com.android.internal.R.id.message); - - mMountButton = (Button) findViewById(com.android.internal.R.id.mount_button); - mMountButton.setOnClickListener(this); - mUnmountButton = (Button) findViewById(com.android.internal.R.id.unmount_button); - mUnmountButton.setOnClickListener(this); - mProgressBar = (ProgressBar) findViewById(com.android.internal.R.id.progress); - } - - private void switchDisplay(boolean usbStorageInUse) { - if (usbStorageInUse) { - mProgressBar.setVisibility(View.GONE); - mUnmountButton.setVisibility(View.VISIBLE); - mMountButton.setVisibility(View.GONE); - mIcon.setImageResource(com.android.internal.R.drawable.usb_android_connected); - mBanner.setText(com.android.internal.R.string.usb_storage_stop_title); - mMessage.setText(com.android.internal.R.string.usb_storage_stop_message); - } else { - mProgressBar.setVisibility(View.GONE); - mUnmountButton.setVisibility(View.GONE); - mMountButton.setVisibility(View.VISIBLE); - mIcon.setImageResource(com.android.internal.R.drawable.usb_android); - mBanner.setText(com.android.internal.R.string.usb_storage_title); - mMessage.setText(com.android.internal.R.string.usb_storage_message); - } - } - - @Override - protected void onResume() { - super.onResume(); - - mStorageManager.registerListener(mStorageListener); - registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - try { - switchDisplay(mStorageManager.isUsbMassStorageEnabled()); - } catch (Exception ex) { - Log.e(TAG, "Failed to read UMS enable state", ex); - } - } - - @Override - protected void onPause() { - super.onPause(); - - unregisterReceiver(mBatteryReceiver); - if (mStorageManager == null && mStorageListener != null) { - mStorageManager.unregisterListener(mStorageListener); - } - } - - private void handleBatteryChanged(Intent intent) { - int pluggedType = intent.getIntExtra("plugged", 0); - if (pluggedType == 0) { - // It was disconnected from the plug, so finish - finish(); - } - } - - private IMountService getMountService() { - IBinder service = ServiceManager.getService("mount"); - if (service != null) { - return IMountService.Stub.asInterface(service); - } - return null; - } - - @Override - public Dialog onCreateDialog(int id, Bundle args) { - switch (id) { - case DLG_CONFIRM_KILL_STORAGE_USERS: - return new AlertDialog.Builder(this) - .setTitle(R.string.dlg_confirm_kill_storage_users_title) - .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - switchUsbMassStorageAsync(true); - }}) - .setNegativeButton(R.string.cancel, null) - .setMessage(R.string.dlg_confirm_kill_storage_users_text) - .setOnCancelListener(this) - .create(); - case DLG_ERROR_SHARING: - return new AlertDialog.Builder(this) - .setTitle(R.string.dlg_error_title) - .setNeutralButton(R.string.dlg_ok, null) - .setMessage(R.string.usb_storage_error_message) - .setOnCancelListener(this) - .create(); - } - return null; - } - - private void showDialogInner(int id) { - removeDialog(id); - showDialog(id); - } - - private void switchUsbMassStorageAsync(boolean on) { - mUnmountButton.setVisibility(View.GONE); - mMountButton.setVisibility(View.GONE); - - mProgressBar.setVisibility(View.VISIBLE); - // will be hidden once USB mass storage kicks in (or fails) - - final boolean _on = on; - new Thread() { - public void run() { - if (_on) { - mStorageManager.enableUsbMassStorage(); - } else { - mStorageManager.disableUsbMassStorage(); - } - } - }.start(); - } - - private void checkStorageUsers() { - IMountService ims = getMountService(); - if (ims == null) { - // Display error dialog - showDialogInner(DLG_ERROR_SHARING); - } - String extStoragePath = Environment.getExternalStorageDirectory().toString(); - boolean showDialog = false; - try { - int[] stUsers = ims.getStorageUsers(extStoragePath); - if (stUsers != null && stUsers.length > 0) { - showDialog = true; - } else { - // List of applications on sdcard. - ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); - List<ApplicationInfo> infoList = am.getRunningExternalApplications(); - if (infoList != null && infoList.size() > 0) { - showDialog = true; - } - } - } catch (RemoteException e) { - // Display error dialog - showDialogInner(DLG_ERROR_SHARING); - } - if (showDialog) { - // Display dialog to user - showDialogInner(DLG_CONFIRM_KILL_STORAGE_USERS); - } else { - if (localLOGV) Log.i(TAG, "Enabling UMS"); - switchUsbMassStorageAsync(true); - } - } - - public void onClick(View v) { - if (v == mMountButton) { - // Check for list of storage users and display dialog if needed. - checkStorageUsers(); - } else if (v == mUnmountButton) { - if (localLOGV) Log.i(TAG, "Disabling UMS"); - switchUsbMassStorageAsync(false); - } - } - - public void onCancel(DialogInterface dialog) { - finish(); - } - -} diff --git a/services/java/com/android/server/status/package.html b/services/java/com/android/server/status/package.html deleted file mode 100755 index c9f96a6..0000000 --- a/services/java/com/android/server/status/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<body> - -{@hide} - -</body> |