From 778cadc4d6aa53742c7bdf41c15dabbb6c4fc856 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 20 Feb 2013 15:23:52 -0500 Subject: [PATCH 01/76] Switch to system process before modifying rotation. Bug: 8218133 Change-Id: Ib44d64d48dbdf3095182c409dd2211f6a3b726ad --- .../server/wm/WindowManagerService.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index b7637b9b99fb6..e6d6436bfe2d9 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -5317,8 +5317,14 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.v(TAG, "freezeRotation: mRotation=" + mRotation); - mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, - rotation == -1 ? mRotation : rotation); + long origId = Binder.clearCallingIdentity(); + try { + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, + rotation == -1 ? mRotation : rotation); + } finally { + Binder.restoreCallingIdentity(origId); + } + updateRotationUnchecked(false, false); } @@ -5335,7 +5341,14 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.v(TAG, "thawRotation: mRotation=" + mRotation); - mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 777); // rot not used + long origId = Binder.clearCallingIdentity(); + try { + mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, + 777); // rot not used + } finally { + Binder.restoreCallingIdentity(origId); + } + updateRotationUnchecked(false, false); } From 170a61375e80b83b515e46add0b8e66e745c5fb4 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Fri, 15 Feb 2013 14:02:56 -0800 Subject: [PATCH 02/76] Begin switch over to task based history. - Introduce the task history and add to and remove from it with verification. Change-Id: If97e74f5a13f85acdb1521fc6d0b066a7e8584ae --- .../com/android/server/am/ActivityRecord.java | 6 +- .../com/android/server/am/ActivityStack.java | 340 +++++++++++++++++- .../com/android/server/am/TaskRecord.java | 60 +++- .../com/android/server/wm/DisplayContent.java | 8 + .../java/com/android/server/wm/TaskGroup.java | 5 + .../java/com/android/server/wm/TaskList.java | 5 + .../server/wm/WindowManagerService.java | 21 +- 7 files changed, 413 insertions(+), 32 deletions(-) diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index c1b406ef473c6..f2d401fdacc99 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -471,6 +471,7 @@ final class ActivityRecord { void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { if (inHistory && !finishing) { if (task != null) { + task.removeActivity(this); task.numActivities--; } if (newTask != null) { @@ -505,6 +506,7 @@ final class ActivityRecord { inHistory = false; if (task != null && !finishing) { task.numActivities--; + task = null; } clearOptionsLocked(); } @@ -539,7 +541,7 @@ final class ActivityRecord { ActivityResult r = new ActivityResult(from, resultWho, requestCode, resultCode, resultData); if (results == null) { - results = new ArrayList(); + results = new ArrayList(); } results.add(r); } @@ -950,6 +952,8 @@ final class ActivityRecord { StringBuilder sb = new StringBuilder(128); sb.append("ActivityRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" t"); + sb.append(task.taskId); sb.append(" u"); sb.append(userId); sb.append(' '); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index c54cdaa36b9e0..a18a0d1c35f89 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -54,6 +54,7 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -66,6 +67,7 @@ import android.os.UserHandle; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.view.Display; import java.io.FileDescriptor; @@ -75,6 +77,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; /** * State and management of a single stack of activities. @@ -98,7 +101,8 @@ final class ActivityStack { static final boolean DEBUG_APP = false; static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS; - + static final boolean VALIDATE_TASK_REPLACE = true; + // How long we wait until giving up on the last activity telling us it // is idle. static final int IDLE_TIMEOUT = 10*1000; @@ -137,7 +141,10 @@ final class ActivityStack { // 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; - + + static final boolean FORWARD_ITERATOR = false; + static final boolean REVERSE_ITERATOR = true; + enum ActivityState { INITIALIZING, RESUMED, @@ -154,13 +161,24 @@ final class ActivityStack { final boolean mMainStack; final Context mContext; - + /** * The back history of all previous (and possibly still * running) activities. It contains #ActivityRecord objects. */ private final ArrayList mHistory = new ArrayList(); + /** + * The back history of all previous (and possibly still + * running) activities. It contains #TaskRecord objects. + */ + private ArrayList mTaskHistory = new ArrayList(); + + /** + * Mapping from taskId to TaskRecord + */ + private SparseArray mTaskIdToTaskRecord = new SparseArray(); + /** * Used for validating app tokens with window manager. */ @@ -289,6 +307,12 @@ final class ActivityStack { */ boolean mDismissKeyguardOnNextActivity = false; + /** So we don't have to keep constructing a new object for utility non-nested use. */ + final ActivityIterator mTmpActivityIterator = new ActivityIterator(FORWARD_ITERATOR, true); + + /** So we don't have to keep constructing a new object for utility non-nested use. */ + final TaskIterator mTmpTaskIterator = new TaskIterator(); + /** * Save the most recent screenshot for reuse. This keeps Recents from taking two identical * screenshots, one for the Recents thumbnail and one for the pauseActivity thumbnail. @@ -508,30 +532,69 @@ final class ActivityStack { } final ActivityRecord isInStackLocked(IBinder token) { + ActivityRecord newAr = newIsInStackLocked(token); + ActivityRecord r = ActivityRecord.forToken(token); if (mHistory.contains(r)) { + if (VALIDATE_TASK_REPLACE && newAr != r) Slog.w(TAG, + "isInStackLocked: mismatch: newAr=" + newAr + " r=" + r); return r; } + if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, + "isInStackLocked: mismatch: newAr!=null"); + return null; + } + + final ActivityRecord newIsInStackLocked(IBinder token) { + final ActivityRecord r = ActivityRecord.forToken(token); + if (r != null) { + final TaskRecord task = r.task; + if (mTaskHistory.contains(task) && task.mActivities.contains(r)) { + return r; + } + } return null; } int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { + int newTaskId = newGetTaskForActivityLocked(token, onlyRoot); + TaskRecord lastTask = null; final int N = mHistory.size(); for (int i = 0; i < N; i++) { ActivityRecord r = mHistory.get(i); if (r.appToken == token) { if (!onlyRoot || lastTask != r.task) { + if (VALIDATE_TASK_REPLACE && newTaskId != r.task.taskId) Slog.w(TAG, + "getTaskForActivityLocked: mismatch: new=" + newTaskId + + " taskId=" + r.task.taskId); return r.task.taskId; } + if (VALIDATE_TASK_REPLACE && newTaskId != -1) Slog.w(TAG, + "getTaskForActivityLocked: mismatch: newTaskId=" + newTaskId + " not -1."); return -1; } lastTask = r.task; } + if (VALIDATE_TASK_REPLACE && newTaskId != -1) Slog.w(TAG, + "getTaskForActivityLocked: mismatch at end: newTaskId=" + newTaskId + " not -1."); return -1; } + int newGetTaskForActivityLocked(IBinder token, boolean onlyRoot) { + final ActivityRecord r = ActivityRecord.forToken(token); + if (r == null) { + return -1; + } + final TaskRecord task = r.task; + switch (task.mActivities.indexOf(r)) { + case -1: return -1; + case 0: return task.taskId; + default: return onlyRoot ? -1 : task.taskId; + } + } + private final boolean updateLRUListLocked(ActivityRecord r) { final boolean hadit = mLRUActivities.remove(r); mLRUActivities.add(r); @@ -624,17 +687,29 @@ final class ActivityStack { * @return whether there are any activities for the specified user. */ final boolean switchUserLocked(int userId, UserStartedState uss) { + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } + final boolean newResult = newSwitchUserLocked(userId, uss); + mCurrentUser = userId; mStartingUsers.add(uss); // Only one activity? Nothing to do... - if (mHistory.size() < 2) + if (mHistory.size() < 2) { + if (VALIDATE_TASK_REPLACE && newResult) Slog.w(TAG, + "switchUserLocked: mismatch: " + newResult + " " + false); return false; + } boolean haveActivities = false; // Check if the top activity is from the new user. ActivityRecord top = mHistory.get(mHistory.size() - 1); - if (top.userId == userId) return true; + if (top.userId == userId) { + if (VALIDATE_TASK_REPLACE && !newResult) Slog.w(TAG, + "switchUserLocked: mismatch: " + newResult + " " + true); + return true; + } // Otherwise, move the user's activities to the top. int N = mHistory.size(); int i = 0; @@ -651,7 +726,44 @@ final class ActivityStack { } } // Transition from the old top to the new top + if (VALIDATE_TASK_REPLACE) Slog.w(TAG, + "switchUserLocked: calling resumeTopActivity " + top); resumeTopActivityLocked(top); + if (VALIDATE_TASK_REPLACE && (newResult != haveActivities)) Slog.w(TAG, + "switchUserLocked: mismatch: " + newResult + " " + haveActivities); + return haveActivities; + } + + /* + * Move the activities around in the stack to bring a user to the foreground. + * @return whether there are any activities for the specified user. + */ + final boolean newSwitchUserLocked(int userId, UserStartedState uss) { +// mStartingUsers.add(uss); + if (mCurrentUser == userId) { + return true; + } + mCurrentUser = userId; + + // Move userId's tasks to the top. + boolean haveActivities = false; + TaskRecord task = null; + int index = mTaskHistory.size(); + for (int i = 0; i < index; ++i) { + task = mTaskHistory.get(i); + if (task.userId == userId) { + haveActivities = true; + mTaskHistory.remove(i); + mTaskHistory.add(task); + --index; + } + } + + // task is now the original topmost TaskRecord. Transition from the old top to the new top. + ActivityRecord top = task != null ? task.getTopActivity() : null; + if (VALIDATE_TASK_REPLACE) Slog.w(TAG, + "newSwitchUserLocked: would call resumeTopActivity " + top); +// resumeTopActivityLocked(top); return haveActivities; } @@ -889,6 +1001,9 @@ final class ActivityStack { mGoingToSleep.release(); } // Ensure activities are no longer sleeping. + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord r = mHistory.get(i); r.setSleeping(false); @@ -933,6 +1048,9 @@ final class ActivityStack { // Make sure any stopped but visible activities are now sleeping. // This ensures that the activity's onStop() is called. + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord r = mHistory.get(i); if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { @@ -1077,6 +1195,9 @@ final class ActivityStack { ActivityRecord r = null; synchronized (mService) { + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } int index = indexOfTokenLocked(token); if (index >= 0) { r = mHistory.get(index); @@ -1094,6 +1215,9 @@ final class ActivityStack { ActivityRecord r = null; synchronized (mService) { + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } int index = indexOfTokenLocked(token); if (index >= 0) { r = mHistory.get(index); @@ -1306,6 +1430,9 @@ final class ActivityStack { // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } final int count = mHistory.size(); int i = count-1; while (mHistory.get(i) != top) { @@ -1868,6 +1995,7 @@ final class ActivityStack { Slog.i(TAG, "Adding activity " + r + " to stack at " + addPos, here); } + r.task.addActivityToTop(r); mHistory.add(addPos, r); r.putInHistory(); mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, @@ -1907,6 +2035,7 @@ final class ActivityStack { here.fillInStackTrace(); Slog.i(TAG, "Adding activity " + r + " to stack at " + addPos, here); } + r.task.addActivityToTop(r); mHistory.add(addPos, r); r.putInHistory(); r.frontOfTask = newTask; @@ -1986,6 +2115,9 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } } final void validateAppTokensLocked() { @@ -2107,8 +2239,8 @@ final class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.setTask(new TaskRecord(mService.mCurTask, target.info, null), - null, false); + target.setTask(createTaskRecord(mService.mCurTask, target.info, null, + false), null, false); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -2172,8 +2304,7 @@ final class ActivityStack { // like these are all in the reply chain. replyChainEnd = targetI+1; while (replyChainEnd < mHistory.size() && - (mHistory.get( - replyChainEnd)).task == task) { + (mHistory.get(replyChainEnd)).task == task) { replyChainEnd++; } replyChainEnd--; @@ -2346,6 +2477,9 @@ final class ActivityStack { } } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(); + } return taskTop; } @@ -3011,7 +3145,7 @@ final class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true); + r.setTask(createTaskRecord(mService.mCurTask, r.info, intent, true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { @@ -3075,7 +3209,7 @@ final class ActivityStack { N > 0 ? mHistory.get(N-1) : null; r.setTask(prev != null ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent), null, true); + : createTaskRecord(mService.mCurTask, r.info, intent, true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -4160,6 +4294,9 @@ final class ActivityStack { here.fillInStackTrace(); Slog.i(TAG, "Removing activity " + r + " from stack"); } + if (r.task != null) { + r.task.removeActivity(r); + } mHistory.remove(r); r.takeFromHistory(); removeTimeoutsForActivityLocked(r); @@ -5169,4 +5306,185 @@ final class ActivityStack { return starting; } + + void verifyActivityRecords() { + /* Until we have activity movement implemented for tasks just do the simple test + ActivityIterator iterator = new ActivityIterator(); + int i; + int N = mHistory.size(); + for (i = 0; i < N && iterator.hasNext(); ++i) { + ActivityRecord r1 = mHistory.get(i); + ActivityRecord r2 = iterator.next(); + if (r1 != r2) { + break; + } + } + if (i != N || iterator.hasNext()) { + Slog.w(TAG, "verifyActivityRecords mHistory=" + mHistory + + " mTaskHistory=" + iterator + " Callers=" + Debug.getCallers(2)); + } */ + // Simple test + ActivityIterator iterator = new ActivityIterator(); + while (iterator.hasNext()) { + ActivityRecord r = iterator.next(); + if (!mHistory.contains(r)) { + break; + } + } + if (iterator.size() != mHistory.size() || iterator.hasNext()) { + Slog.w(TAG, "verifyActivityRecords mHistory=" + mHistory + + " mTaskHistory=" + iterator + " Callers=" + Debug.getCallers(2)); + } + } + + private TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, + boolean toTop) { + TaskRecord oldTask = mTaskIdToTaskRecord.get(taskId); + if (oldTask != null) { + Slog.w(TAG, "createTaskRecord: Reusing taskId=" + taskId + " without removing"); + mTaskHistory.remove(oldTask); + } + TaskRecord task = new TaskRecord(taskId, info, intent); + mTaskIdToTaskRecord.put(taskId, task); + if (toTop) { + mTaskHistory.add(task); + } else { + mTaskHistory.add(0, task); + } + return task; + } + + class TaskIterator implements Iterator { + private int mCur; + private boolean mReverse; + + TaskIterator() { + this(FORWARD_ITERATOR); + } + + TaskIterator(boolean reverse) { + reset(reverse); + } + + public void reset(boolean reverse) { + mReverse = reverse; + mCur = reverse ? mTaskHistory.size() - 1 : 0; + } + + @Override + public boolean hasNext() { + if (mReverse) { + return mCur >= 0; + } + return mCur < mTaskHistory.size(); + } + + @Override + public TaskRecord next() { + if (hasNext()) { + TaskRecord task = mTaskHistory.get(mCur); + mCur += (mReverse ? -1 : 1); + return task; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new IllegalArgumentException(); + } + } + + class ActivityIterator implements Iterator { + final TaskIterator mIterator; + boolean mReverse; + int mCur; + TaskRecord mTaskRecord; + final boolean mSkipFinishing; + + public ActivityIterator() { + this(FORWARD_ITERATOR); + } + + public ActivityIterator(boolean reverse) { + this(reverse, false); + } + + public ActivityIterator(boolean reverse, boolean skipFinishing) { + mSkipFinishing = skipFinishing; + mIterator = new TaskIterator(); + reset(reverse); + } + + public void reset(boolean reverse) { + mReverse = reverse; + mIterator.reset(reverse); + getNextTaskRecord(); + } + + private void getNextTaskRecord() { + if (mIterator.hasNext()) { + mTaskRecord = mIterator.next(); + mCur = mReverse ? mTaskRecord.mActivities.size() - 1 : 0; + } + } + + @Override + public boolean hasNext() { + if (mTaskRecord == null) { + return false; + } + if (mReverse) { + return mCur >= 0; + } + return mCur < mTaskRecord.mActivities.size(); + } + + @Override + public ActivityRecord next() { + while (hasNext()) { + ActivityRecord r = mTaskRecord.mActivities.get(mCur); + mCur += mReverse ? -1 : 1; + if (!hasNext()) { + getNextTaskRecord(); + } + if (mSkipFinishing && r.finishing) { + continue; + } + return r; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + int size() { + int size = 0; + final TaskIterator iterator = new TaskIterator(); + while (iterator.hasNext()) { + size += iterator.next().mActivities.size(); + } + return size; + } + + ActivityRecord peek() { + if (mTaskRecord != null && mCur >= 0 && mCur < mTaskRecord.mActivities.size()) { + return mTaskRecord.mActivities.get(mCur); + } + return null; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < mTaskHistory.size(); ++i) { + final TaskRecord task = mTaskHistory.get(i); + sb.append("task_").append(i).append("-").append(task.mActivities).append(" "); + } + return sb.toString(); + } + } } diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 1bae9ca53adf2..347aa7d039e29 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -23,6 +23,7 @@ import android.os.UserHandle; import android.util.Slog; import java.io.PrintWriter; +import java.util.ArrayList; class TaskRecord extends ThumbnailHolder { final int taskId; // Unique identifier for this task. @@ -39,7 +40,11 @@ class TaskRecord extends ThumbnailHolder { String stringName; // caching of toString() result. int userId; // user for which this task was created - + + int numFullscreen; // Number of fullscreen activities. + + final ArrayList mActivities = new ArrayList(); + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; @@ -104,12 +109,49 @@ class TaskRecord extends ThumbnailHolder { userId = UserHandle.getUserId(info.applicationInfo.uid); } } - + + ActivityRecord getTopActivity() { + for (int i = mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = mActivities.get(i); + if (r.finishing) { + continue; + } + return r; + } + return null; + } + + void addActivityAtBottom(ActivityRecord r) { + if (!mActivities.remove(r) && r.fullscreen) { + // Was not previously in list. + numFullscreen++; + } + mActivities.add(0, r); + } + + void addActivityToTop(ActivityRecord r) { + if (!mActivities.remove(r) && r.fullscreen) { + // Was not previously in list. + numFullscreen++; + } + mActivities.add(r); + } + + /** @return true if this was the last activity in the task */ + boolean removeActivity(ActivityRecord r) { + if (mActivities.remove(r) && r.fullscreen) { + // Was previously in list. + numFullscreen--; + } + return mActivities.size() == 0; + } + void dump(PrintWriter pw, String prefix) { if (numActivities != 0 || rootWasReset || userId != 0) { pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.print(rootWasReset); - pw.print(" userId="); pw.println(userId); + pw.print(" userId="); pw.print(userId); + pw.print(" numFullscreen="); pw.println(numFullscreen); } if (affinity != null) { pw.print(prefix); pw.print("affinity="); pw.println(affinity); @@ -136,6 +178,7 @@ class TaskRecord extends ThumbnailHolder { pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } + pw.print(prefix); pw.print("Activities="); pw.println(mActivities); if (!askedCompatMode) { pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode); } @@ -146,6 +189,7 @@ class TaskRecord extends ThumbnailHolder { pw.print((getInactiveDuration()/1000)); pw.println("s)"); } + @Override public String toString() { if (stringName != null) { return stringName; @@ -156,19 +200,21 @@ class TaskRecord extends ThumbnailHolder { sb.append(" #"); sb.append(taskId); if (affinity != null) { - sb.append(" A "); + sb.append(" A="); sb.append(affinity); } else if (intent != null) { - sb.append(" I "); + sb.append(" I="); sb.append(intent.getComponent().flattenToShortString()); } else if (affinityIntent != null) { - sb.append(" aI "); + sb.append(" aI="); sb.append(affinityIntent.getComponent().flattenToShortString()); } else { sb.append(" ??"); } - sb.append(" U "); + sb.append(" U="); sb.append(userId); + sb.append(" sz="); + sb.append(mActivities.size()); sb.append('}'); return stringName = sb.toString(); } diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index cc7c8b0f832dd..938fa5c15aba2 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -228,6 +228,10 @@ class DisplayContent { public void remove() { throw new IllegalArgumentException(); } + + @Override public String toString() { + return mTaskLists.toString(); + } } class AppTokenIterator implements Iterator { @@ -294,6 +298,10 @@ class DisplayContent { } return size; } + + @Override public String toString() { + return mIterator.toString(); + } } public void dump(String prefix, PrintWriter pw) { diff --git a/services/java/com/android/server/wm/TaskGroup.java b/services/java/com/android/server/wm/TaskGroup.java index 5e82af263aefe..1f1dd5832d558 100644 --- a/services/java/com/android/server/wm/TaskGroup.java +++ b/services/java/com/android/server/wm/TaskGroup.java @@ -23,4 +23,9 @@ import java.util.ArrayList; public class TaskGroup { public int taskId = -1; public ArrayList tokens = new ArrayList(); + + @Override + public String toString() { + return "id=" + taskId + " tokens=" + tokens; + } } diff --git a/services/java/com/android/server/wm/TaskList.java b/services/java/com/android/server/wm/TaskList.java index 88791f206c3e1..67dfa4f333b44 100644 --- a/services/java/com/android/server/wm/TaskList.java +++ b/services/java/com/android/server/wm/TaskList.java @@ -27,4 +27,9 @@ class TaskList { mAppTokens.add(wtoken); mDisplayContent = displayContent; } + + @Override + public String toString() { + return "id=" + taskId + " appTokens=" + mAppTokens; + } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index e6d6436bfe2d9..b1934300dd3b4 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3124,8 +3124,9 @@ public class WindowManagerService extends IWindowManager.Stub return; } + boolean mismatch = false; AppTokenIterator iterator = displayContent.getTmpAppIterator(REVERSE_ITERATOR); - for ( ; t >= 0; --t) { + for ( ; t >= 0 && !mismatch; --t) { task = tasks.get(t); List tokens = task.tokens; int v = task.tokens.size() - 1; @@ -3137,28 +3138,22 @@ public class WindowManagerService extends IWindowManager.Stub return; } - while (v >= 0 && iterator.hasNext()) { + while (v >= 0) { AppWindowToken atoken = iterator.next(); if (atoken.removed) { continue; } if (tokens.get(v) != atoken.token) { - Slog.w(TAG, "Tokens out of sync: external is " + tokens.get(v) - + " @ " + v + ", internal is " + atoken.token); + mismatch = true; + break; } v--; } - while (v >= 0) { - Slog.w(TAG, "External token not found: " + tokens.get(v) + " @ " + v); - v--; - } } - while (iterator.hasNext()) { - AppWindowToken atoken = iterator.next(); - if (!atoken.removed) { - Slog.w(TAG, "Invalid internal atoken: " + atoken.token); - } + if (mismatch || iterator.hasNext()) { + Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks + + " WindowManager=" + iterator); } } } From cf44fea1b6f85c790aa700066e47908c4e4737a2 Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Wed, 20 Feb 2013 16:28:39 -0800 Subject: [PATCH 03/76] Trying to unregister a semi connected accessibility service. When an accessibility service connects we get a callback in which we either add the service, if this service is in the list of connecting services (we still want the service to connect), or we unbind and clear the state, if the service is no longer in the list of connecting services (we do not want this service to connect because something change between the bind request and the connection callback). The problem is that when the service connects and it is not in the list of connecting services on service connected we called the clean up code before the connection was complete. However, the clean up code expects fully configured services. Now we fully connect the service and in case there is a problem - disconnect it. bug:8232627 Change-Id: I939e544e31ffc1406035265a012c180f2ca95d7c --- .../AccessibilityManagerService.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 9c518a11637f4..fd5e79a99268c 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -934,12 +934,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @param service The service. * @return True if the service was removed, false otherwise. */ - private void removeServiceLocked(Service service) { - UserState userState = getUserStateLocked(service.mUserId); + private void removeServiceLocked(Service service, UserState userState) { userState.mBoundServices.remove(service); userState.mComponentNameToServiceMap.remove(service.mComponentName); service.unlinkToOwnDeath(); - service.dispose(); } /** @@ -1672,11 +1670,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean bindLocked() { UserState userState = getUserStateLocked(mUserId); if (!mIsAutomation) { - if (mService == null) { - if (mContext.bindServiceAsUser(mIntent, this, Context.BIND_AUTO_CREATE, - new UserHandle(mUserId))) { - userState.mBindingServices.add(mComponentName); - } + if (mService == null && mContext.bindServiceAsUser( + mIntent, this, Context.BIND_AUTO_CREATE, new UserHandle(mUserId))) { + userState.mBindingServices.add(mComponentName); } } else { userState.mBindingServices.add(mComponentName); @@ -1697,14 +1693,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (mService == null) { return false; } + UserState userState = getUserStateLocked(mUserId); if (!mIsAutomation) { mContext.unbindService(this); } else { - UserState userState = getUserStateLocked(mUserId); userState.mUiAutomationService = null; userState.mUiAutomationServiceClient = null; } - removeServiceLocked(this); + removeServiceLocked(this, userState); + dispose(); return true; } @@ -1750,11 +1747,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mService = service; mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); UserState userState = getUserStateLocked(mUserId); + addServiceLocked(this, userState); if (!userState.mBindingServices.contains(mComponentName)) { binderDied(); } else { userState.mBindingServices.remove(mComponentName); - addServiceLocked(this, userState); onUserStateChangedLocked(userState); } } @@ -2106,9 +2103,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void binderDied() { synchronized (mLock) { - // The death recipient is unregistered in tryRemoveServiceLocked - removeServiceLocked(this); UserState userState = getUserStateLocked(mUserId); + // The death recipient is unregistered in removeServiceLocked + removeServiceLocked(this, userState); + dispose(); if (mIsAutomation) { // We no longer have an automation service, so restore // the state based on values in the settings database. From 231d9b897994d6ec5e383d67d5181bf7c619ef4e Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Tue, 19 Feb 2013 14:08:51 -0800 Subject: [PATCH 04/76] Switch topRunning* and moveTaskTo* - More of the Activity to Task changeover. - Fix bug in validateAppTokens(). - Improved validation of changeover. - Eliminated iterator classes. Change-Id: I934a208eabfc9a2668e5a6162452e1406f2c8d3a --- .../com/android/server/am/ActivityRecord.java | 1 + .../com/android/server/am/ActivityStack.java | 495 ++++++++++-------- .../com/android/server/am/TaskRecord.java | 16 +- .../com/android/server/wm/DisplayContent.java | 86 +-- .../server/wm/WindowManagerService.java | 5 +- 5 files changed, 306 insertions(+), 297 deletions(-) diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index f2d401fdacc99..a2f337296deb7 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -471,6 +471,7 @@ final class ActivityRecord { void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { if (inHistory && !finishing) { if (task != null) { + // TODO: If this is the last ActivityRecord in task, remove from ActivityStack. task.removeActivity(this); task.numActivities--; } diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index a18a0d1c35f89..30a7e23966618 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -77,7 +77,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; /** * State and management of a single stack of activities. @@ -142,9 +141,6 @@ final class ActivityStack { // is being started. static final boolean SHOW_APP_STARTING_PREVIEW = true; - static final boolean FORWARD_ITERATOR = false; - static final boolean REVERSE_ITERATOR = true; - enum ActivityState { INITIALIZING, RESUMED, @@ -307,12 +303,6 @@ final class ActivityStack { */ boolean mDismissKeyguardOnNextActivity = false; - /** So we don't have to keep constructing a new object for utility non-nested use. */ - final ActivityIterator mTmpActivityIterator = new ActivityIterator(FORWARD_ITERATOR, true); - - /** So we don't have to keep constructing a new object for utility non-nested use. */ - final TaskIterator mTmpTaskIterator = new TaskIterator(); - /** * Save the most recent screenshot for reuse. This keeps Recents from taking two identical * screenshots, one for the Recents thumbnail and one for the pauseActivity thumbnail. @@ -355,6 +345,8 @@ final class ActivityStack { final Handler mHandler; + String mLastHistoryModifier; + final class ActivityStackHandler extends Handler { //public Handler() { // if (localLOGV) Slog.v(TAG, "Handler started!"); @@ -481,26 +473,66 @@ final class ActivityStack { } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { + ActivityRecord newAr = newTopRunningActivityLocked(notTop); + int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); if (!r.finishing && r != notTop && okToShow(r)) { + if (VALIDATE_TASK_REPLACE && newAr != r) logHistories( + "topRunningActivityLocked", true); return r; } i--; } + if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, + "topRunningActivityLocked: mismatch: newAr!=null"); + return null; + } + + final ActivityRecord newTopRunningActivityLocked(ActivityRecord notTop) { + for (int i = mTaskHistory.size() - 1; i >= 0; --i) { + final TaskRecord task = mTaskHistory.get(i); + final ArrayList activities = task.mActivities; + for (int j = activities.size() - 1; j >= 0; --j) { + ActivityRecord r = activities.get(j); + if (!r.finishing && r != notTop && okToShow(r)) { + return r; + } + } + } return null; } final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { + ActivityRecord newAr = newTopRunningNonDelayedActivityLocked(notTop); + int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) { + if (VALIDATE_TASK_REPLACE && newAr != r) Slog.w(TAG, + "topRunningNonDelayedActivityLocked: mismatch: newAr=" + newAr + " r=" + r); return r; } i--; } + if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, + "topRunningNonDelayedActivityLocked: mismatch: newAr!=null"); + return null; + } + + final ActivityRecord newTopRunningNonDelayedActivityLocked(ActivityRecord notTop) { + for (int i = mTaskHistory.size() - 1; i >= 0; --i) { + final TaskRecord task = mTaskHistory.get(i); + final ArrayList activities = task.mActivities; + for (int j = activities.size() - 1; j >= 0; --j) { + ActivityRecord r = activities.get(j); + if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) { + return r; + } + } + } return null; } @@ -514,16 +546,40 @@ final class ActivityStack { * @return Returns the HistoryRecord of the next activity on the stack. */ final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { + ActivityRecord newAr = newTopRunningActivityLocked(token, taskId); + int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); // Note: the taskId check depends on real taskId fields being non-zero if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId) && okToShow(r)) { + if (VALIDATE_TASK_REPLACE && newAr != r) Slog.w(TAG, + "topRunningActivityLocked(token): mismatch: newAr=" + newAr + " r=" + r); return r; } i--; } + if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, + "topRunningActivityLocked(token): mismatch: newAr!=null"); + return null; + } + + final ActivityRecord newTopRunningActivityLocked(IBinder token, int taskId) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + TaskRecord task = mTaskHistory.get(taskNdx); + if (task.taskId == taskId) { + continue; + } + ArrayList activities = task.mActivities; + for (int i = activities.size() - 1; i >= 0; --i) { + final ActivityRecord r = activities.get(i); + // Note: the taskId check depends on real taskId fields being non-zero + if (!r.finishing && (token != r.appToken) && okToShow(r)) { + return r; + } + } + } return null; } @@ -1002,7 +1058,7 @@ final class ActivityStack { } // Ensure activities are no longer sleeping. if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + verifyActivityRecords(true); } for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord r = mHistory.get(i); @@ -1049,7 +1105,7 @@ final class ActivityStack { // Make sure any stopped but visible activities are now sleeping. // This ensures that the activity's onStop() is called. if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + verifyActivityRecords(true); } for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord r = mHistory.get(i); @@ -1196,7 +1252,7 @@ final class ActivityStack { synchronized (mService) { if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + verifyActivityRecords(true); } int index = indexOfTokenLocked(token); if (index >= 0) { @@ -1216,7 +1272,7 @@ final class ActivityStack { synchronized (mService) { if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + verifyActivityRecords(true); } int index = indexOfTokenLocked(token); if (index >= 0) { @@ -1431,7 +1487,7 @@ final class ActivityStack { // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + verifyActivityRecords(true); } final int count = mHistory.size(); int i = count-1; @@ -1969,8 +2025,21 @@ final class ActivityStack { return true; } + /** Temporary until startActivityLocked is rewritten for tasks. */ + private int convertAddPos(int addPos) { + final int taskId = mHistory.get(addPos).task.taskId; + addPos--; + int taskOffset = 0; + while (addPos >= 0 && taskId == mHistory.get(addPos).task.taskId) { + ++taskOffset; + --addPos; + } + return taskOffset; + } + private final void startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition, Bundle options) { + mLastHistoryModifier = "startActivityLocked"; final int NH = mHistory.size(); int addPos = -1; @@ -1998,11 +2067,12 @@ final class ActivityStack { r.task.addActivityToTop(r); mHistory.add(addPos, r); r.putInHistory(); - mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, - r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken(convertAddPos(addPos), r.appToken, + r.task.taskId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); if (VALIDATE_TOKENS) { validateAppTokensLocked(); + verifyActivityRecords(true); } ActivityOptions.abort(options); return; @@ -2039,6 +2109,11 @@ final class ActivityStack { mHistory.add(addPos, r); r.putInHistory(); r.frontOfTask = newTask; + if (VALIDATE_TASK_REPLACE) { + if (verifyActivityRecords(false)) { + Slog.w(TAG, "startActivityLocked: addPos=" + addPos); + } + } 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 @@ -2064,8 +2139,8 @@ final class ActivityStack { mNoAnimActivities.remove(r); } r.updateOptionsLocked(options); - mService.mWindowManager.addAppToken( - addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken(convertAddPos(addPos), + r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); boolean doShow = true; if (newTask) { @@ -2103,7 +2178,7 @@ final class ActivityStack { } 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.appToken, r.task.taskId, + mService.mWindowManager.addAppToken(convertAddPos(addPos), r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); ActivityOptions.abort(options); @@ -2116,7 +2191,9 @@ final class ActivityStack { resumeTopActivityLocked(null); } if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + if (verifyActivityRecords(true)) { + Slog.w(TAG, "startActivityLocked: addPos=" + addPos); + } } } @@ -2144,6 +2221,8 @@ final class ActivityStack { */ private final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, ActivityRecord newActivity) { + mLastHistoryModifier = "resetTaskIfNeededLocked"; + boolean forceReset = (newActivity.info.flags &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; if (ACTIVITY_INACTIVE_RESET_TIME > 0 @@ -2281,6 +2360,7 @@ final class ActivityStack { dstPos++; i++; } + rebuildTaskHistory(); mService.mWindowManager.moveTaskToBottom(taskId); if (VALIDATE_TOKENS) { validateAppTokensLocked(); @@ -2429,6 +2509,7 @@ final class ActivityStack { + " in to resetting task " + task); mService.mWindowManager.setAppGroupId(p.appToken, taskId); } + rebuildTaskHistory(); // TODO: This is wrong because it doesn't take lastReparentPos into account. mService.mWindowManager.moveTaskToTop(taskId); if (VALIDATE_TOKENS) { @@ -2478,7 +2559,7 @@ final class ActivityStack { } if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(); + verifyActivityRecords(true); } return taskTop; } @@ -2644,6 +2725,7 @@ final class ActivityStack { */ private final ActivityRecord moveActivityToFrontLocked(int where) { ActivityRecord newTop = mHistory.remove(where); + newMoveActivityToFrontLocked(newTop); int top = mHistory.size(); ActivityRecord oldTop = mHistory.get(top-1); if (DEBUG_ADD_REMOVE) { @@ -2653,6 +2735,17 @@ final class ActivityStack { + top, here); } mHistory.add(top, newTop); + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(true); + } + return newTop; + } + + private final ActivityRecord newMoveActivityToFrontLocked(ActivityRecord newTop) { + final TaskRecord task = newTop.task; + ActivityRecord oldTop = task.getTopActivity(); + task.mActivities.remove(newTop); + task.mActivities.add(newTop); oldTop.frontOfTask = false; newTop.frontOfTask = true; return newTop; @@ -2663,6 +2756,7 @@ final class ActivityStack { String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity) { + mLastHistoryModifier = "startActivityLocked(IApplicationThread)"; int err = ActivityManager.START_SUCCESS; @@ -4294,8 +4388,9 @@ final class ActivityStack { here.fillInStackTrace(); Slog.i(TAG, "Removing activity " + r + " from stack"); } - if (r.task != null) { - r.task.removeActivity(r); + final TaskRecord task = r.task; + if (task != null) { + task.removeActivity(r); } mHistory.remove(r); r.takeFromHistory(); @@ -4597,6 +4692,7 @@ final class ActivityStack { * of the stack. */ final void moveHomeToFrontLocked() { + newMoveHomeToFrontLocked(); TaskRecord homeTask = null; for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord hr = mHistory.get(i); @@ -4606,6 +4702,23 @@ final class ActivityStack { } } if (homeTask != null) { +// moveTaskToFrontLocked(homeTask, null, null); + } + } + + final void newMoveHomeToFrontLocked() { + TaskRecord homeTask = null; + for (int taskNdx = mTaskHistory.size() - 1; homeTask == null && taskNdx >= 0; --taskNdx) { + final ArrayList activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.isHomeActivity) { + homeTask = r.task; + break; + } + } + } + if (homeTask != null) { moveTaskToFrontLocked(homeTask, null, null); } } @@ -4642,31 +4755,19 @@ final class ActivityStack { } final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) { - if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); final int task = tr.taskId; int top = mHistory.size()-1; if (top < 0 || (mHistory.get(top)).task.taskId == task) { // nothing to do! - if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - ActivityOptions.abort(options); - } else { - updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); - } 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. + int pos = top; while (pos >= 0) { ActivityRecord r = mHistory.get(pos); if (localLOGV) Slog.v( @@ -4680,18 +4781,37 @@ final class ActivityStack { } mHistory.remove(pos); mHistory.add(top, r); - moved.add(0, r.appToken); top--; } pos--; } + // + // Start new code here! Delete everything above. + // + if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to front transition: task=" + tr); + final int numTasks = mTaskHistory.size(); + final int index = mTaskHistory.indexOf(tr); + if (numTasks == 0 || index < 0 || index == numTasks - 1) { + // nothing to do! + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + ActivityOptions.abort(options); + } else { + updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); + } + return; + } + + // Shift all activities with this task up to the top + // of the stack, keeping them in the same internal order. + mTaskHistory.remove(tr); + mTaskHistory.add(tr); + + 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( - AppTransition.TRANSIT_NONE, false); + mService.mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); @@ -4702,12 +4822,16 @@ final class ActivityStack { } mService.mWindowManager.moveTaskToTop(task); - if (VALIDATE_TOKENS) { - validateAppTokensLocked(); - } finishTaskMoveLocked(task); EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, task); + + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(true); + } } private final void finishTaskMoveLocked(int task) { @@ -4726,6 +4850,42 @@ final class ActivityStack { * @return Returns true if the move completed, false if not. */ final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { + if (!newMoveTaskToBackLocked(task, reason)) { + return false; + } + + 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 = 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)); + if (DEBUG_ADD_REMOVE) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.i(TAG, "Removing and adding activity " + r + " to stack at " + + bottom, here); + } + mHistory.remove(pos); + mHistory.add(bottom, r); + bottom++; + } + pos++; + } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(true); + } + + return true; + } + + final boolean newMoveTaskToBackLocked(int task, ActivityRecord reason) { Slog.i(TAG, "moveTaskToBack: " + task); // If we have a watcher, preflight the move before committing to it. First check @@ -4750,41 +4910,16 @@ final class ActivityStack { } } - 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 = 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)); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + r + " to stack at " - + bottom, here); - } - mHistory.remove(pos); - mHistory.add(bottom, r); - moved.add(r.appToken); - bottom++; - } - pos++; - } + final TaskRecord tr = mTaskIdToTaskRecord.get(task); + mTaskHistory.remove(tr); + mTaskHistory.add(0, tr); if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, false); + mService.mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); @@ -5307,33 +5442,65 @@ final class ActivityStack { return starting; } - void verifyActivityRecords() { - /* Until we have activity movement implemented for tasks just do the simple test - ActivityIterator iterator = new ActivityIterator(); - int i; - int N = mHistory.size(); - for (i = 0; i < N && iterator.hasNext(); ++i) { - ActivityRecord r1 = mHistory.get(i); - ActivityRecord r2 = iterator.next(); - if (r1 != r2) { + void rebuildTaskHistory() { + mTaskHistory.clear(); + final int numActivities = mHistory.size(); + TaskRecord task = null; + for (int i = 0; i < numActivities; ++i) { + final ActivityRecord r = mHistory.get(i); + if (r.task != task) { + task = r.task; + task.mActivities.clear(); + mTaskHistory.add(task); + } + task.mActivities.add(r); + } + } + + boolean verifyActivityRecords(boolean rebuild) { + final int numHistory = mHistory.size(); + int historyNdx = 0; + + final int numTasks = mTaskHistory.size(); + int taskNdx; + for (taskNdx = historyNdx = 0; taskNdx < numTasks; ++taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList activities = task.mActivities; + final int numActivities = activities.size(); + int activityNdx; + for (activityNdx = 0; + activityNdx < numActivities && historyNdx < numHistory; + ++activityNdx, ++historyNdx) { + ActivityRecord r1 = mHistory.get(historyNdx); + ActivityRecord r2 = activities.get(activityNdx); + if (r1 != r2) { + break; + } + } + if (activityNdx != numActivities) { + // either a mismatch or mHistory ran out before mTaskHistory. break; } } - if (i != N || iterator.hasNext()) { - Slog.w(TAG, "verifyActivityRecords mHistory=" + mHistory - + " mTaskHistory=" + iterator + " Callers=" + Debug.getCallers(2)); - } */ - // Simple test - ActivityIterator iterator = new ActivityIterator(); - while (iterator.hasNext()) { - ActivityRecord r = iterator.next(); - if (!mHistory.contains(r)) { - break; - } + if (taskNdx != numTasks || historyNdx != numHistory) { + logHistories("verifyActivityRecords", rebuild); + return true; } - if (iterator.size() != mHistory.size() || iterator.hasNext()) { - Slog.w(TAG, "verifyActivityRecords mHistory=" + mHistory - + " mTaskHistory=" + iterator + " Callers=" + Debug.getCallers(2)); + return false; + } + + private void logHistories(String caller, boolean rebuild) { + Slog.w(TAG, "Mismatch! " + caller + " mHistory=" + mHistory); + ArrayList> nestedRecords = + new ArrayList>(); + for (TaskRecord task : mTaskHistory) { + nestedRecords.add(task.mActivities); + } + Slog.w(TAG, "Mismatch! " + caller + " mTaskHistory" + nestedRecords); + Slog.w(TAG, "Mismatch! " + caller + " lastHistoryModifier=" + mLastHistoryModifier + + " Caller=" + Debug.getCallers(4)); + if (rebuild) { + rebuildTaskHistory(); } } @@ -5353,138 +5520,4 @@ final class ActivityStack { } return task; } - - class TaskIterator implements Iterator { - private int mCur; - private boolean mReverse; - - TaskIterator() { - this(FORWARD_ITERATOR); - } - - TaskIterator(boolean reverse) { - reset(reverse); - } - - public void reset(boolean reverse) { - mReverse = reverse; - mCur = reverse ? mTaskHistory.size() - 1 : 0; - } - - @Override - public boolean hasNext() { - if (mReverse) { - return mCur >= 0; - } - return mCur < mTaskHistory.size(); - } - - @Override - public TaskRecord next() { - if (hasNext()) { - TaskRecord task = mTaskHistory.get(mCur); - mCur += (mReverse ? -1 : 1); - return task; - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new IllegalArgumentException(); - } - } - - class ActivityIterator implements Iterator { - final TaskIterator mIterator; - boolean mReverse; - int mCur; - TaskRecord mTaskRecord; - final boolean mSkipFinishing; - - public ActivityIterator() { - this(FORWARD_ITERATOR); - } - - public ActivityIterator(boolean reverse) { - this(reverse, false); - } - - public ActivityIterator(boolean reverse, boolean skipFinishing) { - mSkipFinishing = skipFinishing; - mIterator = new TaskIterator(); - reset(reverse); - } - - public void reset(boolean reverse) { - mReverse = reverse; - mIterator.reset(reverse); - getNextTaskRecord(); - } - - private void getNextTaskRecord() { - if (mIterator.hasNext()) { - mTaskRecord = mIterator.next(); - mCur = mReverse ? mTaskRecord.mActivities.size() - 1 : 0; - } - } - - @Override - public boolean hasNext() { - if (mTaskRecord == null) { - return false; - } - if (mReverse) { - return mCur >= 0; - } - return mCur < mTaskRecord.mActivities.size(); - } - - @Override - public ActivityRecord next() { - while (hasNext()) { - ActivityRecord r = mTaskRecord.mActivities.get(mCur); - mCur += mReverse ? -1 : 1; - if (!hasNext()) { - getNextTaskRecord(); - } - if (mSkipFinishing && r.finishing) { - continue; - } - return r; - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - int size() { - int size = 0; - final TaskIterator iterator = new TaskIterator(); - while (iterator.hasNext()) { - size += iterator.next().mActivities.size(); - } - return size; - } - - ActivityRecord peek() { - if (mTaskRecord != null && mCur >= 0 && mCur < mTaskRecord.mActivities.size()) { - return mTaskRecord.mActivities.get(mCur); - } - return null; - } - - @Override - public String toString() { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < mTaskHistory.size(); ++i) { - final TaskRecord task = mTaskHistory.get(i); - sb.append("task_").append(i).append("-").append(task.mActivities).append(" "); - } - return sb.toString(); - } - } } diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 347aa7d039e29..f9b0d4cfe93bd 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -134,7 +134,21 @@ class TaskRecord extends ThumbnailHolder { // Was not previously in list. numFullscreen++; } - mActivities.add(r); + // TODO: This only matters to achieve identical results as mHistory. Later we won't need + // to skip over finishing activities. + int i; + for (i = mActivities.size() - 1; i >= 0; --i) { + if (!mActivities.get(i).finishing) { + break; + } + } + if (i >= 0) { + // Add below finishing activities. + mActivities.add(i + 1, r); + } else { + // All activities are finishing, add to top. + mActivities.add(r); + } } /** @return true if this was the last activity in the task */ diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index 938fa5c15aba2..6aae202d23096 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -19,9 +19,6 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerService.FORWARD_ITERATOR; import static com.android.server.wm.WindowManagerService.REVERSE_ITERATOR; -import android.graphics.Rect; -import android.os.Debug; -import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; @@ -138,7 +135,7 @@ class DisplayContent { mTaskIdToTaskList.put(wtoken.groupId, task); mTaskLists.add(task); } else { - task.mAppTokens.add(wtoken); + task.mAppTokens.add(addPos, wtoken); } } @@ -189,55 +186,10 @@ class DisplayContent { return mTmpAppIterator; } - class TaskListsIterator implements Iterator { - private int mCur; - private boolean mReverse; - - TaskListsIterator() { - this(false); - } - - TaskListsIterator(boolean reverse) { - reset(reverse); - } - - void reset(boolean reverse) { - mReverse = reverse; - mCur = reverse ? mTaskLists.size() - 1 : 0; - } - - @Override - public boolean hasNext() { - if (mReverse) { - return mCur >= 0; - } - return mCur < mTaskLists.size(); - } - - @Override - public TaskList next() { - if (hasNext()) { - TaskList taskList = mTaskLists.get(mCur); - mCur += (mReverse ? -1 : 1); - return taskList; - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new IllegalArgumentException(); - } - - @Override public String toString() { - return mTaskLists.toString(); - } - } - class AppTokenIterator implements Iterator { - final TaskListsIterator mIterator = new TaskListsIterator(); boolean mReverse; - int mCur; + int mTasksNdx; + int mActivityNdx; TaskList mTaskList; public AppTokenIterator() { @@ -250,14 +202,23 @@ class DisplayContent { void reset(boolean reverse) { mReverse = reverse; - mIterator.reset(reverse); + mTasksNdx = reverse ? mTaskLists.size() - 1 : 0; getNextTaskList(); } private void getNextTaskList() { - if (mIterator.hasNext()) { - mTaskList = mIterator.next(); - mCur = mReverse ? mTaskList.mAppTokens.size() - 1 : 0; + if (mReverse) { + if (mTasksNdx >= 0) { + mTaskList = mTaskLists.get(mTasksNdx); + --mTasksNdx; + mActivityNdx = mTaskList.mAppTokens.size() - 1; + } + } else { + if (mTasksNdx < mTaskLists.size()) { + mTaskList = mTaskLists.get(mTasksNdx); + ++mTasksNdx; + mActivityNdx = 0; + } } } @@ -267,16 +228,16 @@ class DisplayContent { return false; } if (mReverse) { - return mCur >= 0; + return mActivityNdx >= 0; } - return mCur < mTaskList.mAppTokens.size(); + return mActivityNdx < mTaskList.mAppTokens.size(); } @Override public AppWindowToken next() { if (hasNext()) { - AppWindowToken wtoken = mTaskList.mAppTokens.get(mCur); - mCur += mReverse ? -1 : 1; + AppWindowToken wtoken = mTaskList.mAppTokens.get(mActivityNdx); + mActivityNdx += mReverse ? -1 : 1; if (!hasNext()) { getNextTaskList(); } @@ -292,15 +253,14 @@ class DisplayContent { int size() { int size = 0; - final TaskListsIterator iterator = new TaskListsIterator(); - while (iterator.hasNext()) { - size += iterator.next().mAppTokens.size(); + for (int i = mTaskLists.size() - 1; i >= 0; --i) { + size += mTaskLists.get(i).mAppTokens.size(); } return size; } @Override public String toString() { - return mIterator.toString(); + return mTaskLists.toString(); } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index b1934300dd3b4..966ceda44f289 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3152,8 +3152,8 @@ public class WindowManagerService extends IWindowManager.Stub } if (mismatch || iterator.hasNext()) { - Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks - + " WindowManager=" + iterator); + Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks); + Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + iterator); } } } @@ -7281,6 +7281,7 @@ public class WindowManagerService extends IWindowManager.Stub performLayoutAndPlaceSurfacesLocked(); } + @Override public void setOverscan(int displayId, int left, int top, int right, int bottom) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS) != From 7abba422f40c1ed5c3ed2e8e73344991a7062b61 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Wed, 20 Feb 2013 16:54:05 -0800 Subject: [PATCH 05/76] Fix crashing bug in validator. Change-Id: I649bb7cfc2b42fbeda478edcaa3a56f3c23d4bc8 --- services/java/com/android/server/wm/WindowManagerService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 966ceda44f289..015987773e734 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3139,6 +3139,10 @@ public class WindowManagerService extends IWindowManager.Stub } while (v >= 0) { + if (!iterator.hasNext()) { + mismatch = true; + break; + } AppWindowToken atoken = iterator.next(); if (atoken.removed) { continue; From b98f331bd19eafbae121d97218689d6b45dc33da Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Thu, 21 Feb 2013 08:26:06 -0800 Subject: [PATCH 06/76] Add null check to setAppGroupId. Fix bug 8217929. Change-Id: I3bd54c32abcf6683c2fa75a85bf5025f47e09398 --- .../java/com/android/server/wm/WindowManagerService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 015987773e734..c2213b3136cca 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3336,12 +3336,17 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - AppWindowToken atoken = findAppWindowToken(token); + final AppWindowToken atoken = findAppWindowToken(token); if (atoken == null) { Slog.w(TAG, "Attempted to set group id of non-existing app token: " + token); return; } - mTaskIdToDisplayContents.get(atoken.groupId).setAppTaskId(atoken, groupId); + DisplayContent displayContent = mTaskIdToDisplayContents.get(atoken.groupId); + if (displayContent == null) { + Slog.w(TAG, "setAppGroupId: No DisplayContent for taskId=" + atoken.groupId); + displayContent = getDefaultDisplayContentLocked(); + } + displayContent.setAppTaskId(atoken, groupId); } } From ce361f5f88d19d192a3ec4a2c369a3050d53aaaa Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Thu, 21 Feb 2013 19:46:56 -0800 Subject: [PATCH 07/76] Update histories simultaneously. In moveTaskToBackLocked calls that used mHistory were being made between the times that mTaskHistory and mHistory were modified. This caused an inconsistent state that led to Windows arranged out of order. Updating both history stacks at the same time fixes this. Fixes bug 8244261. Change-Id: I50b675cbedf72f72e547919210187b7e30a4ccb8 --- .../com/android/server/am/ActivityStack.java | 69 +++++++++---------- .../server/wm/WindowManagerService.java | 1 + 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 30a7e23966618..e0b80150fe306 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -4850,44 +4850,8 @@ final class ActivityStack { * @return Returns true if the move completed, false if not. */ final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { - if (!newMoveTaskToBackLocked(task, reason)) { - return false; - } - - 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 = 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)); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + r + " to stack at " - + bottom, here); - } - mHistory.remove(pos); - mHistory.add(bottom, r); - bottom++; - } - pos++; - } - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } - - return true; - } - - final boolean newMoveTaskToBackLocked(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. @@ -4917,6 +4881,36 @@ final class ActivityStack { mTaskHistory.remove(tr); mTaskHistory.add(0, tr); + // BEGIN REGION TO REMOVE. + 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 = 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)); + if (DEBUG_ADD_REMOVE) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.i(TAG, "Removing and adding activity " + r + " to stack at " + + bottom, here); + } + mHistory.remove(pos); + mHistory.add(bottom, r); + bottom++; + } + pos++; + } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(true); + } + // END REGION TO REMOVE + if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { mService.mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); @@ -4929,6 +4923,7 @@ final class ActivityStack { AppTransition.TRANSIT_TASK_TO_BACK, false); } mService.mWindowManager.moveTaskToBottom(task); + if (VALIDATE_TOKENS) { validateAppTokensLocked(); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index c2213b3136cca..6a5ded0fe3044 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3158,6 +3158,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mismatch || iterator.hasNext()) { Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks); Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + iterator); + Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4)); } } } From 1384a6ccdb233303ed9308d1e14827dc8653a3bc Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Fri, 22 Feb 2013 03:59:25 +0000 Subject: [PATCH 08/76] Revert "Update histories simultaneously." Moving to master where it can be cherry-picked. This reverts commit ce361f5f88d19d192a3ec4a2c369a3050d53aaaa Change-Id: I0880a28c6fa5588ff2cab7995c90528f36bffd2e --- .../com/android/server/am/ActivityStack.java | 69 ++++++++++--------- .../server/wm/WindowManagerService.java | 1 - 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index e0b80150fe306..30a7e23966618 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -4850,8 +4850,44 @@ final class ActivityStack { * @return Returns true if the move completed, false if not. */ final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { - Slog.i(TAG, "moveTaskToBack: " + task); + if (!newMoveTaskToBackLocked(task, reason)) { + return false; + } + 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 = 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)); + if (DEBUG_ADD_REMOVE) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.i(TAG, "Removing and adding activity " + r + " to stack at " + + bottom, here); + } + mHistory.remove(pos); + mHistory.add(bottom, r); + bottom++; + } + pos++; + } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(true); + } + + return true; + } + + final boolean newMoveTaskToBackLocked(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. @@ -4881,36 +4917,6 @@ final class ActivityStack { mTaskHistory.remove(tr); mTaskHistory.add(0, tr); - // BEGIN REGION TO REMOVE. - 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 = 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)); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + r + " to stack at " - + bottom, here); - } - mHistory.remove(pos); - mHistory.add(bottom, r); - bottom++; - } - pos++; - } - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } - // END REGION TO REMOVE - if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { mService.mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); @@ -4923,7 +4929,6 @@ final class ActivityStack { AppTransition.TRANSIT_TASK_TO_BACK, false); } mService.mWindowManager.moveTaskToBottom(task); - if (VALIDATE_TOKENS) { validateAppTokensLocked(); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 6a5ded0fe3044..c2213b3136cca 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3158,7 +3158,6 @@ public class WindowManagerService extends IWindowManager.Stub if (mismatch || iterator.hasNext()) { Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks); Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + iterator); - Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4)); } } } From 1ede8fdbf21fffb2727f0e4869648e15b278259e Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Thu, 21 Feb 2013 20:00:58 -0800 Subject: [PATCH 09/76] Update histories simultaneously. In moveTaskToBackLocked calls that used mHistory were being made between the times that mTaskHistory and mHistory were modified. This caused an inconsistent state that led to Windows arranged out of order. Updating both history stacks at the same time fixes this. Fixes bug 8244261. Change-Id: I9669762ad39b06ab6d401122702b74969d4dc658 --- .../com/android/server/am/ActivityStack.java | 69 +++++++++---------- .../server/wm/WindowManagerService.java | 1 + 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 30a7e23966618..e0b80150fe306 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -4850,44 +4850,8 @@ final class ActivityStack { * @return Returns true if the move completed, false if not. */ final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { - if (!newMoveTaskToBackLocked(task, reason)) { - return false; - } - - 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 = 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)); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + r + " to stack at " - + bottom, here); - } - mHistory.remove(pos); - mHistory.add(bottom, r); - bottom++; - } - pos++; - } - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } - - return true; - } - - final boolean newMoveTaskToBackLocked(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. @@ -4917,6 +4881,36 @@ final class ActivityStack { mTaskHistory.remove(tr); mTaskHistory.add(0, tr); + // BEGIN REGION TO REMOVE. + 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 = 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)); + if (DEBUG_ADD_REMOVE) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.i(TAG, "Removing and adding activity " + r + " to stack at " + + bottom, here); + } + mHistory.remove(pos); + mHistory.add(bottom, r); + bottom++; + } + pos++; + } + if (VALIDATE_TASK_REPLACE) { + verifyActivityRecords(true); + } + // END REGION TO REMOVE + if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { mService.mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); @@ -4929,6 +4923,7 @@ final class ActivityStack { AppTransition.TRANSIT_TASK_TO_BACK, false); } mService.mWindowManager.moveTaskToBottom(task); + if (VALIDATE_TOKENS) { validateAppTokensLocked(); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index c2213b3136cca..6a5ded0fe3044 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -3158,6 +3158,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mismatch || iterator.hasNext()) { Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks); Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + iterator); + Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4)); } } } From 84e78a2759cb1511d079b28b7231ec24baa3d93b Mon Sep 17 00:00:00 2001 From: Justin Ho Date: Mon, 25 Feb 2013 16:08:27 -0800 Subject: [PATCH 10/76] Revert "Fix ListView is not scrolled properly with arrows" This reverts commit db68fac12daa2cf4a7568308995e218aed92728a. --- core/java/android/widget/ListView.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 4436fbbf2a9ea..69e3177e74b11 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -2429,9 +2429,7 @@ public class ListView extends AbsListView { View selectedView = getSelectedView(); int selectedPos = mSelectedPosition; - int nextSelectedPosition = (direction == View.FOCUS_DOWN) ? - lookForSelectablePosition(selectedPos + 1, true) : - lookForSelectablePosition(selectedPos - 1, false); + int nextSelectedPosition = lookForSelectablePositionOnScreen(direction); int amountToScroll = amountToScroll(direction, nextSelectedPosition); // if we are moving focus, we may OVERRIDE the default behavior @@ -2643,18 +2641,14 @@ public class ListView extends AbsListView { final int listBottom = getHeight() - mListPadding.bottom; final int listTop = mListPadding.top; - int numChildren = getChildCount(); + final int numChildren = getChildCount(); if (direction == View.FOCUS_DOWN) { int indexToMakeVisible = numChildren - 1; if (nextSelectedPosition != INVALID_POSITION) { indexToMakeVisible = nextSelectedPosition - mFirstPosition; } - while (numChildren <= indexToMakeVisible) { - // Child to view is not attached yet. - addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1); - numChildren++; - } + final int positionToMakeVisible = mFirstPosition + indexToMakeVisible; final View viewToMakeVisible = getChildAt(indexToMakeVisible); @@ -2688,12 +2682,6 @@ public class ListView extends AbsListView { if (nextSelectedPosition != INVALID_POSITION) { indexToMakeVisible = nextSelectedPosition - mFirstPosition; } - while (indexToMakeVisible < 0) { - // Child to view is not attached yet. - addViewAbove(getChildAt(0), mFirstPosition); - mFirstPosition--; - indexToMakeVisible = nextSelectedPosition - mFirstPosition; - } final int positionToMakeVisible = mFirstPosition + indexToMakeVisible; final View viewToMakeVisible = getChildAt(indexToMakeVisible); int goalTop = listTop; From 18f04b96ed4f2754ff157080329a78a8aca7390c Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Mon, 25 Feb 2013 16:19:24 -0800 Subject: [PATCH 11/76] Revert ActivityManager changes for tasks. DO NOT MERGE Keeping all activity=>task changes in master and removing them from jb-mr2. Revert "Update histories simultaneously." Revert "Add null check to setAppGroupId." Revert "Fix crashing bug in validator." Revert "Switch topRunning* and moveTaskTo*" Revert "Begin switch over to task based history." Revert "Reset and reuse Iterators and don't new() one." Revert "Remove AppWindowToken lists." Revert "Fix build." Revert "Remove unused App methods." Revert "Stop using AppToken movement and start using Task." Revert "Replace access to mAppTokens with AppTokenIterator" Revert "Refactor setAppOpVisibility implementation." Revert "Add AppWindowTokens to TaskList." Revert "Make ActivityStack.mHistory private." Revert "Migrate AppWindowToken lists into DisplayContent." Change-Id: I5722c9a4956dccb52864207e2967690bc58e4ebb --- core/java/android/view/IWindowManager.aidl | 3 + .../server/am/ActivityManagerService.java | 407 +++++++- .../com/android/server/am/ActivityRecord.java | 12 +- .../com/android/server/am/ActivityStack.java | 926 ++---------------- .../android/server/am/CompatModePackages.java | 16 +- .../server/am/PendingThumbnailsRecord.java | 4 +- .../com/android/server/am/TaskRecord.java | 74 +- .../com/android/server/wm/AppWindowToken.java | 4 - .../com/android/server/wm/DisplayContent.java | 214 +--- .../java/com/android/server/wm/TaskGroup.java | 31 - .../java/com/android/server/wm/TaskList.java | 35 - .../com/android/server/wm/WindowAnimator.java | 36 +- .../server/wm/WindowManagerService.java | 819 +++++++++------- .../com/android/server/wm/WindowState.java | 14 +- .../com/android/server/wm/WindowToken.java | 1 + .../tests/WindowManagerPermissionTests.java | 32 +- .../src/android/view/IWindowManagerImpl.java | 18 + 17 files changed, 1031 insertions(+), 1615 deletions(-) delete mode 100644 services/java/com/android/server/wm/TaskGroup.java delete mode 100644 services/java/com/android/server/wm/TaskList.java diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index e4ecb5c8063b5..f0c6241644d84 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -99,6 +99,9 @@ interface IWindowManager void startAppFreezingScreen(IBinder token, int configChanges); void stopAppFreezingScreen(IBinder token, boolean force); void removeAppToken(IBinder token); + void moveAppToken(int index, IBinder token); + void moveAppTokensToTop(in List tokens); + void moveAppTokensToBottom(in List tokens); // Re-evaluate the current orientation from the caller's state. // If there is a change, the new Configuration is returned and the diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 22af3d5b33576..f226683ac14d5 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -37,7 +37,6 @@ import dalvik.system.Zygote; import android.app.Activity; import android.app.ActivityManager; -import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.ActivityThread; @@ -197,7 +196,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; static final boolean DEBUG_MU = localLOGV || false; static final boolean DEBUG_IMMERSIVE = localLOGV || false; - static final boolean VALIDATE_TOKENS = true; + static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; // Control over CPU and battery monitoring. @@ -329,7 +328,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - private final ArrayList mRecentTasks = new ArrayList(); + final ArrayList mRecentTasks = new ArrayList(); public class PendingActivityExtras extends Binder implements Runnable { public final ActivityRecord activity; @@ -597,8 +596,13 @@ public final class ActivityManagerService extends ActivityManagerNative * List of PendingThumbnailsRecord objects of clients who are still * waiting to receive all of the thumbnails for a task. */ - final ArrayList mPendingThumbnails = - new ArrayList(); + final ArrayList mPendingThumbnails = new ArrayList(); + + /** + * List of HistoryRecord objects that have been finished and must + * still report back to a pending thumbnail receiver. + */ + final ArrayList mCancelledThumbnails = new ArrayList(); final ProviderMap mProviderMap; @@ -2838,8 +2842,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i= 0) { + mMainStack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, + null, "finish-heavy", true); + } } } @@ -2925,13 +2932,22 @@ public final class ActivityManagerService extends ActivityManagerNative } } - @Override public boolean willActivityBeVisible(IBinder token) { synchronized(this) { - return mMainStack.willActivityBeVisibleLocked(token); + int i; + for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); + if (r.appToken == token) { + return true; + } + if (r.fullscreen && !r.finishing) { + return false; + } + } + return true; } } - + public void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim) { synchronized(this) { @@ -3701,7 +3717,13 @@ public final class ActivityManagerService extends ActivityManagerNative } mWindowManager.closeSystemDialogs(reason); - mMainStack.closeSystemDialogsLocked(); + for (int 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) { + r.stack.finishActivityLocked(r, i, + Activity.RESULT_CANCELED, null, "close-sys", true); + } + } broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1, @@ -3908,12 +3930,37 @@ public final class ActivityManagerService extends ActivityManagerNative boolean didSomething = killPackageProcessesLocked(name, appId, userId, -100, callerWillRestart, true, doit, evenPersistent, name == null ? ("force stop user " + userId) : ("force stop " + name)); - - if (mMainStack.forceStopPackageLocked(name, doit, evenPersistent, userId)) { - if (!doit) { - return true; + + TaskRecord lastTask = null; + for (i=0; i getTasks(int maxNum, int flags, + public List getTasks(int maxNum, int flags, IThumbnailReceiver receiver) { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList(); - PendingThumbnailsRecord pending = new PendingThumbnailsRecord(receiver); + PendingThumbnailsRecord pending = null; + IApplicationThread topThumbnail = null; ActivityRecord topRecord = null; synchronized(this) { @@ -5654,19 +5701,88 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - topRecord = mMainStack.getTasksLocked(maxNum, receiver, pending, list); + int pos = mMainStack.mHistory.size()-1; + ActivityRecord next = + pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; + ActivityRecord top = null; + TaskRecord curTask = null; + int numActivities = 0; + int numRunning = 0; + while (pos >= 0 && maxNum > 0) { + final ActivityRecord r = next; + pos--; + next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; - if (!pending.pendingRecords.isEmpty()) { + // Initialize state for next task if needed. + if (top == null || + (top.state == ActivityState.INITIALIZING + && top.task == r.task)) { + top = r; + curTask = r.task; + numActivities = numRunning = 0; + } + + // Add 'r' into the current task. + numActivities++; + if (r.app != null && r.app.thread != null) { + numRunning++; + } + + if (localLOGV) Slog.v( + TAG, r.intent.getComponent().flattenToShortString() + + ": task=" + r.task); + + // If the next one is a different task, generate a new + // TaskInfo entry for what we have. + if (next == null || next.task != curTask) { + ActivityManager.RunningTaskInfo ci + = new ActivityManager.RunningTaskInfo(); + ci.id = curTask.taskId; + ci.baseActivity = r.intent.getComponent(); + ci.topActivity = top.intent.getComponent(); + if (top.thumbHolder != null) { + ci.description = top.thumbHolder.lastDescription; + } + ci.numActivities = numActivities; + ci.numRunning = numRunning; + //System.out.println( + // "#" + maxNum + ": " + " descr=" + ci.description); + if (ci.thumbnail == null && receiver != null) { + if (localLOGV) Slog.v( + TAG, "State=" + top.state + "Idle=" + top.idle + + " app=" + top.app + + " thr=" + (top.app != null ? top.app.thread : null)); + if (top.state == ActivityState.RESUMED + || top.state == ActivityState.PAUSING) { + if (top.idle && top.app != null + && top.app.thread != null) { + topRecord = top; + topThumbnail = top.app.thread; + } else { + top.thumbnailNeeded = true; + } + } + if (pending == null) { + pending = new PendingThumbnailsRecord(receiver); + } + pending.pendingRecords.add(top); + } + list.add(ci); + maxNum--; + top = null; + } + } + + if (pending != null) { mPendingThumbnails.add(pending); } } if (localLOGV) Slog.v(TAG, "We have pending thumbnails: " + pending); - if (topRecord != null) { + if (topThumbnail != null) { if (localLOGV) Slog.v(TAG, "Requesting top thumbnail"); try { - IApplicationThread topThumbnail = topRecord.app.thread; topThumbnail.requestThumbnail(topRecord.appToken); } catch (Exception e) { Slog.w(TAG, "Exception thrown when requesting thumbnail", e); @@ -5901,6 +6017,43 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } + private final int findAffinityTaskTopLocked(int startIndex, String affinity) { + int j; + TaskRecord startTask = ((ActivityRecord)mMainStack.mHistory.get(startIndex)).task; + TaskRecord jt = startTask; + + // First look backwards + for (j=startIndex-1; j>=0; j--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); + if (r.task != jt) { + jt = r.task; + if (affinity.equals(jt.affinity)) { + return j; + } + } + } + + // Now look forwards + final int N = mMainStack.mHistory.size(); + jt = startTask; + for (j=startIndex+1; j=0; i--) { + ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); + if (hr.task.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mMainStack.mUserLeaving = true; + } + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } + mMainStack.moveTaskToFrontLocked(hr.task, null, options); + return; + } } } finally { Binder.restoreCallingIdentity(origId); @@ -5970,7 +6135,7 @@ public final class ActivityManagerService extends ActivityManagerNative enforceNotIsolatedCaller("moveActivityTaskToBack"); synchronized(this) { final long origId = Binder.clearCallingIdentity(); - int taskId = mMainStack.getTaskForActivityLocked(token, !nonRoot); + int taskId = getTaskForActivityLocked(token, !nonRoot); if (taskId >= 0) { return mMainStack.moveTaskToBackLocked(taskId, null); } @@ -6000,10 +6165,27 @@ public final class ActivityManagerService extends ActivityManagerNative public int getTaskForActivity(IBinder token, boolean onlyRoot) { synchronized(this) { - return mMainStack.getTaskForActivityLocked(token, onlyRoot); + return getTaskForActivityLocked(token, onlyRoot); } } + int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { + final int N = mMainStack.mHistory.size(); + TaskRecord lastTask = null; + for (int i=0; i=0; i--) { + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); + if (r.app == app) { + Slog.w(TAG, " Force finishing activity " + + r.intent.getComponent().flattenToShortString()); + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "crashed", false); + } + } if (!app.persistent) { // We don't want to start this process again until the user // explicitly does so... but for persistent process, we really @@ -7999,7 +8192,33 @@ public final class ActivityManagerService extends ActivityManagerNative } mMainStack.resumeTopActivityLocked(null); } else { - mMainStack.finishTopRunningActivityLocked(app); + ActivityRecord r = mMainStack.topRunningActivityLocked(null); + if (r != null && 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 = mMainStack.indexOfActivityLocked(r); + r.stack.finishActivityLocked(r, index, + Activity.RESULT_CANCELED, null, "crashed", false); + // Also terminate any 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 = (ActivityRecord)mMainStack.mHistory.get(index); + if (r.state == ActivityState.RESUMED + || r.state == ActivityState.PAUSING + || r.state == ActivityState.PAUSED) { + if (!r.isHomeActivity || mHomeProcess != r.app) { + Slog.w(TAG, " Force finishing activity " + + r.intent.getComponent().flattenToShortString()); + r.stack.finishActivityLocked(r, index, + Activity.RESULT_CANCELED, null, "crashed", false); + } + } + } + } } // Bump up the crash count of any services currently running in the proc. @@ -9018,7 +9237,8 @@ public final class ActivityManagerService extends ActivityManagerNative int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)"); pw.println(" Main stack:"); - mMainStack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage); + dumpHistoryList(fd, pw, mMainStack.mHistory, " ", "Hist", true, !dumpAll, dumpClient, + dumpPackage); pw.println(" "); pw.println(" Running activities (most recent first):"); dumpHistoryList(fd, pw, mMainStack.mLRUActivities, " ", "Run", false, !dumpAll, false, @@ -9548,10 +9768,32 @@ public final class ActivityManagerService extends ActivityManagerNative */ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll) { - ArrayList activities; - - synchronized (this) { - activities = mMainStack.getDumpActivitiesLocked(name); + ArrayList activities = new ArrayList(); + + if ("all".equals(name)) { + synchronized (this) { + for (ActivityRecord r1 : (ArrayList)mMainStack.mHistory) { + activities.add(r1); + } + } + } else if ("top".equals(name)) { + synchronized (this) { + final int N = mMainStack.mHistory.size(); + if (N > 0) { + activities.add((ActivityRecord)mMainStack.mHistory.get(N-1)); + } + } + } else { + ItemMatcher matcher = new ItemMatcher(); + matcher.build(name); + + synchronized (this) { + for (ActivityRecord r1 : (ArrayList)mMainStack.mHistory) { + if (matcher.match(r1, r1.intent.getComponent())) { + activities.add(r1); + } + } + } } if (activities.size() <= 0) { @@ -9803,7 +10045,7 @@ public final class ActivityManagerService extends ActivityManagerNative return needSep; } - static final void dumpHistoryList(FileDescriptor fd, PrintWriter pw, List list, + private static final void dumpHistoryList(FileDescriptor fd, PrintWriter pw, List list, String prefix, String label, boolean complete, boolean brief, boolean client, String dumpPackage) { TaskRecord lastTask = null; @@ -12341,14 +12583,95 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode, Intent resultData) { + ComponentName dest = destIntent.getComponent(); synchronized (this) { ActivityRecord srec = ActivityRecord.forToken(token); if (srec == null) { return false; } - ActivityStack stack = srec.stack; - return stack.navigateUpToLocked(srec, destIntent, resultCode, resultData); + ArrayList history = srec.stack.mHistory; + final int start = history.indexOf(srec); + if (start < 0) { + // Current activity is not in history stack; do nothing. + return false; + } + int finishTo = start - 1; + ActivityRecord parent = null; + boolean foundParentInTask = false; + if (dest != null) { + TaskRecord tr = srec.task; + for (int i = start - 1; i >= 0; i--) { + ActivityRecord r = history.get(i); + if (tr != r.task) { + // Couldn't find parent in the same task; stop at the one above this. + // (Root of current task; in-app "home" behavior) + // Always at least finish the current activity. + finishTo = Math.min(start - 1, i + 1); + parent = history.get(finishTo); + break; + } else if (r.info.packageName.equals(dest.getPackageName()) && + r.info.name.equals(dest.getClassName())) { + finishTo = i; + parent = r; + foundParentInTask = true; + break; + } + } + } + + if (mController != null) { + ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0); + if (next != null) { + // ask watcher if this is allowed + boolean resumeOK = true; + try { + resumeOK = mController.activityResuming(next.packageName); + } catch (RemoteException e) { + mController = null; + } + + if (!resumeOK) { + return false; + } + } + } + final long origId = Binder.clearCallingIdentity(); + for (int i = start; i > finishTo; i--) { + ActivityRecord r = history.get(i); + mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData, + "navigate-up", true); + // Only return the supplied result for the first activity finished + resultCode = Activity.RESULT_CANCELED; + resultData = null; + } + + if (parent != null && foundParentInTask) { + final int parentLaunchMode = parent.info.launchMode; + final int destIntentFlags = destIntent.getFlags(); + if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || + parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || + parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || + (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent); + } else { + try { + ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( + destIntent.getComponent(), 0, srec.userId); + int res = mMainStack.startActivityLocked(srec.app.thread, destIntent, + null, aInfo, parent.appToken, null, + 0, -1, parent.launchedFromUid, parent.launchedFromPackage, + 0, null, true, null); + foundParentInTask = res == ActivityManager.START_SUCCESS; + } catch (RemoteException e) { + foundParentInTask = false; + } + mMainStack.requestFinishActivityLocked(parent.appToken, resultCode, + resultData, "navigate-up", true); + } + } + Binder.restoreCallingIdentity(origId); + return foundParentInTask; } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index a2f337296deb7..cde17c9b39bae 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -22,7 +22,6 @@ import com.android.server.am.ActivityStack.ActivityState; import android.app.Activity; import android.app.ActivityOptions; -import android.app.ResultInfo; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -96,9 +95,9 @@ final class ActivityRecord { 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 + ArrayList results; // pending ActivityResult objs we have received HashSet> pendingResults; // all pending intents for this act - ArrayList newIntents; // any pending new intents for single-top mode + ArrayList newIntents; // any pending new intents for single-top mode ActivityOptions pendingOptions; // most recently given options HashSet connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. @@ -471,8 +470,6 @@ final class ActivityRecord { void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { if (inHistory && !finishing) { if (task != null) { - // TODO: If this is the last ActivityRecord in task, remove from ActivityStack. - task.removeActivity(this); task.numActivities--; } if (newTask != null) { @@ -507,7 +504,6 @@ final class ActivityRecord { inHistory = false; if (task != null && !finishing) { task.numActivities--; - task = null; } clearOptionsLocked(); } @@ -542,7 +538,7 @@ final class ActivityRecord { ActivityResult r = new ActivityResult(from, resultWho, requestCode, resultCode, resultData); if (results == null) { - results = new ArrayList(); + results = new ArrayList(); } results.add(r); } @@ -953,8 +949,6 @@ final class ActivityRecord { StringBuilder sb = new StringBuilder(128); sb.append("ActivityRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" t"); - sb.append(task.taskId); sb.append(" u"); sb.append(userId); sb.append(' '); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index e0b80150fe306..526b24f052b3c 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -21,23 +21,18 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.os.BatteryStatsImpl; -import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.ActivityManagerService.PendingActivityLaunch; import com.android.server.wm.AppTransition; -import com.android.server.wm.TaskGroup; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AppGlobals; -import android.app.IActivityController; import android.app.IActivityManager; -import android.app.IThumbnailReceiver; import android.app.IThumbnailRetriever; import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.ResultInfo; -import android.app.ActivityManager.RunningTaskInfo; import android.app.IActivityManager.WaitResult; import android.content.ComponentName; import android.content.Context; @@ -54,7 +49,6 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.os.Binder; import android.os.Bundle; -import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -67,12 +61,9 @@ import android.os.UserHandle; import android.util.EventLog; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; import android.view.Display; -import java.io.FileDescriptor; import java.io.IOException; -import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; @@ -100,8 +91,7 @@ final class ActivityStack { static final boolean DEBUG_APP = false; static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS; - static final boolean VALIDATE_TASK_REPLACE = true; - + // How long we wait until giving up on the last activity telling us it // is idle. static final int IDLE_TIMEOUT = 10*1000; @@ -140,7 +130,7 @@ final class ActivityStack { // 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, @@ -157,28 +147,17 @@ final class ActivityStack { final boolean mMainStack; final Context mContext; - + /** * The back history of all previous (and possibly still - * running) activities. It contains #ActivityRecord objects. + * running) activities. It contains HistoryRecord objects. */ - private final ArrayList mHistory = new ArrayList(); - - /** - * The back history of all previous (and possibly still - * running) activities. It contains #TaskRecord objects. - */ - private ArrayList mTaskHistory = new ArrayList(); - - /** - * Mapping from taskId to TaskRecord - */ - private SparseArray mTaskIdToTaskRecord = new SparseArray(); + final ArrayList mHistory = new ArrayList(); /** * Used for validating app tokens with window manager. */ - final ArrayList mValidateAppTokens = new ArrayList(); + final ArrayList mValidateAppTokens = new ArrayList(); /** * List of running activities, sorted by recent usage. @@ -310,12 +289,6 @@ final class ActivityStack { private ActivityRecord mLastScreenshotActivity = null; private Bitmap mLastScreenshotBitmap = null; - /** - * List of ActivityRecord objects that have been finished and must - * still report back to a pending thumbnail receiver. - */ - private final ArrayList mCancelledThumbnails = new ArrayList(); - int mThumbnailWidth = -1; int mThumbnailHeight = -1; @@ -345,8 +318,6 @@ final class ActivityStack { final Handler mHandler; - String mLastHistoryModifier; - final class ActivityStackHandler extends Handler { //public Handler() { // if (localLOGV) Slog.v(TAG, "Handler started!"); @@ -473,66 +444,26 @@ final class ActivityStack { } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { - ActivityRecord newAr = newTopRunningActivityLocked(notTop); - int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); if (!r.finishing && r != notTop && okToShow(r)) { - if (VALIDATE_TASK_REPLACE && newAr != r) logHistories( - "topRunningActivityLocked", true); return r; } i--; } - if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, - "topRunningActivityLocked: mismatch: newAr!=null"); - return null; - } - - final ActivityRecord newTopRunningActivityLocked(ActivityRecord notTop) { - for (int i = mTaskHistory.size() - 1; i >= 0; --i) { - final TaskRecord task = mTaskHistory.get(i); - final ArrayList activities = task.mActivities; - for (int j = activities.size() - 1; j >= 0; --j) { - ActivityRecord r = activities.get(j); - if (!r.finishing && r != notTop && okToShow(r)) { - return r; - } - } - } return null; } final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { - ActivityRecord newAr = newTopRunningNonDelayedActivityLocked(notTop); - int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) { - if (VALIDATE_TASK_REPLACE && newAr != r) Slog.w(TAG, - "topRunningNonDelayedActivityLocked: mismatch: newAr=" + newAr + " r=" + r); return r; } i--; } - if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, - "topRunningNonDelayedActivityLocked: mismatch: newAr!=null"); - return null; - } - - final ActivityRecord newTopRunningNonDelayedActivityLocked(ActivityRecord notTop) { - for (int i = mTaskHistory.size() - 1; i >= 0; --i) { - final TaskRecord task = mTaskHistory.get(i); - final ArrayList activities = task.mActivities; - for (int j = activities.size() - 1; j >= 0; --j) { - ActivityRecord r = activities.get(j); - if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) { - return r; - } - } - } return null; } @@ -546,111 +477,35 @@ final class ActivityStack { * @return Returns the HistoryRecord of the next activity on the stack. */ final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { - ActivityRecord newAr = newTopRunningActivityLocked(token, taskId); - int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); // Note: the taskId check depends on real taskId fields being non-zero if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId) && okToShow(r)) { - if (VALIDATE_TASK_REPLACE && newAr != r) Slog.w(TAG, - "topRunningActivityLocked(token): mismatch: newAr=" + newAr + " r=" + r); return r; } i--; } - if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, - "topRunningActivityLocked(token): mismatch: newAr!=null"); return null; } - final ActivityRecord newTopRunningActivityLocked(IBinder token, int taskId) { - for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { - TaskRecord task = mTaskHistory.get(taskNdx); - if (task.taskId == taskId) { - continue; - } - ArrayList activities = task.mActivities; - for (int i = activities.size() - 1; i >= 0; --i) { - final ActivityRecord r = activities.get(i); - // Note: the taskId check depends on real taskId fields being non-zero - if (!r.finishing && (token != r.appToken) && okToShow(r)) { - return r; - } - } - } - return null; - } - - private final int indexOfTokenLocked(IBinder token) { + final int indexOfTokenLocked(IBinder token) { return mHistory.indexOf(ActivityRecord.forToken(token)); } - final ActivityRecord isInStackLocked(IBinder token) { - ActivityRecord newAr = newIsInStackLocked(token); + final int indexOfActivityLocked(ActivityRecord r) { + return mHistory.indexOf(r); + } + final ActivityRecord isInStackLocked(IBinder token) { ActivityRecord r = ActivityRecord.forToken(token); if (mHistory.contains(r)) { - if (VALIDATE_TASK_REPLACE && newAr != r) Slog.w(TAG, - "isInStackLocked: mismatch: newAr=" + newAr + " r=" + r); return r; } - if (VALIDATE_TASK_REPLACE && newAr != null) Slog.w(TAG, - "isInStackLocked: mismatch: newAr!=null"); return null; } - final ActivityRecord newIsInStackLocked(IBinder token) { - final ActivityRecord r = ActivityRecord.forToken(token); - if (r != null) { - final TaskRecord task = r.task; - if (mTaskHistory.contains(task) && task.mActivities.contains(r)) { - return r; - } - } - return null; - } - - int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { - int newTaskId = newGetTaskForActivityLocked(token, onlyRoot); - - TaskRecord lastTask = null; - final int N = mHistory.size(); - for (int i = 0; i < N; i++) { - ActivityRecord r = mHistory.get(i); - if (r.appToken == token) { - if (!onlyRoot || lastTask != r.task) { - if (VALIDATE_TASK_REPLACE && newTaskId != r.task.taskId) Slog.w(TAG, - "getTaskForActivityLocked: mismatch: new=" + newTaskId - + " taskId=" + r.task.taskId); - return r.task.taskId; - } - if (VALIDATE_TASK_REPLACE && newTaskId != -1) Slog.w(TAG, - "getTaskForActivityLocked: mismatch: newTaskId=" + newTaskId + " not -1."); - return -1; - } - lastTask = r.task; - } - - if (VALIDATE_TASK_REPLACE && newTaskId != -1) Slog.w(TAG, - "getTaskForActivityLocked: mismatch at end: newTaskId=" + newTaskId + " not -1."); - return -1; - } - - int newGetTaskForActivityLocked(IBinder token, boolean onlyRoot) { - final ActivityRecord r = ActivityRecord.forToken(token); - if (r == null) { - return -1; - } - final TaskRecord task = r.task; - switch (task.mActivities.indexOf(r)) { - case -1: return -1; - case 0: return task.taskId; - default: return onlyRoot ? -1 : task.taskId; - } - } - private final boolean updateLRUListLocked(ActivityRecord r) { final boolean hadit = mLRUActivities.remove(r); mLRUActivities.add(r); @@ -743,29 +598,17 @@ final class ActivityStack { * @return whether there are any activities for the specified user. */ final boolean switchUserLocked(int userId, UserStartedState uss) { - if (VALIDATE_TOKENS) { - validateAppTokensLocked(); - } - final boolean newResult = newSwitchUserLocked(userId, uss); - mCurrentUser = userId; mStartingUsers.add(uss); // Only one activity? Nothing to do... - if (mHistory.size() < 2) { - if (VALIDATE_TASK_REPLACE && newResult) Slog.w(TAG, - "switchUserLocked: mismatch: " + newResult + " " + false); + if (mHistory.size() < 2) return false; - } boolean haveActivities = false; // Check if the top activity is from the new user. ActivityRecord top = mHistory.get(mHistory.size() - 1); - if (top.userId == userId) { - if (VALIDATE_TASK_REPLACE && !newResult) Slog.w(TAG, - "switchUserLocked: mismatch: " + newResult + " " + true); - return true; - } + if (top.userId == userId) return true; // Otherwise, move the user's activities to the top. int N = mHistory.size(); int i = 0; @@ -782,44 +625,7 @@ final class ActivityStack { } } // Transition from the old top to the new top - if (VALIDATE_TASK_REPLACE) Slog.w(TAG, - "switchUserLocked: calling resumeTopActivity " + top); resumeTopActivityLocked(top); - if (VALIDATE_TASK_REPLACE && (newResult != haveActivities)) Slog.w(TAG, - "switchUserLocked: mismatch: " + newResult + " " + haveActivities); - return haveActivities; - } - - /* - * Move the activities around in the stack to bring a user to the foreground. - * @return whether there are any activities for the specified user. - */ - final boolean newSwitchUserLocked(int userId, UserStartedState uss) { -// mStartingUsers.add(uss); - if (mCurrentUser == userId) { - return true; - } - mCurrentUser = userId; - - // Move userId's tasks to the top. - boolean haveActivities = false; - TaskRecord task = null; - int index = mTaskHistory.size(); - for (int i = 0; i < index; ++i) { - task = mTaskHistory.get(i); - if (task.userId == userId) { - haveActivities = true; - mTaskHistory.remove(i); - mTaskHistory.add(task); - --index; - } - } - - // task is now the original topmost TaskRecord. Transition from the old top to the new top. - ActivityRecord top = task != null ? task.getTopActivity() : null; - if (VALIDATE_TASK_REPLACE) Slog.w(TAG, - "newSwitchUserLocked: would call resumeTopActivity " + top); -// resumeTopActivityLocked(top); return haveActivities; } @@ -903,13 +709,7 @@ final class ActivityStack { try { profileFd = profileFd.dup(); } catch (IOException e) { - if (profileFd != null) { - try { - profileFd.close(); - } catch (IOException o) { - } - profileFd = null; - } + profileFd = null; } } app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, @@ -1057,9 +857,6 @@ final class ActivityStack { mGoingToSleep.release(); } // Ensure activities are no longer sleeping. - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord r = mHistory.get(i); r.setSleeping(false); @@ -1104,9 +901,6 @@ final class ActivityStack { // Make sure any stopped but visible activities are now sleeping. // This ensures that the activity's onStop() is called. - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord r = mHistory.get(i); if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { @@ -1251,9 +1045,6 @@ final class ActivityStack { ActivityRecord r = null; synchronized (mService) { - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } int index = indexOfTokenLocked(token); if (index >= 0) { r = mHistory.get(index); @@ -1271,9 +1062,6 @@ final class ActivityStack { ActivityRecord r = null; synchronized (mService) { - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } int index = indexOfTokenLocked(token); if (index >= 0) { r = mHistory.get(index); @@ -1486,9 +1274,6 @@ final class ActivityStack { // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } final int count = mHistory.size(); int i = count-1; while (mHistory.get(i) != top) { @@ -1937,7 +1722,7 @@ final class ActivityStack { try { // Deliver all pending results. - ArrayList a = next.results; + ArrayList a = next.results; if (a != null) { final int N = a.size(); if (!next.finishing && N > 0) { @@ -2025,21 +1810,8 @@ final class ActivityStack { return true; } - /** Temporary until startActivityLocked is rewritten for tasks. */ - private int convertAddPos(int addPos) { - final int taskId = mHistory.get(addPos).task.taskId; - addPos--; - int taskOffset = 0; - while (addPos >= 0 && taskId == mHistory.get(addPos).task.taskId) { - ++taskOffset; - --addPos; - } - return taskOffset; - } - private final void startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition, Bundle options) { - mLastHistoryModifier = "startActivityLocked"; final int NH = mHistory.size(); int addPos = -1; @@ -2064,15 +1836,13 @@ final class ActivityStack { Slog.i(TAG, "Adding activity " + r + " to stack at " + addPos, here); } - r.task.addActivityToTop(r); mHistory.add(addPos, r); r.putInHistory(); - mService.mWindowManager.addAppToken(convertAddPos(addPos), r.appToken, - r.task.taskId, r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, + r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); if (VALIDATE_TOKENS) { validateAppTokensLocked(); - verifyActivityRecords(true); } ActivityOptions.abort(options); return; @@ -2105,15 +1875,9 @@ final class ActivityStack { here.fillInStackTrace(); Slog.i(TAG, "Adding activity " + r + " to stack at " + addPos, here); } - r.task.addActivityToTop(r); mHistory.add(addPos, r); r.putInHistory(); r.frontOfTask = newTask; - if (VALIDATE_TASK_REPLACE) { - if (verifyActivityRecords(false)) { - Slog.w(TAG, "startActivityLocked: addPos=" + addPos); - } - } 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 @@ -2139,8 +1903,8 @@ final class ActivityStack { mNoAnimActivities.remove(r); } r.updateOptionsLocked(options); - mService.mWindowManager.addAppToken(convertAddPos(addPos), - r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, + mService.mWindowManager.addAppToken( + addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); boolean doShow = true; if (newTask) { @@ -2178,7 +1942,7 @@ final class ActivityStack { } 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(convertAddPos(addPos), r.appToken, r.task.taskId, + mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); ActivityOptions.abort(options); @@ -2190,27 +1954,13 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - if (VALIDATE_TASK_REPLACE) { - if (verifyActivityRecords(true)) { - Slog.w(TAG, "startActivityLocked: addPos=" + addPos); - } - } } final void validateAppTokensLocked() { mValidateAppTokens.clear(); mValidateAppTokens.ensureCapacity(mHistory.size()); - int taskId = Integer.MIN_VALUE; - TaskGroup task = null; for (int i=0; i 0 @@ -2318,20 +2066,19 @@ final class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.setTask(createTaskRecord(mService.mCurTask, target.info, null, - false), null, false); + target.setTask(new TaskRecord(mService.mCurTask, target.info, null), + null, false); 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.appToken, target.task.taskId); + mService.mWindowManager.setAppGroupId(target.appToken, task.taskId); if (replyChainEnd < 0) { replyChainEnd = targetI; } int dstPos = 0; ThumbnailHolder curThumbHolder = target.thumbHolder; boolean gotOptions = !canMoveOptions; - final int taskId = target.task.taskId; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { p = mHistory.get(srcPos); if (p.finishing) { @@ -2356,15 +2103,14 @@ final class ActivityStack { } mHistory.remove(srcPos); mHistory.add(dstPos, p); - mService.mWindowManager.setAppGroupId(p.appToken, taskId); + mService.mWindowManager.moveAppToken(dstPos, p.appToken); + mService.mWindowManager.setAppGroupId(p.appToken, p.task.taskId); dstPos++; + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } i++; } - rebuildTaskHistory(); - mService.mWindowManager.moveTaskToBottom(taskId); - if (VALIDATE_TOKENS) { - validateAppTokensLocked(); - } if (taskTop == p) { taskTop = below; } @@ -2384,7 +2130,8 @@ final class ActivityStack { // like these are all in the reply chain. replyChainEnd = targetI+1; while (replyChainEnd < mHistory.size() && - (mHistory.get(replyChainEnd)).task == task) { + (mHistory.get( + replyChainEnd)).task == task) { replyChainEnd++; } replyChainEnd--; @@ -2481,7 +2228,6 @@ final class ActivityStack { if (replyChainEnd < 0) { replyChainEnd = targetI; } - final int taskId = task.taskId; if (DEBUG_TASKS) Slog.v(TAG, "Reparenting task at index " + targetI + " to " + replyChainEnd); for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { @@ -2507,13 +2253,11 @@ final class ActivityStack { if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + " from " + srcPos + " to " + lastReparentPos + " in to resetting task " + task); - mService.mWindowManager.setAppGroupId(p.appToken, taskId); - } - rebuildTaskHistory(); - // TODO: This is wrong because it doesn't take lastReparentPos into account. - mService.mWindowManager.moveTaskToTop(taskId); - if (VALIDATE_TOKENS) { - validateAppTokensLocked(); + mService.mWindowManager.moveAppToken(lastReparentPos, p.appToken); + mService.mWindowManager.setAppGroupId(p.appToken, p.task.taskId); + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } } replyChainEnd = -1; @@ -2558,9 +2302,6 @@ final class ActivityStack { } } - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } return taskTop; } @@ -2627,7 +2368,7 @@ final class ActivityStack { if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { if (!ret.finishing) { - int index = mHistory.indexOf(ret); + int index = indexOfTokenLocked(ret.appToken); if (index >= 0) { finishActivityLocked(ret, index, Activity.RESULT_CANCELED, null, "clear", false); @@ -2725,7 +2466,6 @@ final class ActivityStack { */ private final ActivityRecord moveActivityToFrontLocked(int where) { ActivityRecord newTop = mHistory.remove(where); - newMoveActivityToFrontLocked(newTop); int top = mHistory.size(); ActivityRecord oldTop = mHistory.get(top-1); if (DEBUG_ADD_REMOVE) { @@ -2735,17 +2475,6 @@ final class ActivityStack { + top, here); } mHistory.add(top, newTop); - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } - return newTop; - } - - private final ActivityRecord newMoveActivityToFrontLocked(ActivityRecord newTop) { - final TaskRecord task = newTop.task; - ActivityRecord oldTop = task.getTopActivity(); - task.mActivities.remove(newTop); - task.mActivities.add(newTop); oldTop.frontOfTask = false; newTop.frontOfTask = true; return newTop; @@ -2756,7 +2485,6 @@ final class ActivityStack { String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity) { - mLastHistoryModifier = "startActivityLocked(IApplicationThread)"; int err = ActivityManager.START_SUCCESS; @@ -3239,7 +2967,7 @@ final class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - r.setTask(createTaskRecord(mService.mCurTask, r.info, intent, true), null, true); + r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { @@ -3303,7 +3031,7 @@ final class ActivityStack { N > 0 ? mHistory.get(N-1) : null; r.setTask(prev != null ? prev.task - : createTaskRecord(mService.mCurTask, r.info, intent, true), null, true); + : new TaskRecord(mService.mCurTask, r.info, intent), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -3795,7 +3523,7 @@ final class ActivityStack { } // Get the activity record. - int index = mHistory.indexOf(r); + int index = indexOfActivityLocked(r); if (index >= 0) { res = r; @@ -3851,9 +3579,9 @@ final class ActivityStack { finishes = new ArrayList(mFinishingActivities); mFinishingActivities.clear(); } - if ((NT=mCancelledThumbnails.size()) > 0) { - thumbnails = new ArrayList(mCancelledThumbnails); - mCancelledThumbnails.clear(); + if ((NT=mService.mCancelledThumbnails.size()) > 0) { + thumbnails = new ArrayList(mService.mCancelledThumbnails); + mService.mCancelledThumbnails.clear(); } if (mMainStack) { @@ -3881,7 +3609,7 @@ final class ActivityStack { // Stop any activities that are scheduled to do so but have been // waiting for the next one to start. for (i=0; i=0; i--) { - ActivityRecord r = mHistory.get(i); + ActivityRecord r = (ActivityRecord)mHistory.get(i); if (r.resultTo == self && r.requestCode == requestCode) { if ((r.resultWho == null && resultWho == null) || (r.resultWho != null && r.resultWho.equals(resultWho))) { @@ -3969,36 +3697,6 @@ final class ActivityStack { mService.updateOomAdjLocked(); } - final void finishTopRunningActivityLocked(ProcessRecord app) { - ActivityRecord r = topRunningActivityLocked(null); - if (r != null && 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 = mHistory.indexOf(r); - r.stack.finishActivityLocked(r, index, - Activity.RESULT_CANCELED, null, "crashed", false); - // Also terminate any 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 = mHistory.get(index); - if (r.state == ActivityState.RESUMED - || r.state == ActivityState.PAUSING - || r.state == ActivityState.PAUSED) { - if (!r.isHomeActivity || mService.mHomeProcess != r.app) { - Slog.w(TAG, " Force finishing activity " - + r.intent.getComponent().flattenToShortString()); - r.stack.finishActivityLocked(r, index, - Activity.RESULT_CANCELED, null, "crashed", false); - } - } - } - } - } - final boolean finishActivityAffinityLocked(IBinder token) { int index = indexOfTokenLocked(token); if (DEBUG_RESULTS) Slog.v( @@ -4053,19 +3751,6 @@ final class ActivityStack { r.icicle = null; } - /** - * @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 resultCode, Intent resultData, String reason, boolean oomAdj) { - int index = mHistory.indexOf(r); - if (index >= 0) { - return finishActivityLocked(r, index, resultCode, resultData, reason, false, oomAdj); - } - return false; - } - /** * @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. @@ -4119,7 +3804,7 @@ final class ActivityStack { // 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); + mService.mCancelledThumbnails.add(r); } if (immediate) { @@ -4162,7 +3847,7 @@ final class ActivityStack { private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) { - final int index = mHistory.indexOf(r); + final int index = indexOfActivityLocked(r); if (index < 0) { return null; } @@ -4218,102 +3903,16 @@ final class ActivityStack { resumeTopActivityLocked(null); } return activityRemoved ? 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); } - - // 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; } - final boolean navigateUpToLocked(ActivityRecord srec, Intent destIntent, int resultCode, - Intent resultData) { - final int start = mHistory.indexOf(srec); - if (start < 0) { - // Current activity is not in history stack; do nothing. - return false; - } - int finishTo = start - 1; - ActivityRecord parent = null; - boolean foundParentInTask = false; - ComponentName dest = destIntent.getComponent(); - if (dest != null) { - TaskRecord tr = srec.task; - for (int i = start - 1; i >= 0; i--) { - ActivityRecord r = mHistory.get(i); - if (tr != r.task) { - // Couldn't find parent in the same task; stop at the one above this. - // (Root of current task; in-app "home" behavior) - // Always at least finish the current activity. - finishTo = Math.min(start - 1, i + 1); - parent = mHistory.get(finishTo); - break; - } else if (r.info.packageName.equals(dest.getPackageName()) && - r.info.name.equals(dest.getClassName())) { - finishTo = i; - parent = r; - foundParentInTask = true; - break; - } - } - } - - IActivityController controller = mService.mController; - if (controller != null) { - ActivityRecord next = topRunningActivityLocked(srec.appToken, 0); - if (next != null) { - // ask watcher if this is allowed - boolean resumeOK = true; - try { - resumeOK = controller.activityResuming(next.packageName); - } catch (RemoteException e) { - mService.mController = null; - } - - if (!resumeOK) { - return false; - } - } - } - final long origId = Binder.clearCallingIdentity(); - for (int i = start; i > finishTo; i--) { - ActivityRecord r = mHistory.get(i); - requestFinishActivityLocked(r.appToken, resultCode, resultData, - "navigate-up", true); - // Only return the supplied result for the first activity finished - resultCode = Activity.RESULT_CANCELED; - resultData = null; - } - - if (parent != null && foundParentInTask) { - final int parentLaunchMode = parent.info.launchMode; - final int destIntentFlags = destIntent.getFlags(); - if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || - parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || - parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || - (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent); - } else { - try { - ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( - destIntent.getComponent(), 0, srec.userId); - int res = startActivityLocked(srec.app.thread, destIntent, - null, aInfo, parent.appToken, null, - 0, -1, parent.launchedFromUid, parent.launchedFromPackage, - 0, null, true, null); - foundParentInTask = res == ActivityManager.START_SUCCESS; - } catch (RemoteException e) { - foundParentInTask = false; - } - requestFinishActivityLocked(parent.appToken, resultCode, - resultData, "navigate-up", true); - } - } - Binder.restoreCallingIdentity(origId); - return foundParentInTask; - } /** * Perform the common clean-up of an activity record. This is called both * as part of destroyActivityLocked() (when destroying the client-side @@ -4365,7 +3964,7 @@ final class ActivityStack { // 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); + mService.mCancelledThumbnails.add(r); } // Get rid of any pending idle timeouts. @@ -4388,10 +3987,6 @@ final class ActivityStack { here.fillInStackTrace(); Slog.i(TAG, "Removing activity " + r + " from stack"); } - final TaskRecord task = r.task; - if (task != null) { - task.removeActivity(r); - } mHistory.remove(r); r.takeFromHistory(); removeTimeoutsForActivityLocked(r); @@ -4576,7 +4171,7 @@ final class ActivityStack { mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); } - int index = mHistory.indexOf(r); + int index = indexOfActivityLocked(r); if (index >= 0) { if (r.state == ActivityState.DESTROYING) { cleanUpActivityLocked(r, true, false); @@ -4590,15 +4185,15 @@ final class ActivityStack { } } - private void removeHistoryRecordsForAppLocked(ArrayList list, - ProcessRecord app, String listName) { + private void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app, + String listName) { int i = list.size(); if (DEBUG_CLEANUP) Slog.v( TAG, "Removing app " + app + " from list " + listName + " with " + i + " entries"); while (i > 0) { i--; - ActivityRecord r = list.get(i); + ActivityRecord r = (ActivityRecord)list.get(i); if (DEBUG_CLEANUP) Slog.v(TAG, "Record #" + i + " " + r); if (r.app == app) { if (DEBUG_CLEANUP) Slog.v(TAG, "---> REMOVING this entry!"); @@ -4624,7 +4219,7 @@ final class ActivityStack { TAG, "Removing app " + app + " from history with " + i + " entries"); while (i > 0) { i--; - ActivityRecord r = mHistory.get(i); + ActivityRecord r = (ActivityRecord)mHistory.get(i); if (DEBUG_CLEANUP) Slog.v( TAG, "Record #" + i + " " + r + ": app=" + r.app); if (r.app == app) { @@ -4692,7 +4287,6 @@ final class ActivityStack { * of the stack. */ final void moveHomeToFrontLocked() { - newMoveHomeToFrontLocked(); TaskRecord homeTask = null; for (int i=mHistory.size()-1; i>=0; i--) { ActivityRecord hr = mHistory.get(i); @@ -4702,23 +4296,6 @@ final class ActivityStack { } } if (homeTask != null) { -// moveTaskToFrontLocked(homeTask, null, null); - } - } - - final void newMoveHomeToFrontLocked() { - TaskRecord homeTask = null; - for (int taskNdx = mTaskHistory.size() - 1; homeTask == null && taskNdx >= 0; --taskNdx) { - final ArrayList activities = mTaskHistory.get(taskNdx).mActivities; - for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { - final ActivityRecord r = activities.get(activityNdx); - if (r.isHomeActivity) { - homeTask = r.task; - break; - } - } - } - if (homeTask != null) { moveTaskToFrontLocked(homeTask, null, null); } } @@ -4735,39 +4312,32 @@ final class ActivityStack { mService.mWindowManager.prepareAppTransition(transit, false); } - final boolean findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) { - for (int i = mHistory.size() - 1; i >= 0; i--) { - ActivityRecord hr = mHistory.get(i); - if (hr.task.taskId == taskId) { - if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mUserLeaving = true; - } - if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just move the home task to the top first. - moveHomeToFrontLocked(); - } - moveTaskToFrontLocked(hr.task, null, options); - return true; - } - } - return false; - } - final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) { + if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); final int task = tr.taskId; int top = mHistory.size()-1; if (top < 0 || (mHistory.get(top)).task.taskId == task) { // nothing to do! + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + ActivityOptions.abort(options); + } else { + updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); + } 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. - int pos = top; while (pos >= 0) { ActivityRecord r = mHistory.get(pos); if (localLOGV) Slog.v( @@ -4781,37 +4351,18 @@ final class ActivityStack { } mHistory.remove(pos); mHistory.add(top, r); + moved.add(0, r.appToken); top--; } pos--; } - // - // Start new code here! Delete everything above. - // - if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); - final int numTasks = mTaskHistory.size(); - final int index = mTaskHistory.indexOf(tr); - if (numTasks == 0 || index < 0 || index == numTasks - 1) { - // nothing to do! - if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - ActivityOptions.abort(options); - } else { - updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); - } - return; - } - - // Shift all activities with this task up to the top - // of the stack, keeping them in the same internal order. - mTaskHistory.remove(tr); - mTaskHistory.add(tr); - - if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to front transition: task=" + tr); + 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(AppTransition.TRANSIT_NONE, false); + mService.mWindowManager.prepareAppTransition( + AppTransition.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); @@ -4820,18 +4371,14 @@ final class ActivityStack { } else { updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); } - - mService.mWindowManager.moveTaskToTop(task); - - finishTaskMoveLocked(task); - EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, task); - + + mService.mWindowManager.moveAppTokensToTop(moved); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } + + finishTaskMoveLocked(task); + EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, task); } private final void finishTaskMoveLocked(int task) { @@ -4851,7 +4398,7 @@ final class ActivityStack { */ 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. @@ -4874,14 +4421,11 @@ final class ActivityStack { } } + ArrayList moved = new ArrayList(); + if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to back transition: task=" + task); - - final TaskRecord tr = mTaskIdToTaskRecord.get(task); - mTaskHistory.remove(tr); - mTaskHistory.add(0, tr); - - // BEGIN REGION TO REMOVE. + final int N = mHistory.size(); int bottom = 0; int pos = 0; @@ -4902,18 +4446,16 @@ final class ActivityStack { } mHistory.remove(pos); mHistory.add(bottom, r); + moved.add(r.appToken); bottom++; } pos++; } - if (VALIDATE_TASK_REPLACE) { - verifyActivityRecords(true); - } - // END REGION TO REMOVE if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); + mService.mWindowManager.prepareAppTransition( + AppTransition.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); @@ -4922,8 +4464,7 @@ final class ActivityStack { mService.mWindowManager.prepareAppTransition( AppTransition.TRANSIT_TASK_TO_BACK, false); } - mService.mWindowManager.moveTaskToBottom(task); - + mService.mWindowManager.moveAppTokensToBottom(moved); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } @@ -4956,8 +4497,9 @@ final class ActivityStack { TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); if (info.numSubThumbbails <= 0) { return info.mainThumbnail != null ? info.mainThumbnail : tr.lastThumbnail; + } else { + return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail; } - return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail; } public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex, @@ -5037,7 +4579,6 @@ final class ActivityStack { } if (thumbs.numSubThumbbails > 0) { thumbs.retriever = new IThumbnailRetriever.Stub() { - @Override public Bitmap getThumbnail(int index) { if (index < 0 || index >= thumbs.subtasks.size()) { return null; @@ -5236,283 +4777,4 @@ final class ActivityStack { public void dismissKeyguardOnNextActivityLocked() { mDismissKeyguardOnNextActivity = true; } - - boolean willActivityBeVisibleLocked(IBinder token) { - int i; - for (i = mHistory.size() - 1; i >= 0; i--) { - ActivityRecord r = mHistory.get(i); - if (r.appToken == token) { - return true; - } - if (r.fullscreen && !r.finishing) { - return false; - } - } - return true; - } - - void closeSystemDialogsLocked() { - for (int i = mHistory.size() - 1; i >= 0; i--) { - ActivityRecord r = mHistory.get(i); - if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { - r.stack.finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "close-sys", true); - } - } - } - - boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) { - boolean didSomething = false; - TaskRecord lastTask = null; - final int N = mHistory.size(); - for (int i = 0; i < N; i++) { - ActivityRecord r = mHistory.get(i); - final boolean samePackage = r.packageName.equals(name) - || (name == null && r.userId == userId); - if ((userId == UserHandle.USER_ALL || r.userId == userId) - && (samePackage || r.task == lastTask) - && (r.app == null || evenPersistent || !r.app.persistent)) { - if (!doit) { - if (r.finishing) { - // If this activity is just finishing, then it is not - // interesting as far as something to stop. - continue; - } - return true; - } - didSomething = true; - Slog.i(TAG, " Force finishing activity " + r); - if (samePackage) { - if (r.app != null) { - r.app.removed = true; - } - r.app = null; - } - lastTask = r.task; - if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "force-stop", true)) { - i--; - } - } - } - return didSomething; - } - - ActivityRecord getTasksLocked(int maxNum, IThumbnailReceiver receiver, - PendingThumbnailsRecord pending, List list) { - ActivityRecord topRecord = null; - int pos = mHistory.size() - 1; - ActivityRecord next = pos >= 0 ? mHistory.get(pos) : null; - ActivityRecord top = null; - TaskRecord curTask = null; - int numActivities = 0; - int numRunning = 0; - while (pos >= 0 && maxNum > 0) { - final ActivityRecord r = next; - pos--; - next = pos >= 0 ? mHistory.get(pos) : null; - - // Initialize state for next task if needed. - if (top == null || (top.state == ActivityState.INITIALIZING && top.task == r.task)) { - top = r; - curTask = r.task; - numActivities = numRunning = 0; - } - - // Add 'r' into the current task. - numActivities++; - if (r.app != null && r.app.thread != null) { - numRunning++; - } - - if (localLOGV) Slog.v( - TAG, r.intent.getComponent().flattenToShortString() - + ": task=" + r.task); - - // If the next one is a different task, generate a new - // TaskInfo entry for what we have. - if (next == null || next.task != curTask) { - RunningTaskInfo ci = new RunningTaskInfo(); - ci.id = curTask.taskId; - ci.baseActivity = r.intent.getComponent(); - ci.topActivity = top.intent.getComponent(); - if (top.thumbHolder != null) { - ci.description = top.thumbHolder.lastDescription; - } - ci.numActivities = numActivities; - ci.numRunning = numRunning; - //System.out.println( - // "#" + maxNum + ": " + " descr=" + ci.description); - if (receiver != null) { - if (localLOGV) Slog.v( - TAG, "State=" + top.state + "Idle=" + top.idle - + " app=" + top.app - + " thr=" + (top.app != null ? top.app.thread : null)); - if (top.state == ActivityState.RESUMED || top.state == ActivityState.PAUSING) { - if (top.idle && top.app != null && top.app.thread != null) { - topRecord = top; - } else { - top.thumbnailNeeded = true; - } - } - pending.pendingRecords.add(top); - } - list.add(ci); - maxNum--; - top = null; - } - } - return topRecord; - } - - public void unhandledBackLocked() { - int top = mHistory.size() - 1; - if (DEBUG_SWITCH) Slog.d( - TAG, "Performing unhandledBack(): top activity at " + top); - if (top > 0) { - finishActivityLocked(mHistory.get(top), - top, Activity.RESULT_CANCELED, null, "unhandled-back", true); - } - } - - void handleAppCrashLocked(ProcessRecord app) { - for (int i = mHistory.size() - 1; i >= 0; i--) { - ActivityRecord r = mHistory.get(i); - if (r.app == app) { - Slog.w(TAG, " Force finishing activity " - + r.intent.getComponent().flattenToShortString()); - r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "crashed", false); - } - } - } - - void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll, - boolean dumpClient, String dumpPackage) { - ActivityManagerService.dumpHistoryList(fd, pw, mHistory, " ", "Hist", true, !dumpAll, - dumpClient, dumpPackage); - } - - ArrayList getDumpActivitiesLocked(String name) { - ArrayList activities = new ArrayList(); - - if ("all".equals(name)) { - for (ActivityRecord r1 : mHistory) { - activities.add(r1); - } - } else if ("top".equals(name)) { - final int N = mHistory.size(); - if (N > 0) { - activities.add(mHistory.get(N-1)); - } - } else { - ItemMatcher matcher = new ItemMatcher(); - matcher.build(name); - - for (ActivityRecord r1 : mHistory) { - if (matcher.match(r1, r1.intent.getComponent())) { - activities.add(r1); - } - } - } - - return activities; - } - - ActivityRecord restartPackage(String packageName) { - ActivityRecord starting = topRunningActivityLocked(null); - - // All activities that came from the package must be - // restarted as if there was a config change. - for (int i = mHistory.size() - 1; i >= 0; i--) { - ActivityRecord a = mHistory.get(i); - if (a.info.packageName.equals(packageName)) { - a.forceNewConfig = true; - if (starting != null && a == starting && a.visible) { - a.startFreezingScreenLocked(starting.app, ActivityInfo.CONFIG_SCREEN_LAYOUT); - } - } - } - - return starting; - } - - void rebuildTaskHistory() { - mTaskHistory.clear(); - final int numActivities = mHistory.size(); - TaskRecord task = null; - for (int i = 0; i < numActivities; ++i) { - final ActivityRecord r = mHistory.get(i); - if (r.task != task) { - task = r.task; - task.mActivities.clear(); - mTaskHistory.add(task); - } - task.mActivities.add(r); - } - } - - boolean verifyActivityRecords(boolean rebuild) { - final int numHistory = mHistory.size(); - int historyNdx = 0; - - final int numTasks = mTaskHistory.size(); - int taskNdx; - for (taskNdx = historyNdx = 0; taskNdx < numTasks; ++taskNdx) { - final TaskRecord task = mTaskHistory.get(taskNdx); - final ArrayList activities = task.mActivities; - final int numActivities = activities.size(); - int activityNdx; - for (activityNdx = 0; - activityNdx < numActivities && historyNdx < numHistory; - ++activityNdx, ++historyNdx) { - ActivityRecord r1 = mHistory.get(historyNdx); - ActivityRecord r2 = activities.get(activityNdx); - if (r1 != r2) { - break; - } - } - if (activityNdx != numActivities) { - // either a mismatch or mHistory ran out before mTaskHistory. - break; - } - } - if (taskNdx != numTasks || historyNdx != numHistory) { - logHistories("verifyActivityRecords", rebuild); - return true; - } - return false; - } - - private void logHistories(String caller, boolean rebuild) { - Slog.w(TAG, "Mismatch! " + caller + " mHistory=" + mHistory); - ArrayList> nestedRecords = - new ArrayList>(); - for (TaskRecord task : mTaskHistory) { - nestedRecords.add(task.mActivities); - } - Slog.w(TAG, "Mismatch! " + caller + " mTaskHistory" + nestedRecords); - Slog.w(TAG, "Mismatch! " + caller + " lastHistoryModifier=" + mLastHistoryModifier - + " Caller=" + Debug.getCallers(4)); - if (rebuild) { - rebuildTaskHistory(); - } - } - - private TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, - boolean toTop) { - TaskRecord oldTask = mTaskIdToTaskRecord.get(taskId); - if (oldTask != null) { - Slog.w(TAG, "createTaskRecord: Reusing taskId=" + taskId + " without removing"); - mTaskHistory.remove(oldTask); - } - TaskRecord task = new TaskRecord(taskId, info, intent); - mTaskIdToTaskRecord.put(taskId, task); - if (toTop) { - mTaskHistory.add(task); - } else { - mTaskHistory.add(0, task); - } - return task; - } } diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java index 863bdad421777..3a6492e2957b1 100644 --- a/services/java/com/android/server/am/CompatModePackages.java +++ b/services/java/com/android/server/am/CompatModePackages.java @@ -295,8 +295,20 @@ public class CompatModePackages { Message msg = mHandler.obtainMessage(MSG_WRITE); mHandler.sendMessageDelayed(msg, 10000); - - ActivityRecord starting = mService.mMainStack.restartPackage(packageName); + ActivityRecord starting = mService.mMainStack.topRunningActivityLocked(null); + + // All activities that came from the package must be + // restarted as if there was a config change. + for (int i=mService.mMainStack.mHistory.size()-1; i>=0; i--) { + ActivityRecord a = (ActivityRecord)mService.mMainStack.mHistory.get(i); + if (a.info.packageName.equals(packageName)) { + a.forceNewConfig = true; + if (starting != null && a == starting && a.visible) { + a.startFreezingScreenLocked(starting.app, + ActivityInfo.CONFIG_SCREEN_LAYOUT); + } + } + } // Tell all processes that loaded this package about the change. for (int i=mService.mLruProcesses.size()-1; i>=0; i--) { diff --git a/services/java/com/android/server/am/PendingThumbnailsRecord.java b/services/java/com/android/server/am/PendingThumbnailsRecord.java index c4607916bb394..ed478c9741c8b 100644 --- a/services/java/com/android/server/am/PendingThumbnailsRecord.java +++ b/services/java/com/android/server/am/PendingThumbnailsRecord.java @@ -27,13 +27,13 @@ import java.util.HashSet; class PendingThumbnailsRecord { final IThumbnailReceiver receiver; // who is waiting. - final HashSet pendingRecords; // HistoryRecord objects we still wait for. + HashSet pendingRecords; // HistoryRecord objects we still wait for. boolean finished; // Is pendingRecords empty? PendingThumbnailsRecord(IThumbnailReceiver _receiver) { receiver = _receiver; - pendingRecords = new HashSet(); + pendingRecords = new HashSet(); finished = false; } } diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index f9b0d4cfe93bd..1bae9ca53adf2 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -23,7 +23,6 @@ import android.os.UserHandle; import android.util.Slog; import java.io.PrintWriter; -import java.util.ArrayList; class TaskRecord extends ThumbnailHolder { final int taskId; // Unique identifier for this task. @@ -40,11 +39,7 @@ class TaskRecord extends ThumbnailHolder { String stringName; // caching of toString() result. int userId; // user for which this task was created - - int numFullscreen; // Number of fullscreen activities. - - final ArrayList mActivities = new ArrayList(); - + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; @@ -109,63 +104,12 @@ class TaskRecord extends ThumbnailHolder { userId = UserHandle.getUserId(info.applicationInfo.uid); } } - - ActivityRecord getTopActivity() { - for (int i = mActivities.size() - 1; i >= 0; --i) { - final ActivityRecord r = mActivities.get(i); - if (r.finishing) { - continue; - } - return r; - } - return null; - } - - void addActivityAtBottom(ActivityRecord r) { - if (!mActivities.remove(r) && r.fullscreen) { - // Was not previously in list. - numFullscreen++; - } - mActivities.add(0, r); - } - - void addActivityToTop(ActivityRecord r) { - if (!mActivities.remove(r) && r.fullscreen) { - // Was not previously in list. - numFullscreen++; - } - // TODO: This only matters to achieve identical results as mHistory. Later we won't need - // to skip over finishing activities. - int i; - for (i = mActivities.size() - 1; i >= 0; --i) { - if (!mActivities.get(i).finishing) { - break; - } - } - if (i >= 0) { - // Add below finishing activities. - mActivities.add(i + 1, r); - } else { - // All activities are finishing, add to top. - mActivities.add(r); - } - } - - /** @return true if this was the last activity in the task */ - boolean removeActivity(ActivityRecord r) { - if (mActivities.remove(r) && r.fullscreen) { - // Was previously in list. - numFullscreen--; - } - return mActivities.size() == 0; - } - + void dump(PrintWriter pw, String prefix) { if (numActivities != 0 || rootWasReset || userId != 0) { pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.print(rootWasReset); - pw.print(" userId="); pw.print(userId); - pw.print(" numFullscreen="); pw.println(numFullscreen); + pw.print(" userId="); pw.println(userId); } if (affinity != null) { pw.print(prefix); pw.print("affinity="); pw.println(affinity); @@ -192,7 +136,6 @@ class TaskRecord extends ThumbnailHolder { pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } - pw.print(prefix); pw.print("Activities="); pw.println(mActivities); if (!askedCompatMode) { pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode); } @@ -203,7 +146,6 @@ class TaskRecord extends ThumbnailHolder { pw.print((getInactiveDuration()/1000)); pw.println("s)"); } - @Override public String toString() { if (stringName != null) { return stringName; @@ -214,21 +156,19 @@ class TaskRecord extends ThumbnailHolder { sb.append(" #"); sb.append(taskId); if (affinity != null) { - sb.append(" A="); + sb.append(" A "); sb.append(affinity); } else if (intent != null) { - sb.append(" I="); + sb.append(" I "); sb.append(intent.getComponent().flattenToShortString()); } else if (affinityIntent != null) { - sb.append(" aI="); + sb.append(" aI "); sb.append(affinityIntent.getComponent().flattenToShortString()); } else { sb.append(" ??"); } - sb.append(" U="); + sb.append(" U "); sb.append(userId); - sb.append(" sz="); - sb.append(mActivities.size()); sb.append('}'); return stringName = sb.toString(); } diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 8cc1d0211132b..fbb501397163a 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -30,10 +30,6 @@ import android.view.View; import android.view.WindowManager; import java.io.PrintWriter; -import java.util.ArrayList; - -class AppTokenList extends ArrayList { -} /** * Version of WindowToken that is specifically for a particular application (or diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index 6aae202d23096..59e4b0e6f7302 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -16,17 +16,11 @@ package com.android.server.wm; -import static com.android.server.wm.WindowManagerService.FORWARD_ITERATOR; -import static com.android.server.wm.WindowManagerService.REVERSE_ITERATOR; - -import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Iterator; -import java.util.NoSuchElementException; class DisplayContentList extends ArrayList { } @@ -39,7 +33,6 @@ class DisplayContentList extends ArrayList { * WindowManagerService.mWindowMap. */ class DisplayContent { -// private final static String TAG = "DisplayContent"; /** Unique identifier of this stack. */ private final int mDisplayId; @@ -73,26 +66,6 @@ class DisplayContent { int pendingLayoutChanges; final boolean isDefaultDisplay; - /** - * Window tokens that are in the process of exiting, but still - * on screen for animations. - */ - final ArrayList mExitingTokens = new ArrayList(); - - /** - * Application tokens that are in the process of exiting, but still - * on screen for animations. - */ - final AppTokenList mExitingAppTokens = new AppTokenList(); - - /** - * Sorted most recent at top, oldest at [0]. - */ - ArrayList mTaskLists = new ArrayList(); - SparseArray mTaskIdToTaskList = new SparseArray(); - - private final AppTokenIterator mTmpAppIterator = new AppTokenIterator(); - /** * @param display May not be null. */ @@ -123,147 +96,6 @@ class DisplayContent { mDisplay.getDisplayInfo(mDisplayInfo); } - /** - * Find the location to insert a new AppWindowToken into the window-ordered app token list. - * @param addPos The location the token was inserted into in mAppTokens. - * @param wtoken The token to insert. - */ - void addAppToken(final int addPos, final AppWindowToken wtoken) { - TaskList task = mTaskIdToTaskList.get(wtoken.groupId); - if (task == null) { - task = new TaskList(wtoken, this); - mTaskIdToTaskList.put(wtoken.groupId, task); - mTaskLists.add(task); - } else { - task.mAppTokens.add(addPos, wtoken); - } - } - - void removeAppToken(final AppWindowToken wtoken) { - final int taskId = wtoken.groupId; - final TaskList task = mTaskIdToTaskList.get(taskId); - if (task != null) { - AppTokenList appTokens = task.mAppTokens; - appTokens.remove(wtoken); - if (appTokens.size() == 0) { - mTaskLists.remove(task); - mTaskIdToTaskList.delete(taskId); - } - } - } - - void setAppTaskId(AppWindowToken wtoken, int newTaskId) { - final int taskId = wtoken.groupId; - TaskList task = mTaskIdToTaskList.get(taskId); - if (task != null) { - AppTokenList appTokens = task.mAppTokens; - appTokens.remove(wtoken); - if (appTokens.size() == 0) { - mTaskIdToTaskList.delete(taskId); - } - } - - task = mTaskIdToTaskList.get(newTaskId); - if (task == null) { - task = new TaskList(wtoken, this); - mTaskIdToTaskList.put(newTaskId, task); - } else { - task.mAppTokens.add(wtoken); - } - - wtoken.groupId = newTaskId; - } - - /** - * Return the utility iterator so we don't have to construct new iterators every time we - * iterate. - * NOTE: Do not ever nest this call or you will have a bad time! - * @param reverse Direction of iterator. - * @return The utility iterator. - */ - AppTokenIterator getTmpAppIterator(boolean reverse) { - mTmpAppIterator.reset(reverse); - return mTmpAppIterator; - } - - class AppTokenIterator implements Iterator { - boolean mReverse; - int mTasksNdx; - int mActivityNdx; - TaskList mTaskList; - - public AppTokenIterator() { - this(FORWARD_ITERATOR); - } - - public AppTokenIterator(boolean reverse) { - reset(reverse); - } - - void reset(boolean reverse) { - mReverse = reverse; - mTasksNdx = reverse ? mTaskLists.size() - 1 : 0; - getNextTaskList(); - } - - private void getNextTaskList() { - if (mReverse) { - if (mTasksNdx >= 0) { - mTaskList = mTaskLists.get(mTasksNdx); - --mTasksNdx; - mActivityNdx = mTaskList.mAppTokens.size() - 1; - } - } else { - if (mTasksNdx < mTaskLists.size()) { - mTaskList = mTaskLists.get(mTasksNdx); - ++mTasksNdx; - mActivityNdx = 0; - } - } - } - - @Override - public boolean hasNext() { - if (mTaskList == null) { - return false; - } - if (mReverse) { - return mActivityNdx >= 0; - } - return mActivityNdx < mTaskList.mAppTokens.size(); - } - - @Override - public AppWindowToken next() { - if (hasNext()) { - AppWindowToken wtoken = mTaskList.mAppTokens.get(mActivityNdx); - mActivityNdx += mReverse ? -1 : 1; - if (!hasNext()) { - getNextTaskList(); - } - return wtoken; - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new IllegalArgumentException(); - } - - int size() { - int size = 0; - for (int i = mTaskLists.size() - 1; i >= 0; --i) { - size += mTaskLists.get(i).mAppTokens.size(); - } - return size; - } - - @Override public String toString() { - return mTaskLists.toString(); - } - } - public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId); final String subPrefix = " " + prefix; @@ -287,51 +119,7 @@ class DisplayContent { pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight); pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth); pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); - pw.print(subPrefix); pw.print("layoutNeeded="); pw.println(layoutNeeded); - AppTokenIterator iterator = getTmpAppIterator(REVERSE_ITERATOR); - int ndx = iterator.size() - 1; - if (ndx >= 0) { - pw.println(); - pw.println(" Application tokens in Z order:"); - while (iterator.hasNext()) { - AppWindowToken wtoken = iterator.next(); - pw.print(" App #"); pw.print(ndx--); - pw.print(' '); pw.print(wtoken); pw.println(":"); - wtoken.dump(pw, " "); - } - } - if (mExitingTokens.size() > 0) { - pw.println(); - pw.println(" Exiting tokens:"); - for (int i=mExitingTokens.size()-1; i>=0; i--) { - WindowToken token = mExitingTokens.get(i); - pw.print(" Exiting #"); pw.print(i); - pw.print(' '); pw.print(token); - pw.println(':'); - token.dump(pw, " "); - } - } - if (mExitingAppTokens.size() > 0) { - pw.println(); - pw.println(" Exiting application tokens:"); - for (int i=mExitingAppTokens.size()-1; i>=0; i--) { - WindowToken token = mExitingAppTokens.get(i); - pw.print(" Exiting App #"); pw.print(i); - pw.print(' '); pw.print(token); - pw.println(':'); - token.dump(pw, " "); - } - } - if (mTaskIdToTaskList.size() > 0) { - pw.println(); - for (int i = 0; i < mTaskIdToTaskList.size(); ++i) { - pw.print(" TaskList #"); pw.print(i); - pw.print(" taskId="); pw.println(mTaskIdToTaskList.keyAt(i)); - pw.print(" mAppTokens="); - pw.println(mTaskIdToTaskList.valueAt(i).mAppTokens); - pw.println(); - } - } + pw.print(subPrefix); pw.print("layoutNeeded="); pw.print(layoutNeeded); pw.println(); } } diff --git a/services/java/com/android/server/wm/TaskGroup.java b/services/java/com/android/server/wm/TaskGroup.java deleted file mode 100644 index 1f1dd5832d558..0000000000000 --- a/services/java/com/android/server/wm/TaskGroup.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.view.IApplicationToken; - -import java.util.ArrayList; - -public class TaskGroup { - public int taskId = -1; - public ArrayList tokens = new ArrayList(); - - @Override - public String toString() { - return "id=" + taskId + " tokens=" + tokens; - } -} diff --git a/services/java/com/android/server/wm/TaskList.java b/services/java/com/android/server/wm/TaskList.java deleted file mode 100644 index 67dfa4f333b44..0000000000000 --- a/services/java/com/android/server/wm/TaskList.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -class TaskList { -// private final String TAG = "TaskGroup"; - DisplayContent mDisplayContent; - final AppTokenList mAppTokens = new AppTokenList(); - final int taskId; - - TaskList(AppWindowToken wtoken, DisplayContent displayContent) { - taskId = wtoken.groupId; - mAppTokens.add(wtoken); - mDisplayContent = displayContent; - } - - @Override - public String toString() { - return "id=" + taskId + " appTokens=" + mAppTokens; - } -} diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index 67daf751d4563..3964782baced9 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -10,8 +10,6 @@ import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPA import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_ACTION_PENDING; -import static com.android.server.wm.WindowManagerService.FORWARD_ITERATOR; -import static com.android.server.wm.WindowManagerService.REVERSE_ITERATOR; import android.content.Context; import android.os.Debug; @@ -28,7 +26,6 @@ import android.view.SurfaceControl; import android.view.WindowManagerPolicy; import android.view.animation.Animation; -import com.android.server.wm.DisplayContent.AppTokenIterator; import com.android.server.wm.WindowManagerService.DisplayContentsIterator; import com.android.server.wm.WindowManagerService.LayoutFields; @@ -175,12 +172,12 @@ public class WindowAnimator { } } - private void updateAppWindowsLocked(int displayId) { + private void updateAppWindowsLocked() { int i; - final DisplayContent displayContent = mService.getDisplayContentLocked(displayId); - AppTokenIterator iterator = displayContent.getTmpAppIterator(FORWARD_ITERATOR); - while (iterator.hasNext()) { - final AppWindowAnimator appAnimator = iterator.next().mAppAnimator; + final ArrayList appTokens = mService.mAnimatingAppTokens; + final int NAT = appTokens.size(); + for (i=0; i appTokens = mService.mAnimatingAppTokens; + final int NT = appTokens.size(); + for (int i=0; i mTokenMap = new HashMap(); + final HashMap mTokenMap = + new HashMap(); + + /** + * Window tokens that are in the process of exiting, but still + * on screen for animations. + */ + final ArrayList mExitingTokens = new ArrayList(); + + /** + * List controlling the ordering of windows in different applications which must + * be kept in sync with ActivityManager. + */ + final ArrayList mAppTokens = new ArrayList(); + + /** + * AppWindowTokens in the Z order they were in at the start of an animation. Between + * animations this list is maintained in the exact order of mAppTokens. If tokens + * are added to mAppTokens during an animation an attempt is made to insert them at the same + * logical location in this list. Note that this list is always in sync with mWindows. + */ + ArrayList mAnimatingAppTokens = new ArrayList(); + + /** + * Application tokens that are in the process of exiting, but still + * on screen for animations. + */ + final ArrayList mExitingAppTokens = new ArrayList(); /** * List of window tokens that have finished starting their application, @@ -422,12 +444,8 @@ public class WindowManagerService extends IWindowManager.Stub String mLastANRState; - /** All DisplayContents in the world, kept here */ + /** All DisplayDontents in the world, kept here */ private SparseArray mDisplayContents = new SparseArray(); - private SparseArray mTaskIdToDisplayContents = - new SparseArray(); - - private final AllWindowsIterator mTmpWindowsIterator = new AllWindowsIterator(); int mRotation = 0; int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -901,6 +919,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowList windows = win.getWindowList(); final int N = windows.size(); final WindowState attached = win.mAttachedWindow; + int i; WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); if (attached == null) { int tokenWindowsPos = 0; @@ -950,11 +969,12 @@ public class WindowManagerService extends IWindowManager.Stub + client.asBinder() + " (token=" + token + ")"); // Figure out where the window should go, based on the // order of applications. + final int NA = mAnimatingAppTokens.size(); WindowState pos = null; - AppTokenIterator iterator = displayContent.getTmpAppIterator(REVERSE_ITERATOR); - while (iterator.hasNext()) { - AppWindowToken t = iterator.next(); + for (i=NA-1; i>=0; i--) { + AppWindowToken t = mAnimatingAppTokens.get(i); if (t == token) { + i--; break; } @@ -987,14 +1007,15 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Continue looking down until we find the first // token that has windows on this display. - while (iterator.hasNext()) { - AppWindowToken t = iterator.next(); - tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); + while (i >= 0) { + AppWindowToken t = mAnimatingAppTokens.get(i); + tokenWindowList = getTokenWindowsOnDisplay(t, win.mDisplayContent); final int NW = tokenWindowList.size(); if (NW > 0) { pos = tokenWindowList.get(NW-1); break; } + i--; } if (pos != null) { // Move in front of any windows attached to this @@ -1013,8 +1034,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Just search for the start of this layer. final int myLayer = win.mBaseLayer; - int i; - for (i = 0; i < N; i++) { + for (i=0; i myLayer) { break; @@ -1032,8 +1052,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Figure out where window should go, based on layer. final int myLayer = win.mBaseLayer; - int i; - for (i = N - 1; i >= 0; i--) { + for (i=N-1; i>=0; i--) { if (windows.get(i).mBaseLayer <= myLayer) { break; } @@ -1058,8 +1077,7 @@ public class WindowManagerService extends IWindowManager.Stub final int sublayer = win.mSubLayer; int largestSublayer = Integer.MIN_VALUE; WindowState windowWithLargestSublayer = null; - int i; - for (i = 0; i < NA; i++) { + for (i=0; i= largestSublayer) { @@ -2464,15 +2482,22 @@ public class WindowManagerService extends IWindowManager.Stub public void updateAppOpsState() { synchronized(mWindowMap) { - mTmpWindowsIterator.reset(FORWARD_ITERATOR); - while (mTmpWindowsIterator.hasNext()) { - final WindowState win = mTmpWindowsIterator.next(); - if (win.mAppOp != AppOpsManager.OP_NONE) { - final int mode = mAppOps.checkOpNoThrow(win.mAppOp, win.getOwningUid(), - win.getOwningPackage()); - win.setAppOpVisibilityLw(mode == AppOpsManager.MODE_ALLOWED); + boolean changed = false; + for (int i=0; i tasks) { - synchronized (mWindowMap) { - int t = tasks.size() - 1; - if (t < 0) { - Slog.w(TAG, "validateAppTokens: empty task list"); - return; + public void validateAppTokens(List tokens) { + int v = tokens.size()-1; + int m = mAppTokens.size()-1; + while (v >= 0 && m >= 0) { + AppWindowToken atoken = mAppTokens.get(m); + if (atoken.removed) { + m--; + continue; } - - TaskGroup task = tasks.get(0); - int taskId = task.taskId; - DisplayContent displayContent = mTaskIdToDisplayContents.get(taskId); - if (displayContent == null) { - Slog.w(TAG, "validateAppTokens: no Display for taskId=" + taskId); - return; + if (tokens.get(v) != atoken.token) { + Slog.w(TAG, "Tokens out of sync: external is " + tokens.get(v) + + " @ " + v + ", internal is " + atoken.token + " @ " + m); } - - boolean mismatch = false; - AppTokenIterator iterator = displayContent.getTmpAppIterator(REVERSE_ITERATOR); - for ( ; t >= 0 && !mismatch; --t) { - task = tasks.get(t); - List tokens = task.tokens; - int v = task.tokens.size() - 1; - - DisplayContent lastDisplayContent = displayContent; - displayContent = mTaskIdToDisplayContents.get(taskId); - if (displayContent != lastDisplayContent) { - Slog.w(TAG, "validateAppTokens: displayContent changed in TaskGroup list!"); - return; - } - - while (v >= 0) { - if (!iterator.hasNext()) { - mismatch = true; - break; - } - AppWindowToken atoken = iterator.next(); - if (atoken.removed) { - continue; - } - if (tokens.get(v) != atoken.token) { - mismatch = true; - break; - } - v--; - } - } - - if (mismatch || iterator.hasNext()) { - Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks); - Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + iterator); - Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4)); + v--; + m--; + } + while (v >= 0) { + Slog.w(TAG, "External token not found: " + tokens.get(v) + " @ " + v); + v--; + } + while (m >= 0) { + AppWindowToken atoken = mAppTokens.get(m); + if (!atoken.removed) { + Slog.w(TAG, "Invalid internal atoken: " + atoken.token + " @ " + m); } + m--; } } @@ -3227,7 +3226,6 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { - DisplayContent displayContent = null; WindowToken wtoken = mTokenMap.remove(token); if (wtoken != null) { boolean delayed = false; @@ -3237,7 +3235,6 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i=0; i--) { + Slog.v(TAG, " #" + i + ": " + mAppTokens.get(i).token); + } + } + + void dumpAnimatingAppTokensLocked() { + for (int i=mAnimatingAppTokens.size()-1; i>=0; i--) { + Slog.v(TAG, " #" + i + ": " + mAnimatingAppTokens.get(i).token); } } void dumpWindowsLocked() { int i = 0; - mTmpWindowsIterator.reset(REVERSE_ITERATOR); - while (mTmpWindowsIterator.hasNext()) { - final WindowState w = mTmpWindowsIterator.next(); + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); Slog.v(TAG, " #" + i++ + ": " + w); } } - private int findAppWindowInsertionPointLocked(AppWindowToken target) { - final int taskId = target.groupId; - DisplayContent displayContent = mTaskIdToDisplayContents.get(taskId); - if (displayContent == null) { - Slog.w(TAG, "findTopAppWindowLocked: no DisplayContent for " + target); - return 0; - } - final WindowList windows = displayContent.getWindowList(); + private int findWindowOffsetLocked(WindowList windows, int tokenPos) { final int NW = windows.size(); - AppTokenIterator iterator = displayContent.getTmpAppIterator(REVERSE_ITERATOR); - while (iterator.hasNext()) { - if (iterator.next() == target) { - break; + if (tokenPos >= mAnimatingAppTokens.size()) { + int i = NW; + while (i > 0) { + i--; + WindowState win = windows.get(i); + if (win.getAppToken() != null) { + return i+1; + } } } - while (iterator.hasNext()) { + while (tokenPos > 0) { // Find the first app token below the new position that has // a window displayed. - final AppWindowToken wtoken = iterator.next(); - if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows in " + wtoken.token); + final AppWindowToken wtoken = mAppTokens.get(tokenPos-1); + if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows @ " + + tokenPos + " -- " + wtoken.token); if (wtoken.sendingToBottom) { - if (DEBUG_REORDER) Slog.v(TAG, "Skipping token -- currently sending to bottom"); + if (DEBUG_REORDER) Slog.v(TAG, + "Skipping token -- currently sending to bottom"); + tokenPos--; continue; } - for (int i = wtoken.windows.size() - 1; i >= 0; --i) { + int i = wtoken.windows.size(); + while (i > 0) { + i--; WindowState win = wtoken.windows.get(i); - for (int j = win.mChildWindows.size() - 1; j >= 0; --j) { + int j = win.mChildWindows.size(); + while (j > 0) { + j--; WindowState cwin = win.mChildWindows.get(j); if (cwin.mSubLayer >= 0) { - for (int pos = NW - 1; pos >= 0; pos--) { + for (int pos=NW-1; pos>=0; pos--) { if (windows.get(pos) == cwin) { if (DEBUG_REORDER) Slog.v(TAG, - "Found child win @" + (pos + 1)); - return pos + 1; + "Found child win @" + (pos+1)); + return pos+1; } } } } - for (int pos = NW - 1; pos >= 0; pos--) { + for (int pos=NW-1; pos>=0; pos--) { if (windows.get(pos) == win) { - if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos + 1)); - return pos + 1; + if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos+1)); + return pos+1; } } } + tokenPos--; } return 0; @@ -4499,104 +4505,198 @@ public class WindowManagerService extends IWindowManager.Stub return index; } - private void moveTaskWindowsLocked(int taskId) { - DisplayContent displayContent = mTaskIdToDisplayContents.get(taskId); - if (displayContent == null) { - Slog.w(TAG, "moveTaskWindowsLocked: can't find DisplayContent for taskId=" + taskId); - return; + @Override + public void moveAppToken(int index, IBinder token) { + if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, + "moveAppToken()")) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } - TaskList taskList = displayContent.mTaskIdToTaskList.get(taskId); - if (taskList == null) { - Slog.w(TAG, "moveTaskWindowsLocked: can't find TaskList for taskId=" + taskId); - return; - } + synchronized(mWindowMap) { + if (DEBUG_REORDER) Slog.v(TAG, "Initial app tokens:"); + if (DEBUG_REORDER) dumpAppTokensLocked(); + final AppWindowToken wtoken = findAppWindowToken(token); + final int oldIndex = mAppTokens.indexOf(wtoken); + if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, + "Start moving token " + wtoken + " initially at " + + oldIndex); + if (oldIndex > index && mAppTransition.isTransitionSet()) { + // animation towards back has not started, copy old list for duration of animation. + mAnimatingAppTokens.clear(); + mAnimatingAppTokens.addAll(mAppTokens); + } + if (wtoken == null || !mAppTokens.remove(wtoken)) { + Slog.w(TAG, "Attempting to reorder token that doesn't exist: " + + token + " (" + wtoken + ")"); + return; + } + mAppTokens.add(index, wtoken); + if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":"); + else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index); + if (DEBUG_REORDER) dumpAppTokensLocked(); + if (!mAppTransition.isTransitionSet()) { + // Not animating, bring animating app list in line with mAppTokens. + mAnimatingAppTokens.clear(); + mAnimatingAppTokens.addAll(mAppTokens); + // Bring window ordering, window focus and input window in line with new app token + final long origId = Binder.clearCallingIdentity(); + if (DEBUG_REORDER) Slog.v(TAG, "Removing windows in " + token + ":"); + if (DEBUG_REORDER) dumpWindowsLocked(); + if (tmpRemoveAppWindowsLocked(wtoken)) { + if (DEBUG_REORDER) Slog.v(TAG, "Adding windows back in:"); + if (DEBUG_REORDER) dumpWindowsLocked(); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while(iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + final WindowList windows = displayContent.getWindowList(); + final int pos = findWindowOffsetLocked(windows, index); + final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken); + if (pos != newPos) { + displayContent.layoutNeeded = true; + } + } + if (DEBUG_REORDER) Slog.v(TAG, "Final window list:"); + if (DEBUG_REORDER) dumpWindowsLocked(); + updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/); + mInputMonitor.setUpdateInputWindowsNeededLw(); + performLayoutAndPlaceSurfacesLocked(); + mInputMonitor.updateInputWindowsLw(false /*force*/); + } + Binder.restoreCallingIdentity(origId); + } + } + } + + private void removeAppTokensLocked(List tokens) { + // XXX This should be done more efficiently! + // (take advantage of the fact that both lists should be + // ordered in the same way.) + int N = tokens.size(); + for (int i=0; i tokens, int tokenPos) { // First remove all of the windows from the list. - for (AppWindowToken wtoken : taskList.mAppTokens) { - tmpRemoveAppWindowsLocked(wtoken); + final int N = tokens.size(); + int i; + for (i=0; i tokens) { + if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, + "moveAppTokensToTop()")) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } + + final long origId = Binder.clearCallingIdentity(); + synchronized(mWindowMap) { + removeAppTokensLocked(tokens); + final int N = tokens.size(); + for (int i=0; i tokens) { + if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, + "moveAppTokensToBottom()")) { + throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } + + final long origId = Binder.clearCallingIdentity(); + synchronized(mWindowMap) { + final int N = tokens.size(); + if (N > 0) { + // animating towards back, hang onto old list for duration of animation. + mAnimatingAppTokens.clear(); + mAnimatingAppTokens.addAll(mAppTokens); + } + removeAppTokensLocked(tokens); + int pos = 0; + for (int i=0; i 0) { + i--; + AppWindowToken tok = mAppTokens.get(i); if (tok.mAppAnimator.freezingScreen) { Slog.w(TAG, "Force clearing freeze: " + tok); unsetAppFreezingScreenLocked(tok, true, true); @@ -7307,7 +7408,6 @@ public class WindowManagerService extends IWindowManager.Stub performLayoutAndPlaceSurfacesLocked(); } - @Override public void setOverscan(int displayId, int left, int top, int right, int bottom) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS) != @@ -7426,16 +7526,15 @@ public class WindowManagerService extends IWindowManager.Stub // in the main app list, but still have windows shown. We put them // in the back because now that the animation is over we no longer // will care about them. - AppTokenList exitingAppTokens = displayContent.mExitingAppTokens; - int NT = exitingAppTokens.size(); + int NT = mExitingAppTokens.size(); for (int j=0; j= 0; i--) { + mAnimatingAppTokens.get(i).sendingToBottom = false; } + mAnimatingAppTokens.clear(); + mAnimatingAppTokens.addAll(mAppTokens); rebuildAppWindowListLocked(); changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; @@ -8266,12 +8365,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void updateAllDrawnLocked(DisplayContent displayContent) { + private void updateAllDrawnLocked() { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - AppTokenIterator iterator = displayContent.getTmpAppIterator(FORWARD_ITERATOR); - while (iterator.hasNext()) { - AppWindowToken wtoken = iterator.next(); + final ArrayList appTokens = mAnimatingAppTokens; + final int NT = appTokens.size(); + for (int i=0; i 0 && wtoken.numDrawnWindows >= numInteresting) { @@ -8303,17 +8403,13 @@ public class WindowManagerService extends IWindowManager.Stub } // Initialize state of exiting tokens. - DisplayContentsIterator iterator = new DisplayContentsIterator(); - while (iterator.hasNext()) { - final DisplayContent displayContent = iterator.next(); - for (i=displayContent.mExitingTokens.size()-1; i>=0; i--) { - displayContent.mExitingTokens.get(i).hasVisible = false; - } + for (i=mExitingTokens.size()-1; i>=0; i--) { + mExitingTokens.get(i).hasVisible = false; + } - // Initialize state of exiting applications. - for (i=displayContent.mExitingAppTokens.size()-1; i>=0; i--) { - displayContent.mExitingAppTokens.get(i).hasVisible = false; - } + // Initialize state of exiting applications. + for (i=mExitingAppTokens.size()-1; i>=0; i--) { + mExitingAppTokens.get(i).hasVisible = false; } mInnerFields.mHoldScreen = null; @@ -8342,10 +8438,10 @@ public class WindowManagerService extends IWindowManager.Stub } boolean focusDisplayed = false; + boolean updateAllDrawn = false; - iterator = new DisplayContentsIterator(); + DisplayContentsIterator iterator = new DisplayContentsIterator(); while (iterator.hasNext()) { - boolean updateAllDrawn = false; final DisplayContent displayContent = iterator.next(); WindowList windows = displayContent.getWindowList(); DisplayInfo displayInfo = displayContent.getDisplayInfo(); @@ -8589,10 +8685,10 @@ public class WindowManagerService extends IWindowManager.Stub if (!mInnerFields.mDimming && mAnimator.isDimmingLocked(displayId)) { stopDimmingLocked(displayId); } + } - if (updateAllDrawn) { - updateAllDrawnLocked(displayContent); - } + if (updateAllDrawn) { + updateAllDrawnLocked(); } if (focusDisplayed) { @@ -8739,35 +8835,30 @@ public class WindowManagerService extends IWindowManager.Stub } // Time to remove any exiting tokens? - iterator = new DisplayContentsIterator(); - while (iterator.hasNext()) { - final DisplayContent displayContent = iterator.next(); - ArrayList exitingTokens = displayContent.mExitingTokens; - for (i = exitingTokens.size() - 1; i >= 0; i--) { - WindowToken token = exitingTokens.get(i); - if (!token.hasVisible) { - exitingTokens.remove(i); - if (token.windowType == TYPE_WALLPAPER) { - mWallpaperTokens.remove(token); - } + for (i=mExitingTokens.size()-1; i>=0; i--) { + WindowToken token = mExitingTokens.get(i); + if (!token.hasVisible) { + mExitingTokens.remove(i); + if (token.windowType == TYPE_WALLPAPER) { + mWallpaperTokens.remove(token); } } + } - // Time to remove any exiting applications? - AppTokenList exitingAppTokens = displayContent.mExitingAppTokens; - for (i = exitingAppTokens.size() - 1; i >= 0; i--) { - AppWindowToken token = exitingAppTokens.get(i); - if (!token.hasVisible && !mClosingApps.contains(token)) { - // Make sure there is no animation running on this token, - // so any windows associated with it will be removed as - // soon as their animations are complete - token.mAppAnimator.clearAnimation(); - token.mAppAnimator.animating = false; - if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, - "performLayout: App token exiting now removed" + token); - displayContent.removeAppToken(token); - exitingAppTokens.remove(i); - } + // Time to remove any exiting applications? + for (i=mExitingAppTokens.size()-1; i>=0; i--) { + AppWindowToken token = mExitingAppTokens.get(i); + if (!token.hasVisible && !mClosingApps.contains(token)) { + // Make sure there is no animation running on this token, + // so any windows associated with it will be removed as + // soon as their animations are complete + token.mAppAnimator.clearAnimation(); + token.mAppAnimator.animating = false; + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "performLayout: App token exiting now removed" + token); + mAppTokens.remove(token); + mAnimatingAppTokens.remove(token); + mExitingAppTokens.remove(i); } } @@ -8787,7 +8878,7 @@ public class WindowManagerService extends IWindowManager.Stub defaultDisplay.layoutNeeded = true; } - iterator = new DisplayContentsIterator(); + DisplayContentsIterator iterator = new DisplayContentsIterator(); while (iterator.hasNext()) { DisplayContent displayContent = iterator.next(); if (displayContent.pendingLayoutChanges != 0) { @@ -9038,10 +9129,10 @@ public class WindowManagerService extends IWindowManager.Stub // window list to make sure we haven't left any dangling surfaces // around. - mTmpWindowsIterator.reset(FORWARD_ITERATOR); + AllWindowsIterator iterator = new AllWindowsIterator(); Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); - while (mTmpWindowsIterator.hasNext()) { - WindowState ws = mTmpWindowsIterator.next(); + while (iterator.hasNext()) { + WindowState ws = iterator.next(); WindowStateAnimator wsa = ws.mWinAnimator; if (wsa.mSurfaceControl != null) { if (!mSessions.contains(wsa.mSession)) { @@ -9074,9 +9165,9 @@ public class WindowManagerService extends IWindowManager.Stub if (!leakedSurface) { Slog.w(TAG, "No leaked surfaces; killing applicatons!"); SparseIntArray pidCandidates = new SparseIntArray(); - mTmpWindowsIterator.reset(FORWARD_ITERATOR); - while (mTmpWindowsIterator.hasNext()) { - WindowState ws = mTmpWindowsIterator.next(); + iterator = new AllWindowsIterator(); + while (iterator.hasNext()) { + WindowState ws = iterator.next(); if (mForceRemoves.contains(ws)) { continue; } @@ -9202,8 +9293,8 @@ public class WindowManagerService extends IWindowManager.Stub } private WindowState findFocusedWindowLocked(DisplayContent displayContent) { - AppTokenIterator iterator = displayContent.getTmpAppIterator(REVERSE_ITERATOR); - WindowToken nextApp = iterator.hasNext() ? iterator.next() : null; + int nextAppIndex = mAppTokens.size()-1; + WindowToken nextApp = nextAppIndex >= 0 ? mAppTokens.get(nextAppIndex) : null; final WindowList windows = displayContent.getWindowList(); for (int i = windows.size() - 1; i >= 0; i--) { @@ -9229,8 +9320,8 @@ public class WindowManagerService extends IWindowManager.Stub // through the app tokens until we find its app. if (thisApp != null && nextApp != null && thisApp != nextApp && win.mAttrs.type != TYPE_APPLICATION_STARTING) { - final WindowToken origAppToken = nextApp; - while (iterator.hasNext()) { + int origAppIndex = nextAppIndex; + while (nextAppIndex > 0) { if (nextApp == mFocusedApp) { // Whoops, we are below the focused app... no focus // for you! @@ -9238,7 +9329,8 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Reached focused app: " + mFocusedApp); return null; } - nextApp = iterator.next(); + nextAppIndex--; + nextApp = mAppTokens.get(nextAppIndex); if (nextApp == thisApp) { break; } @@ -9247,14 +9339,8 @@ public class WindowManagerService extends IWindowManager.Stub // Uh oh, the app token doesn't exist! This shouldn't // happen, but if it does we can get totally hosed... // so restart at the original app. - nextApp = origAppToken; - iterator = displayContent.new AppTokenIterator(true); - while (iterator.hasNext()) { - // return iterator to same place. - if (iterator.next() == origAppToken) { - break; - } - } + nextAppIndex = origAppIndex; + nextApp = mAppTokens.get(nextAppIndex); } } @@ -9624,6 +9710,15 @@ public class WindowManagerService extends IWindowManager.Stub } } } + if (mAppTokens.size() > 0) { + pw.println(); + pw.println(" Application tokens in Z order:"); + for (int i=mAppTokens.size()-1; i>=0; i--) { + pw.print(" App #"); pw.print(i); + pw.print(' '); pw.print(mAppTokens.get(i)); pw.println(":"); + mAppTokens.get(i).dump(pw, " "); + } + } if (mFinishedStarting.size() > 0) { pw.println(); pw.println(" Finishing start of application tokens:"); @@ -9639,6 +9734,51 @@ public class WindowManagerService extends IWindowManager.Stub } } } + if (mExitingTokens.size() > 0) { + pw.println(); + pw.println(" Exiting tokens:"); + for (int i=mExitingTokens.size()-1; i>=0; i--) { + WindowToken token = mExitingTokens.get(i); + pw.print(" Exiting #"); pw.print(i); + pw.print(' '); pw.print(token); + if (dumpAll) { + pw.println(':'); + token.dump(pw, " "); + } else { + pw.println(); + } + } + } + if (mExitingAppTokens.size() > 0) { + pw.println(); + pw.println(" Exiting application tokens:"); + for (int i=mExitingAppTokens.size()-1; i>=0; i--) { + WindowToken token = mExitingAppTokens.get(i); + pw.print(" Exiting App #"); pw.print(i); + pw.print(' '); pw.print(token); + if (dumpAll) { + pw.println(':'); + token.dump(pw, " "); + } else { + pw.println(); + } + } + } + if (mAppTransition.isRunning() && mAnimatingAppTokens.size() > 0) { + pw.println(); + pw.println(" Application tokens during animation:"); + for (int i=mAnimatingAppTokens.size()-1; i>=0; i--) { + WindowToken token = mAnimatingAppTokens.get(i); + pw.print(" App moving to bottom #"); pw.print(i); + pw.print(' '); pw.print(token); + if (dumpAll) { + pw.println(':'); + token.dump(pw, " "); + } else { + pw.println(); + } + } + } if (mOpeningApps.size() > 0 || mClosingApps.size() > 0) { pw.println(); if (mOpeningApps.size() > 0) { @@ -9683,9 +9823,9 @@ public class WindowManagerService extends IWindowManager.Stub void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll, ArrayList windows) { int j = 0; - mTmpWindowsIterator.reset(REVERSE_ITERATOR); - while (mTmpWindowsIterator.hasNext()) { - final WindowState w = mTmpWindowsIterator.next(); + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (windows == null || windows.contains(w)) { pw.print(" Window #"); pw.print(j++); pw.print(' '); pw.print(w); pw.println(":"); @@ -9871,9 +10011,9 @@ public class WindowManagerService extends IWindowManager.Stub WindowList windows = new WindowList(); if ("visible".equals(name)) { synchronized(mWindowMap) { - mTmpWindowsIterator.reset(REVERSE_ITERATOR); - while (mTmpWindowsIterator.hasNext()) { - final WindowState w = mTmpWindowsIterator.next(); + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (w.mWinAnimator.mSurfaceShown) { windows.add(w); } @@ -9888,9 +10028,9 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RuntimeException e) { } synchronized(mWindowMap) { - mTmpWindowsIterator.reset(REVERSE_ITERATOR); - while (mTmpWindowsIterator.hasNext()) { - final WindowState w = mTmpWindowsIterator.next(); + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (name != null) { if (w.mAttrs.getTitle().toString().contains(name)) { windows.add(w); @@ -10143,10 +10283,6 @@ public class WindowManagerService extends IWindowManager.Stub class DisplayContentsIterator implements Iterator { private int cur; - void reset() { - cur = 0; - } - @Override public boolean hasNext() { return cur < mDisplayContents.size(); @@ -10166,6 +10302,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + final static boolean REVERSE_ITERATOR = true; class AllWindowsIterator implements Iterator { private DisplayContent mDisplayContent; private DisplayContentsIterator mDisplayContentsIterator; @@ -10174,33 +10311,19 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mReverse; AllWindowsIterator() { - this(false); + mDisplayContentsIterator = new DisplayContentsIterator(); + mDisplayContent = mDisplayContentsIterator.next(); + mWindowList = mDisplayContent.getWindowList(); } AllWindowsIterator(boolean reverse) { - mDisplayContentsIterator = new DisplayContentsIterator(); - reset(reverse); - } - - void reset(boolean reverse) { + this(); mReverse = reverse; - mDisplayContentsIterator.reset(); - if (mDisplayContentsIterator.hasNext()) { - mDisplayContent = mDisplayContentsIterator.next(); - mWindowList = mDisplayContent.getWindowList(); - mWindowListIndex = reverse ? mWindowList.size() - 1 : 0; - } else { - mDisplayContent = null; - mWindowList = null; - mWindowListIndex = 0; - } + mWindowListIndex = reverse ? mWindowList.size() - 1 : 0; } @Override public boolean hasNext() { - if (mDisplayContent == null) { - return false; - } if (mReverse) { return mWindowListIndex >= 0; } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 517c4e414ada3..a60062345cd9d 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -52,12 +52,6 @@ import java.io.PrintWriter; import java.util.ArrayList; class WindowList extends ArrayList { - WindowList() { - super(); - } - WindowList(WindowList windows) { - super(windows); - } } /** @@ -1092,7 +1086,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { return true; } - public void setAppOpVisibilityLw(boolean state) { + public boolean setAppOpVisibilityLw(boolean state) { if (mAppOpVisibility != state) { mAppOpVisibility = state; if (state) { @@ -1102,11 +1096,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { // ops modifies they should only be hidden by policy due to the // lock screen, and the user won't be changing this if locked. // Plus it will quickly be fixed the next time we do a layout. - showLw(true, true); + showLw(true, false); } else { - hideLw(true, true); + hideLw(true, false); } + return true; } + return false; } @Override diff --git a/services/java/com/android/server/wm/WindowToken.java b/services/java/com/android/server/wm/WindowToken.java index 22671234c5384..bd0ace89e49d1 100644 --- a/services/java/com/android/server/wm/WindowToken.java +++ b/services/java/com/android/server/wm/WindowToken.java @@ -19,6 +19,7 @@ package com.android.server.wm; import android.os.IBinder; import java.io.PrintWriter; +import java.util.ArrayList; /** * Container of a set of related windows in the window manager. Often this diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 03871f63db201..746ac06223008 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -222,7 +222,37 @@ public class WindowManagerPermissionTests extends TestCase { } catch (RemoteException e) { fail("Unexpected remote exception"); } - } + + try { + mWm.moveAppToken(0, null); + fail("IWindowManager.moveAppToken did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + + try { + mWm.moveAppTokensToTop(null); + fail("IWindowManager.moveAppTokensToTop did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + + try { + mWm.moveAppTokensToBottom(null); + fail("IWindowManager.moveAppTokensToBottom did not throw SecurityException as" + + " expected"); + } catch (SecurityException e) { + // expected + } catch (RemoteException e) { + fail("Unexpected remote exception"); + } + } @SmallTest public void testDISABLE_KEYGUARD() { diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 278413e4395e6..59ae1a1d48175 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -209,6 +209,24 @@ public class IWindowManagerImpl implements IWindowManager { return false; } + @Override + public void moveAppToken(int arg0, IBinder arg1) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public void moveAppTokensToBottom(List arg0) throws RemoteException { + // TODO Auto-generated method stub + + } + + @Override + public void moveAppTokensToTop(List arg0) throws RemoteException { + // TODO Auto-generated method stub + + } + @Override public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1) throws RemoteException { From bf3a312eb6ea33fa0f89a289126b536c779b8e48 Mon Sep 17 00:00:00 2001 From: Derek Sollenberger Date: Thu, 28 Feb 2013 11:18:50 -0500 Subject: [PATCH 12/76] fix issue when replacement bitmap is larger than original. getTotalClip() returns the canvas bounds even if no explicit clip was set. This CL fixes that issue by only transfering clips that were explicitly set to the new canvas. bug: 8255582 Change-Id: I0144d430e7718151ad93d988fcf20b412f74b256 --- core/jni/android/graphics/Canvas.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 6410bc33dda83..6640555772d61 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -54,6 +54,21 @@ static uint32_t get_thread_msec() { namespace android { +class ClipCopier : public SkCanvas::ClipVisitor { +public: + ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {} + + virtual void clipRect(const SkRect& rect, SkRegion::Op op, bool antialias) { + m_dstCanvas->clipRect(rect, op, antialias); + } + virtual void clipPath(const SkPath& path, SkRegion::Op op, bool antialias) { + m_dstCanvas->clipPath(path, op, antialias); + } + +private: + SkCanvas* m_dstCanvas; +}; + class SkCanvasGlue { public: @@ -68,13 +83,15 @@ public: static void copyCanvasState(JNIEnv* env, jobject clazz, SkCanvas* srcCanvas, SkCanvas* dstCanvas) { if (srcCanvas && dstCanvas) { - if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) { - dstCanvas->clipRegion(srcCanvas->getTotalClip()); - } dstCanvas->setMatrix(srcCanvas->getTotalMatrix()); + if (NULL != srcCanvas->getDevice() && NULL != dstCanvas->getDevice()) { + ClipCopier copier(dstCanvas); + srcCanvas->replayClips(&copier); + } } } + static void freeCaches(JNIEnv* env, jobject) { // these are called in no particular order SkImageRef_GlobalPool::SetRAMUsed(0); From 65742dd1eee9024f46a6bf5944690dbbafac74ea Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Thu, 28 Feb 2013 11:25:39 -0800 Subject: [PATCH 13/76] Account for text alignment in Op bounds calculation bug:8243821 Previously this wasn't done for deferred clipping + reordering, so non-left-aligned text would be clipped at defer time, when it wouldn't have been at draw time (in OpenGLRenderer::quickReject()) Change-Id: Ic96949c2dca4378f284606b37d9411ed42f8d203 --- libs/hwui/DisplayListOp.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 9db8fe8d158c2..9ecfb5a11f13a 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1140,9 +1140,20 @@ public: const float* positions, SkPaint* paint, float length) : DrawBoundedOp(paint), mText(text), mBytesCount(bytesCount), mCount(count), mX(x), mY(y), mPositions(positions), mLength(length) { + // duplicates bounds calculation from OpenGLRenderer::drawText, but doesn't alter mX SkPaint::FontMetrics metrics; paint->getFontMetrics(&metrics, 0.0f); - mLocalBounds.set(mX, mY + metrics.fTop, mX + length, mY + metrics.fBottom); + switch (paint->getTextAlign()) { + case SkPaint::kCenter_Align: + x -= length / 2.0f; + break; + case SkPaint::kRight_Align: + x -= length; + break; + default: + break; + } + mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom); } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level, From 7b720ac67c3cf7feb4d613dacb9ae2718a6b4184 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 28 Feb 2013 12:15:35 -0800 Subject: [PATCH 14/76] Support 3D rotations when drawing text If a perspective transform is set on the Canvas, drawText() should not attempt to rasterize glyphs in screen space. This change uses the old behavior instead (i.e. rasterize the glyphs at the native font size and apply the transform on the resulting mesh.) This change also adds an optimization: empty glyphs (spaces) do not generate vertices anymore. This saves a lot of vertices in text heavy applications such as Gmail. Change-Id: Ib531384163f5165b5785501612a7b1474f3ff599 --- libs/hwui/DisplayListRenderer.cpp | 8 +- libs/hwui/FontRenderer.cpp | 10 +++ libs/hwui/OpenGLRenderer.cpp | 25 +++--- libs/hwui/font/Font.cpp | 81 ++++++++++++++----- libs/hwui/font/Font.h | 3 + tests/HwAccelerationTest/AndroidManifest.xml | 10 +++ .../test/hwui/Rotate3dTextActivity.java | 64 +++++++++++++++ 7 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 710f12fe04f20..16218fa5f3c62 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -406,7 +406,7 @@ status_t DisplayListRenderer::drawTextOnPath(const char* text, int bytesCount, i if (addDrawOp(op)) { // precache if draw operation is visible FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); - fontRenderer.precache(paint, text, count, *mSnapshot->transform); + fontRenderer.precache(paint, text, count, mat4::identity()); } return DrawGlInfo::kStatusDone; } @@ -423,7 +423,7 @@ status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int if (addDrawOp(op)) { // precache if draw operation is visible FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); - fontRenderer.precache(paint, text, count, *mSnapshot->transform); + fontRenderer.precache(paint, text, count, mat4::identity()); } return DrawGlInfo::kStatusDone; } @@ -442,7 +442,9 @@ status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int cou if (addDrawOp(op)) { // precache if draw operation is visible FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); - fontRenderer.precache(paint, text, count, *mSnapshot->transform); + const bool pureTranslate = mSnapshot->transform->isPureTranslate(); + fontRenderer.precache(paint, text, count, + pureTranslate ? mat4::identity() : *mSnapshot->transform); } return DrawGlInfo::kStatusDone; } diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index db65b8875982c..d5ea0f9c11650 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -180,7 +180,17 @@ CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { checkInit(); + + // If the glyph bitmap is empty let's assum the glyph is valid + // so we can avoid doing extra work later on + if (glyph.fWidth == 0 || glyph.fHeight == 0) { + cachedGlyph->mIsValid = true; + cachedGlyph->mCacheTexture = NULL; + return; + } + cachedGlyph->mIsValid = false; + // If the glyph is too tall, don't cache it if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheTextures[mCacheTextures.size() - 1]->getHeight()) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index fb77ef610fd02..7e9734f9e57de 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2151,17 +2151,17 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const alpha *= mSnapshot->alpha; - mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); - if (!texture) return DrawGlInfo::kStatusDone; - const AutoTexture autoCleanup(texture); - texture->setWrap(GL_CLAMP_TO_EDGE, true); - texture->setFilter(GL_LINEAR, true); - const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(), right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors); if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { + mCaches.activeTexture(0); + Texture* texture = mCaches.textureCache.get(bitmap); + if (!texture) return DrawGlInfo::kStatusDone; + const AutoTexture autoCleanup(texture); + texture->setWrap(GL_CLAMP_TO_EDGE, true); + texture->setFilter(GL_LINEAR, true); + const bool pureTranslate = mSnapshot->transform->isPureTranslate(); // Mark the current layer dirty where we are going to draw the patch if (hasLayer() && mesh->hasEmptyQuads) { @@ -2666,6 +2666,7 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, const float oldX = x; const float oldY = y; const bool pureTranslate = mSnapshot->transform->isPureTranslate(); + const bool isPerspective = mSnapshot->transform->isPerspective(); if (CC_LIKELY(pureTranslate)) { x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f); @@ -2687,8 +2688,7 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform); // Pick the appropriate texture filtering - bool linearFilter = !mSnapshot->transform->isPureTranslate() || - fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f; + bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f; // The font renderer will always use texture unit 0 mCaches.activeTexture(0); @@ -2701,13 +2701,13 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, setupDrawShader(); setupDrawBlending(true, mode); setupDrawProgram(); - setupDrawModelView(x, y, x, y, true, true); + setupDrawModelView(x, y, x, y, !isPerspective, true); // See comment above; the font renderer must use texture unit 0 // assert(mTextureUnit == 0) setupDrawTexture(fontRenderer.getTexture(linearFilter)); setupDrawPureColorUniforms(); setupDrawColorFilterUniforms(); - setupDrawShaderUniforms(true); + setupDrawShaderUniforms(!isPerspective); setupDrawTextGammaUniforms(); const Rect* clip = mSnapshot->hasPerspectiveTransform() ? NULL : mSnapshot->clipRect; @@ -2727,6 +2727,9 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, } if (status && hasActiveLayer) { + if (isPerspective) { + mSnapshot->transform->mapRect(bounds); + } dirtyLayerUnchecked(bounds, getRegion()); } diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index ea9fd031e7eb4..d48b6128cc2bf 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -52,6 +52,7 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) mStyle = paint->getStyle(); mStrokeWidth = paint->getStrokeWidth(); mAntiAliasing = paint->isAntiAlias(); + mLookupTransform.reset(); mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX]; mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY]; mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX]; @@ -165,7 +166,7 @@ void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { float nPenX = x + glyph->mBitmapLeft; - float nPenY = y + (glyph->mBitmapTop + glyph->mBitmapHeight); + float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight; float width = (float) glyph->mBitmapWidth; float height = (float) glyph->mBitmapHeight; @@ -181,6 +182,38 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, nPenX, nPenY - height, u1, v1, glyph->mCacheTexture); } +void Font::drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y, + uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { + SkMatrix i; + if (!mDescription.mLookupTransform.invert(&i)) { + return; + } + + SkPoint p[4]; + p[0].set(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight); + p[1].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight); + p[2].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop); + p[3].set(glyph->mBitmapLeft, glyph->mBitmapTop); + + i.mapPoints(p, 4); + + p[0].offset(x, y); + p[1].offset(x, y); + p[2].offset(x, y); + p[3].offset(x, y); + + float u1 = glyph->mBitmapMinU; + float u2 = glyph->mBitmapMaxU; + float v1 = glyph->mBitmapMinV; + float v2 = glyph->mBitmapMaxV; + + mState->appendRotatedMeshQuad( + p[0].fX, p[0].fY, u1, v2, + p[1].fX, p[1].fY, u2, v2, + p[2].fX, p[2].fY, u2, v1, + p[3].fX, p[3].fY, u1, v1, glyph->mCacheTexture); +} + void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { int nPenX = x + glyph->mBitmapLeft; @@ -307,7 +340,7 @@ void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); prevRsbDelta = cachedGlyph->mRsbDelta; - if (cachedGlyph->mIsValid) { + if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) { drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent); } @@ -328,7 +361,6 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le } void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { - if (numGlyphs == 0 || text == NULL) { return; } @@ -357,14 +389,18 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len static RenderGlyph gRenderGlyph[] = { &android::uirenderer::Font::drawCachedGlyph, + &android::uirenderer::Font::drawCachedGlyphPerspective, &android::uirenderer::Font::drawCachedGlyphBitmap, + &android::uirenderer::Font::drawCachedGlyphBitmap, + &android::uirenderer::Font::measureCachedGlyph, &android::uirenderer::Font::measureCachedGlyph }; - RenderGlyph render = gRenderGlyph[mode]; + RenderGlyph render = gRenderGlyph[(mode << 1) + mTransform.isPerspective()]; text += start; int glyphsCount = 0; + const bool applyTransform = !mTransform.isIdentity() && !mTransform.isPerspective(); const SkPaint::Align align = paint->getTextAlign(); while (glyphsCount < numGlyphs) { @@ -377,12 +413,13 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); - // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage - if (cachedGlyph->mIsValid) { + // If it's still not valid, we couldn't cache it, so we shouldn't + // draw garbage; also skip empty glyphs (spaces) + if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) { float penX = x + positions[(glyphsCount << 1)]; float penY = y + positions[(glyphsCount << 1) + 1]; - if (!mTransform.isIdentity()) { + if (applyTransform) { mTransform.mapPoint(penX, penY); } @@ -424,15 +461,18 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp glyph->mBitmapWidth = skiaGlyph.fWidth; glyph->mBitmapHeight = skiaGlyph.fHeight; - uint32_t cacheWidth = glyph->mCacheTexture->getWidth(); - uint32_t cacheHeight = glyph->mCacheTexture->getHeight(); + bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0; + if (!empty) { + uint32_t cacheWidth = glyph->mCacheTexture->getWidth(); + uint32_t cacheHeight = glyph->mCacheTexture->getHeight(); - glyph->mBitmapMinU = startX / (float) cacheWidth; - glyph->mBitmapMinV = startY / (float) cacheHeight; - glyph->mBitmapMaxU = endX / (float) cacheWidth; - glyph->mBitmapMaxV = endY / (float) cacheHeight; + glyph->mBitmapMinU = startX / (float) cacheWidth; + glyph->mBitmapMinV = startY / (float) cacheHeight; + glyph->mBitmapMaxU = endX / (float) cacheWidth; + glyph->mBitmapMaxV = endY / (float) cacheHeight; - mState->setTextureDirty(); + mState->setTextureDirty(); + } } CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) { @@ -440,8 +480,8 @@ CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching mCachedGlyphs.add(glyph, newGlyph); const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform); - newGlyph->mGlyphIndex = skiaGlyph.fID; newGlyph->mIsValid = false; + newGlyph->mGlyphIndex = skiaGlyph.fID; updateGlyphCache(paint, skiaGlyph, newGlyph, precaching); @@ -452,14 +492,13 @@ Font* Font::create(FontRenderer* state, const SkPaint* paint, const mat4& matrix FontDescription description(paint, matrix); Font* font = state->mActiveFonts.get(description); - if (font) { - font->mTransform.load(matrix); - return font; + if (!font) { + font = new Font(state, description); + state->mActiveFonts.put(description, font); } + font->mTransform.load(matrix); - Font* newFont = new Font(state, description); - state->mActiveFonts.put(description, newFont); - return newFont; + return font; } }; // namespace uirenderer diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 26b10afd4be8d..542b552640f1c 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -124,6 +124,9 @@ private: void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos); + void drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y, + uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, + Rect* bounds, const float* pos); void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos); diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 7c7d10e02a82a..e1027cee24dcf 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -42,6 +42,16 @@ + + + + + + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java new file mode 100644 index 0000000000000..93b8705baa144 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java @@ -0,0 +1,64 @@ +/* + * 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.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Bundle; +import android.view.View; + +@SuppressWarnings({"UnusedDeclaration"}) +public class Rotate3dTextActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Rotate3dTextView view = new Rotate3dTextView(this); + setContentView(view); + } + + public static class Rotate3dTextView extends View { + private static final String TEXT = "Hello libhwui! "; + + private final Paint mPaint; + + public Rotate3dTextView(Context c) { + super(c); + + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setTextSize(50.0f); + mPaint.setTextAlign(Paint.Align.CENTER); + + setRotationY(45.0f); + setScaleX(2.0f); + setScaleY(2.0f); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawText(TEXT, getWidth() / 2.0f, getHeight() / 2.0f, mPaint); + + invalidate(); + } + } +} From 9becfc168b612805e24619dd6267c62aad001880 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 28 Feb 2013 18:13:54 -0800 Subject: [PATCH 15/76] Restore the ability to track native Surface changes Bug #8230990 ViewRootImpl needs to know when the native Surface objects changes to recreate the EGL surface. A recent refactoring in Surface broke the behavior of getGenerationId(). This simply restores the old behavior (every change increments the generation ID by 1.) Change-Id: Ife1df1ffb2ee7a373b8ebf2431192702ba10f344 --- core/java/android/view/Surface.java | 34 ++++++++--------------------- core/jni/android_view_Surface.cpp | 3 --- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 03a9b094c5610..9955bc18bc212 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -74,17 +74,13 @@ public class Surface implements Parcelable { int mNativeObject; // package scope only for SurfaceControl access private int mGenerationId; // incremented each time mNativeSurface changes + @SuppressWarnings("UnusedDeclaration") private final Canvas mCanvas = new CompatibleCanvas(); - // The Translator for density compatibility mode. This is used for scaling - // the canvas to perform the appropriate density transformation. - private Translator mCompatibilityTranslator; - // A matrix to scale the matrix set by application. This is set to null for // non compatibility mode. private Matrix mCompatibleMatrix; - /** * Rotation constant: 0 degree rotation (natural orientation) */ @@ -105,8 +101,6 @@ public class Surface implements Parcelable { */ public static final int ROTATION_270 = 3; - - /** * Create an empty surface, which will later be filled in by readFromParcel(). * @hide @@ -169,6 +163,7 @@ public class Surface implements Parcelable { if (mNativeObject != 0) { nativeRelease(mNativeObject); mNativeObject = 0; + mGenerationId++; } mCloseGuard.close(); } @@ -183,6 +178,7 @@ public class Surface implements Parcelable { if (mNativeObject != 0) { nativeDestroy(mNativeObject); mNativeObject = 0; + mGenerationId++; } mCloseGuard.close(); } @@ -291,6 +287,7 @@ public class Surface implements Parcelable { "SurfaceControl native object is null. Are you using a released SurfaceControl?"); } mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject); + mGenerationId++; } /** @@ -312,7 +309,10 @@ public class Surface implements Parcelable { } // transfer the reference from other to us mNativeObject = other.mNativeObject; + mGenerationId++; + other.mNativeObject = 0; + other.mGenerationId++; } } @@ -327,6 +327,7 @@ public class Surface implements Parcelable { } mName = source.readString(); mNativeObject = nativeReadFromParcel(mNativeObject, source); + mGenerationId++; } @Override @@ -404,24 +405,6 @@ public class Surface implements Parcelable { // A temp matrix to remember what an application obtained via {@link getMatrix} private Matrix mOrigMatrix = null; - @Override - public int getWidth() { - int w = super.getWidth(); - if (mCompatibilityTranslator != null) { - w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f); - } - return w; - } - - @Override - public int getHeight() { - int h = super.getHeight(); - if (mCompatibilityTranslator != null) { - h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f); - } - return h; - } - @Override public void setMatrix(Matrix matrix) { if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) { @@ -435,6 +418,7 @@ public class Surface implements Parcelable { } } + @SuppressWarnings("deprecation") @Override public void getMatrix(Matrix m) { super.getMatrix(m); diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 02e76e5ad3a25..1ffb1b877e631 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -53,7 +53,6 @@ static const char* const OutOfResourcesException = static struct { jclass clazz; jfieldID mNativeObject; - jfieldID mGenerationId; jfieldID mCanvas; jmethodID ctor; } gSurfaceClassInfo; @@ -384,8 +383,6 @@ int register_android_view_Surface(JNIEnv* env) gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz)); gSurfaceClassInfo.mNativeObject = env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeObject", "I"); - gSurfaceClassInfo.mGenerationId = - env->GetFieldID(gSurfaceClassInfo.clazz, "mGenerationId", "I"); gSurfaceClassInfo.mCanvas = env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;"); gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "", "(I)V"); From beb7105d4b78e5e1995fd1afea919307d7537572 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 4 Mar 2013 18:17:50 -0800 Subject: [PATCH 16/76] Fix issue #8311263: Corrupted UI across all tabs in People app The problem is that there are other configurations that the action bar can be in that ActionBarOverlayLayout didn't account for -- such as here, the nav part visible but the rest hidden. Fixing this was non-trivial because it means that to correctly implement fitSystemWindows() we need to in these cases take the actual measured height of the action bar for positioning the content view... but that is not yet available, since fitSystemWindows() must run before layout. To solve this, ActionBarOverlayLayout now inherits directly from ViewGroup and implements its own custom layout. In its measure pass it does all of the fitSystemWindows() work that is dependent on the measured sizes of the action bar child views after those are measured and applies them to the content view before it is measured. Change-Id: Ie327075d502e9c348aa80b0968c6b0403478301e --- .../widget/ActionBarOverlayLayout.java | 227 ++++++++++++++---- core/res/res/layout/screen_action_bar.xml | 4 +- 2 files changed, 184 insertions(+), 47 deletions(-) diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 482eba71e959a..699fa6ed6e472 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -16,33 +16,42 @@ package com.android.internal.widget; +import android.view.ViewGroup; import com.android.internal.app.ActionBarImpl; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.View; -import android.widget.FrameLayout; /** * Special layout for the containing of an overlay action bar (and its * content) to correctly handle fitting system windows when the content * has request that its layout ignore them. */ -public class ActionBarOverlayLayout extends FrameLayout { +public class ActionBarOverlayLayout extends ViewGroup { private int mActionBarHeight; private ActionBarImpl mActionBar; private int mWindowVisibility = View.VISIBLE; + + // The main UI elements that we handle the layout of. private View mContent; private View mActionBarTop; + private View mActionBarBottom; + + // Some interior UI elements. private ActionBarContainer mContainerView; private ActionBarView mActionView; - private View mActionBarBottom; + private boolean mOverlayMode; private int mLastSystemUiVisibility; - private final Rect mLocalInsets = new Rect(); + private final Rect mBaseContentInsets = new Rect(); + private final Rect mLastBaseContentInsets = new Rect(); + private final Rect mContentInsets = new Rect(); + private final Rect mBaseInnerInsets = new Rect(); + private final Rect mInnerInsets = new Rect(); + private final Rect mLastInnerInsets = new Rect(); static final int[] mActionBarSizeAttr = new int [] { com.android.internal.R.attr.actionBarSize @@ -139,7 +148,7 @@ public class ActionBarOverlayLayout extends FrameLayout { private boolean applyInsets(View view, Rect insets, boolean left, boolean top, boolean bottom, boolean right) { boolean changed = false; - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)view.getLayoutParams(); + LayoutParams lp = (LayoutParams)view.getLayoutParams(); if (left && lp.leftMargin != insets.left) { changed = true; lp.leftMargin = insets.left; @@ -168,54 +177,165 @@ public class ActionBarOverlayLayout extends FrameLayout { // The top and bottom action bars are always within the content area. boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true); - if (mActionBarBottom != null) { - changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); - } + changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); - int topSpace = 0; - if (stable || mActionBarTop.getVisibility() == VISIBLE) { - // This is the space needed on top of the window for the action bar. - topSpace = mActionBarHeight; + mBaseInnerInsets.set(insets); + computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets); + if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { + changed = true; + mLastBaseContentInsets.set(mBaseContentInsets); } - if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) { - View tabs = mContainerView.getTabContainer(); - if (tabs != null && (stable || tabs.getVisibility() == VISIBLE)) { - // If tabs are not embedded, increase space on top to account for them. - topSpace += mActionBarHeight; - } - } - - int bottomSpace = 0; - if (mActionView.isSplitActionBar()) { - if ((mActionBarBottom != null - && (stable || mActionBarBottom.getVisibility() == VISIBLE))) { - // If action bar is split, adjust bottom insets for it. - bottomSpace = mActionBarHeight; - } - } - - // If the window has not requested system UI layout flags, we need to - // make sure its content is not being covered by system UI... though it - // will still be covered by the action bar since they have requested it to - // overlay. - boolean res = computeFitSystemWindows(insets, mLocalInsets); - if (!mOverlayMode && !stable) { - mLocalInsets.top += topSpace; - mLocalInsets.bottom += bottomSpace; - } else { - insets.top += topSpace; - insets.bottom += bottomSpace; - } - changed |= applyInsets(mContent, mLocalInsets, true, true, true, true); if (changed) { requestLayout(); } - super.fitSystemWindows(insets); + // We don't do any more at this point. To correctly compute the content/inner + // insets in all cases, we need to know the measured size of the various action + // bar elements. fitSystemWindows() happens before the measure pass, so we can't + // do that here. Instead we will take this up in onMeasure(). return true; } + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + int topInset = 0; + int bottomInset = 0; + + measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0); + LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState()); + + measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); + lp = (LayoutParams) mActionBarBottom.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState()); + + final int vis = getWindowSystemUiVisibility(); + final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + + if (stable) { + // This is the standard space needed for the action bar. For stable measurement, + // we can't depend on the size currently reported by it -- this must remain constant. + topInset = mActionBarHeight; + if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) { + View tabs = mContainerView.getTabContainer(); + if (tabs != null) { + // If tabs are not embedded, increase space on top to account for them. + topInset += mActionBarHeight; + } + } + } else if (mActionBarTop.getVisibility() == VISIBLE) { + // This is the space needed on top of the window for all of the action bar + // and tabs. + topInset = mActionBarTop.getMeasuredHeight(); + } + + if (mActionView.isSplitActionBar()) { + // If action bar is split, adjust bottom insets for it. + if (stable) { + bottomInset = mActionBarHeight; + } else { + bottomInset = mActionBarBottom.getMeasuredHeight(); + } + } + + // If the window has not requested system UI layout flags, we need to + // make sure its content is not being covered by system UI... though it + // will still be covered by the action bar if they have requested it to + // overlay. + mContentInsets.set(mBaseContentInsets); + mInnerInsets.set(mBaseInnerInsets); + if (!mOverlayMode && !stable) { + mContentInsets.top += topInset; + mContentInsets.bottom += bottomInset; + } else { + mInnerInsets.top += topInset; + mInnerInsets.bottom += bottomInset; + } + applyInsets(mContent, mContentInsets, true, true, true, true); + + if (!mLastInnerInsets.equals(mInnerInsets)) { + // If the inner insets have changed, we need to dispatch this down to + // the app's fitSystemWindows(). We do this before measuring the content + // view to keep the same semantics as the normal fitSystemWindows() call. + mLastInnerInsets.set(mInnerInsets); + super.fitSystemWindows(mInnerInsets); + } + + measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); + lp = (LayoutParams) mContent.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, mContent.getMeasuredState()); + + // Account for padding too + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int count = getChildCount(); + + final int parentLeft = getPaddingLeft(); + final int parentRight = right - left - getPaddingRight(); + + final int parentTop = getPaddingTop(); + final int parentBottom = bottom - top - getPaddingBottom(); + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + final int width = child.getMeasuredWidth(); + final int height = child.getMeasuredHeight(); + + int childLeft = parentLeft + lp.leftMargin; + int childTop; + if (child == mActionBarBottom) { + childTop = parentBottom - height - lp.bottomMargin; + } else { + childTop = parentTop + lp.topMargin; + } + + child.layout(childLeft, childTop, childLeft + width, childTop + height); + } + } + } + void pullChildren() { if (mContent == null) { mContent = findViewById(com.android.internal.R.id.content); @@ -226,4 +346,23 @@ public class ActionBarOverlayLayout extends FrameLayout { mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar); } } + + + public static class LayoutParams extends MarginLayoutParams { + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + public LayoutParams(ViewGroup.MarginLayoutParams source) { + super(source); + } + } } diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml index 95519c6d48c66..e310bf5752b0a 100644 --- a/core/res/res/layout/screen_action_bar.xml +++ b/core/res/res/layout/screen_action_bar.xml @@ -29,8 +29,7 @@ This is an optimized layout for a screen with the Action Bar enabled. android:layout_height="match_parent" /> + android:layout_height="wrap_content"> From 1a1921ddbe0a95148a5a2ccf3bca93242de6dd33 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Tue, 5 Mar 2013 13:36:21 -0500 Subject: [PATCH 17/76] Defend against NaNs in panel animation code. Once these get into the Animators, they freeze up and disable the whole notification panel. Not cool. Bug: 7686690 Change-Id: I04567417b3840a82d9cfe071c601e3078b2e3fe3 --- .../systemui/statusbar/phone/PanelView.java | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 988951c3eaa09..e351429aa0e45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Iterator; import android.animation.ObjectAnimator; @@ -30,7 +29,6 @@ import android.content.res.Resources; import android.util.AttributeSet; import android.util.Slog; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.View; import android.widget.FrameLayout; @@ -39,6 +37,9 @@ import com.android.systemui.R; public class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); + + public static final boolean DEBUG_NAN = true; // http://b/7686690 + public final void LOG(String fmt, Object... args) { if (!DEBUG) return; Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); @@ -141,8 +142,17 @@ public class PanelView extends FrameLayout { last = event; i++; } - mVX /= totalweight; - mVY /= totalweight; + if (totalweight > 0) { + mVX /= totalweight; + mVY /= totalweight; + } else { + if (DEBUG_NAN) { + Slog.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0", + new Throwable()); + } + // so as not to contaminate the velocities with NaN + mVX = mVY = 0; + } if (FlingTracker.DEBUG) { Slog.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY); @@ -150,15 +160,19 @@ public class PanelView extends FrameLayout { } public float getXVelocity() { if (Float.isNaN(mVX)) { - Slog.v("FlingTracker", "warning: vx=NaN"); - // XXX: should return 0 + if (DEBUG_NAN) { + Slog.v("FlingTracker", "warning: vx=NaN"); + } + mVX = 0; } return mVX; } public float getYVelocity() { if (Float.isNaN(mVY)) { - Slog.v("FlingTracker", "warning: vx=NaN"); - // XXX: should return 0 + if (DEBUG_NAN) { + Slog.v("FlingTracker", "warning: vx=NaN"); + } + mVY = 0; } return mVY; } @@ -531,8 +545,12 @@ public class PanelView extends FrameLayout { public void setExpandedHeightInternal(float h) { if (Float.isNaN(h)) { - Slog.v(TAG, "setExpandedHeightInternal: warning: h=NaN"); - // XXX: should set h to 0 + // If a NaN gets in here, it will freeze the Animators. + if (DEBUG_NAN) { + Slog.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead", + new Throwable()); + } + h = 0; } float fh = getFullHeight(); @@ -566,8 +584,12 @@ public class PanelView extends FrameLayout { public void setExpandedFraction(float frac) { if (Float.isNaN(frac)) { - Slog.v(TAG, "setExpandedFraction: frac=NaN"); - // XXX: set frac to 0 to defend + // If a NaN gets in here, it will freeze the Animators. + if (DEBUG_NAN) { + Slog.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead", + new Throwable()); + } + frac = 0; } setExpandedHeight(getFullHeight() * frac); } From f73f9aa207357fd420a8320ea9be913c1e76d254 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Wed, 6 Mar 2013 10:22:16 -0800 Subject: [PATCH 18/76] Add window leak check and possible recovery. Search output for "!!! LEAK !!!" For bug 8322020. Change-Id: I6db572a1c7fe0e6b386ccb0dba08bc376659a75a --- services/java/com/android/server/wm/WindowState.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index a0ed53001e003..506fcecfa51b4 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -997,6 +997,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { mService.removeWindowLocked(mSession, win); + } else if (WindowState.this.mHasSurface) { + Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid."); + mService.removeWindowLocked(mSession, WindowState.this); } } } catch (IllegalArgumentException ex) { From ff85f58a792f7b6cb125067d3a0999cd266ed696 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Wed, 6 Mar 2013 11:33:26 -0800 Subject: [PATCH 19/76] Fix issue #8325007: EyeEm app crashes on launch- NPE at... ...android.view.ViewGroup.measureChildWithMargins The app is doing grungy stuff with trying to insert its own views inside the window decor. This new custom layout of the action bar was assuming it would get fitSystemWindows() called at which point it would retrieve all of its child views... but with the app doing this, it blocks the call down to fitSystemWindows() and breaks us. So we now make the layout manager more robust and ensure it has retrieved its children when measuring. Also fix an issue where the xlarge layouts were not updated. Change-Id: I6c69f26f26b59a6aa8fa1e5788288ffce0b490ca --- .../widget/ActionBarOverlayLayout.java | 37 +++++++----- .../res/layout-xlarge/screen_action_bar.xml | 60 +++++++++++-------- .../screen_action_bar_overlay.xml | 58 ------------------ core/res/res/values/symbols.xml | 1 - 4 files changed, 56 insertions(+), 100 deletions(-) delete mode 100644 core/res/res/layout-xlarge/screen_action_bar_overlay.xml diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 79dadd75581bd..f3591463e2838 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -73,10 +73,6 @@ public class ActionBarOverlayLayout extends ViewGroup { ta.recycle(); } - public void setOverlayMode(boolean mode) { - mOverlayMode = mode; - } - public void setActionBar(ActionBarImpl impl, boolean overlayMode) { mActionBar = impl; mOverlayMode = overlayMode; @@ -177,7 +173,9 @@ public class ActionBarOverlayLayout extends ViewGroup { // The top and bottom action bars are always within the content area. boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true); - changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); + if (mActionBarBottom != null) { + changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); + } mBaseInnerInsets.set(insets); computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets); @@ -219,6 +217,8 @@ public class ActionBarOverlayLayout extends ViewGroup { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + pullChildren(); + int maxHeight = 0; int maxWidth = 0; int childState = 0; @@ -234,13 +234,16 @@ public class ActionBarOverlayLayout extends ViewGroup { mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState()); - measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); - lp = (LayoutParams) mActionBarBottom.getLayoutParams(); - maxWidth = Math.max(maxWidth, - mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); - maxHeight = Math.max(maxHeight, - mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); - childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState()); + // xlarge screen layout doesn't have bottom action bar. + if (mActionBarBottom != null) { + measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); + lp = (LayoutParams) mActionBarBottom.getLayoutParams(); + maxWidth = Math.max(maxWidth, + mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState()); + } final int vis = getWindowSystemUiVisibility(); final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; @@ -264,10 +267,12 @@ public class ActionBarOverlayLayout extends ViewGroup { if (mActionView.isSplitActionBar()) { // If action bar is split, adjust bottom insets for it. - if (stable) { - bottomInset = mActionBarHeight; - } else { - bottomInset = mActionBarBottom.getMeasuredHeight(); + if (mActionBarBottom != null) { + if (stable) { + bottomInset = mActionBarHeight; + } else { + bottomInset = mActionBarBottom.getMeasuredHeight(); + } } } diff --git a/core/res/res/layout-xlarge/screen_action_bar.xml b/core/res/res/layout-xlarge/screen_action_bar.xml index 0b6122d5f1492..4f286780104fa 100644 --- a/core/res/res/layout-xlarge/screen_action_bar.xml +++ b/core/res/res/layout-xlarge/screen_action_bar.xml @@ -15,33 +15,43 @@ --> - - - - - - + android:layout_height="match_parent" /> + + + + + + + + diff --git a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml deleted file mode 100644 index a95635e6a6622..0000000000000 --- a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c87cb27ee31c4..140ff704f2771 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1363,7 +1363,6 @@ - From 20a6b3b9a431be44b04bd46fc64117f5061d15a8 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Wed, 6 Mar 2013 19:09:59 -0800 Subject: [PATCH 20/76] Round scale factors to rasterize text Harder, better, faster, stronger. Change-Id: Iee4125de98c4e61603f56bb7f06002cc86458214 --- libs/hwui/DisplayList.cpp | 3 +-- libs/hwui/font/Font.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index bdd539e6a8ed2..5781f4d906a1f 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -442,9 +442,8 @@ status_t DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flag for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) { DisplayListOp *op = mDisplayListData->displayListOps[i]; #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS - Caches::getInstance().eventMark(strlen(op->name()), op->name()); + renderer.eventMark(strlen(op->name()), op->name()); #endif - drawGlStatus |= op->replay(renderer, dirty, flags, saveCount, level, mCaching, mMultipliedAlpha, deferredList); logBuffer.writeCommand(level, op->name()); diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 9307f115392d5..c932087fe4c61 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -53,8 +53,8 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) mStrokeWidth = paint->getStrokeWidth(); mAntiAliasing = paint->isAntiAlias(); mLookupTransform.reset(); - mLookupTransform[SkMatrix::kMScaleX] = matrix[mat4::kScaleX]; - mLookupTransform[SkMatrix::kMScaleY] = matrix[mat4::kScaleY]; + mLookupTransform[SkMatrix::kMScaleX] = roundf(fmaxf(1.0f, matrix[mat4::kScaleX])); + mLookupTransform[SkMatrix::kMScaleY] = roundf(fmaxf(1.0f, matrix[mat4::kScaleY])); if (!mLookupTransform.invert(&mInverseLookupTransform)) { ALOGW("Could not query the inverse lookup transform for this font"); } From ef68af6942f955222cabd06a727f364f4b4f806c Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 7 Mar 2013 17:01:05 -0800 Subject: [PATCH 21/76] Don't apply scale to glyphs when a perspective is set. Bug #8337925 Magazines uses very large scale factors that blow up font cache & CPU. Change-Id: I1c0ed430ed91b86cc6c6f59f7e8bdcc89a464e46 --- libs/hwui/OpenGLRenderer.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 22ec93b1a5e5e..11fdd6cb50942 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2633,16 +2633,7 @@ mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const { fontTransform = mat4::identity(); } else { if (CC_UNLIKELY(transform.isPerspective())) { - // When the below condition is true, we are rendering text with a - // perspective transform inside a layer (either an inline layer - // created by Canvas.saveLayer() or a hardware layer.) - if (hasLayer() || getTargetFbo() != 0) { - float sx, sy; - currentTransform().decomposeScale(sx, sy); - fontTransform.loadScale(sx, sy, 1.0f); - } else { - fontTransform = mat4::identity(); - } + fontTransform = mat4::identity(); } else { float sx, sy; currentTransform().decomposeScale(sx, sy); From 3a4dc42b86ac411101dcf9fa2498617edddb50fe Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Mon, 11 Mar 2013 12:28:07 -0700 Subject: [PATCH 22/76] Add debug for b/8263462. Look for race condition in soft input method attach. For bug 8263462. Change-Id: Id0609a743d57ab685c036372100ddd33819bff03 --- .../java/android/inputmethodservice/InputMethodService.java | 2 ++ .../java/com/android/server/InputMethodManagerService.java | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 99624cc817fc8..d3cbb7ef5dd34 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -39,6 +39,7 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Slog; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -351,6 +352,7 @@ public class InputMethodService extends AbstractInputMethodService { * Take care of attaching the given window token provided by the system. */ public void attachToken(IBinder token) { + Slog.i(TAG, "attachToken: b8263462 Existing token=" + mToken + " new token=" + token); if (mToken == null) { mToken = token; mWindow.setToken(token); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 6ba5cffcaa669..e7ac6861acc31 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1199,7 +1199,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurId = info.getId(); mCurToken = new Binder(); try { - if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); + if (true || DEBUG) Slog.v(TAG, "b8263462 Adding window token: " + mCurToken); mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD); } catch (RemoteException e) { @@ -1243,7 +1243,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub unbindCurrentMethodLocked(false, false); return; } - if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); + if (true || DEBUG) Slog.v(TAG, "b8263462 Initiating attach with token: " + mCurToken); executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); if (mCurClient != null) { @@ -2319,7 +2319,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_ATTACH_TOKEN: args = (SomeArgs)msg.obj; try { - if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); + if (true || DEBUG) Slog.v(TAG, "b8263462 Sending attach of token: " + args.arg2); ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); } catch (RemoteException e) { } From e4ea9bc0ec169799059c2030364d23b6eaffeaed Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Mon, 11 Mar 2013 15:35:36 -0700 Subject: [PATCH 23/76] am a45bafc2: am c9e0cd2a: Merge "Fix DateFormat k and h, and implement K and H for the first time." * commit 'a45bafc2f025d2ef513f3e3dcac162ba6a95d575': Fix DateFormat k and h, and implement K and H for the first time. --- core/java/android/text/format/DateFormat.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index 6d61a5c52dd36..be4663d87385b 100644 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -46,7 +46,7 @@ import libcore.icu.LocaleData; *

The format methods in this class implement a subset of Unicode * UTS #35 patterns. * The subset supported by this class includes the following format characters: - * {@code acdEhkLMmsyz}. See {@link java.text.SimpleDateFormat} for more documentation + * {@code acdEHhLKkLMmsyz}. See {@link java.text.SimpleDateFormat} for more documentation * about patterns, or if you need a more compete implementation. */ public class DateFormat { @@ -191,7 +191,6 @@ public class DateFormat { public static java.text.DateFormat getDateFormatForSetting(Context context, String value) { String format = getDateFormatStringForSetting(context, value); - return new java.text.SimpleDateFormat(format); } @@ -441,12 +440,25 @@ public class DateFormat { replacement = getDayOfWeekString(localeData, inDate.get(Calendar.DAY_OF_WEEK), count, c); break; - case 'h': - int hour = inDate.get(Calendar.HOUR); - replacement = zeroPad(hour == 0 ? 24 : hour, count); + case 'K': // hour in am/pm (0-11) + case 'h': // hour in am/pm (1-12) + { + int hour = inDate.get(Calendar.HOUR); + if (c == 'h' && hour == 0) { + hour = 12; + } + replacement = zeroPad(hour, count); + } break; - case 'k': - replacement = zeroPad(inDate.get(Calendar.HOUR_OF_DAY), count); + case 'H': // hour in day (0-23) + case 'k': // hour in day (1-24) + { + int hour = inDate.get(Calendar.HOUR_OF_DAY); + if (c == 'k' && hour == 0) { + hour = 24; + } + replacement = zeroPad(hour, count); + } break; case 'L': case 'M': From fc4a2b175bfef304dd8d219b58e60ea6d5dbd786 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Tue, 12 Mar 2013 14:12:39 -0700 Subject: [PATCH 24/76] Remove old show/hide messages on new ime (DO NOT MERGE) The previous show/hide messages in the queue were still trying to be honored even after a new ime was attached. Fixes bug 8263462. Change-Id: Iee60dbd1d58542f73aedeac5ccb54cddeb5d5dfe --- .../java/com/android/server/InputMethodManagerService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 6ba5cffcaa669..01f75449c4300 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1243,6 +1243,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub unbindCurrentMethodLocked(false, false); return; } + // Remove commands relating to the previous service. Otherwise WindowManagerService + // will reject the command because the token attached to these messages is invalid. + mCaller.removeMessages(MSG_SHOW_SOFT_INPUT); + mCaller.removeMessages(MSG_HIDE_SOFT_INPUT); if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); From b2f89298b502dcca60028f0ba7917da6b6f1a067 Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Wed, 13 Mar 2013 10:17:41 -0700 Subject: [PATCH 25/76] Put debug back in for b/b8263462. Previous CL did not fix. Change-Id: Ief9e8f519480fa41248d53e5b0187c9657f00b79 --- .../inputmethodservice/InputMethodService.java | 6 ++++-- .../server/InputMethodManagerService.java | 16 +++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 99624cc817fc8..288cefff55df4 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -39,6 +39,7 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Slog; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -351,6 +352,7 @@ public class InputMethodService extends AbstractInputMethodService { * Take care of attaching the given window token provided by the system. */ public void attachToken(IBinder token) { + Slog.i(TAG, "attachToken: Existing token=" + mToken + " new token=" + token); if (mToken == null) { mToken = token; mWindow.setToken(token); @@ -417,7 +419,7 @@ public class InputMethodService extends AbstractInputMethodService { * Handle a request by the system to show the soft input area. */ public void showSoftInput(int flags, ResultReceiver resultReceiver) { - if (DEBUG) Log.v(TAG, "showSoftInput()"); + if (true || DEBUG) Slog.v(TAG, "showSoftInput()"); boolean wasVis = isInputViewShown(); mShowInputFlags = 0; if (onShowInputRequested(flags, false)) { @@ -1388,7 +1390,7 @@ public class InputMethodService extends AbstractInputMethodService { } public void showWindow(boolean showInput) { - if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + if (true || DEBUG) Slog.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested + " mWindowAdded=" + mWindowAdded + " mWindowCreated=" + mWindowCreated diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 01f75449c4300..d0048bf440a9e 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1247,7 +1247,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // will reject the command because the token attached to these messages is invalid. mCaller.removeMessages(MSG_SHOW_SOFT_INPUT); mCaller.removeMessages(MSG_HIDE_SOFT_INPUT); - if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); + if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); if (mCurClient != null) { @@ -1693,7 +1693,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); + if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(flags, resultReceiver); } } finally { @@ -1717,6 +1717,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean res = false; if (mCurMethod != null) { + if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken, + new RuntimeException("here").fillInStackTrace()); executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, resultReceiver)); @@ -1788,12 +1790,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { - if (DEBUG) Slog.v(TAG, + if (true ||DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); return false; } if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { - if (DEBUG) Slog.v(TAG, + if (true ||DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); return false; } @@ -2305,6 +2307,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_SHOW_SOFT_INPUT: args = (SomeArgs)msg.obj; try { + if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + + msg.arg1 + ", " + args.arg2 + ")"); ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); } catch (RemoteException e) { @@ -2314,6 +2318,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_SOFT_INPUT: args = (SomeArgs)msg.obj; try { + if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " + + args.arg2 + ")"); ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2); } catch (RemoteException e) { @@ -2323,7 +2329,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_ATTACH_TOKEN: args = (SomeArgs)msg.obj; try { - if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); + if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); } catch (RemoteException e) { } From 6fbe7ff1be07819998a67f283189316f97637f8e Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Thu, 14 Mar 2013 09:43:02 -0700 Subject: [PATCH 26/76] Remove WindowManager messages from remote queue. When a new IME is attached it is not enough to remove the WindowManager messages from the local queue, but the ones in the previous IME queue must also be removed. Fixes bug 8263462. Change-Id: I9e916c6052a83dc7691bcba0b6ab8328b9b7cc36 --- .../IInputMethodWrapper.java | 6 +++++ .../android/internal/view/IInputMethod.aidl | 26 ++++++++++--------- .../server/InputMethodManagerService.java | 19 +++++++++----- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 2d67875c6084e..d59c7b819df38 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -284,6 +284,12 @@ class IInputMethodWrapper extends IInputMethod.Stub flags, resultReceiver)); } + @Override + public void removeSoftInputMessages() { + mCaller.removeMessages(DO_SHOW_SOFT_INPUT); + mCaller.removeMessages(DO_HIDE_SOFT_INPUT); + } + public void changeInputMethodSubtype(InputMethodSubtype subtype) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE, subtype)); diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index c7fcab8ecd29d..c2a7fc73b0c33 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -33,26 +33,28 @@ import com.android.internal.view.IInputMethodSession; * Service). * {@hide} */ -oneway interface IInputMethod { - void attachToken(IBinder token); +interface IInputMethod { + oneway void attachToken(IBinder token); - void bindInput(in InputBinding binding); + oneway void bindInput(in InputBinding binding); - void unbindInput(); + oneway void unbindInput(); - void startInput(in IInputContext inputContext, in EditorInfo attribute); + oneway void startInput(in IInputContext inputContext, in EditorInfo attribute); - void restartInput(in IInputContext inputContext, in EditorInfo attribute); + oneway void restartInput(in IInputContext inputContext, in EditorInfo attribute); - void createSession(IInputMethodCallback callback); + oneway void createSession(IInputMethodCallback callback); - void setSessionEnabled(IInputMethodSession session, boolean enabled); + oneway void setSessionEnabled(IInputMethodSession session, boolean enabled); - void revokeSession(IInputMethodSession session); + oneway void revokeSession(IInputMethodSession session); - void showSoftInput(int flags, in ResultReceiver resultReceiver); + oneway void showSoftInput(int flags, in ResultReceiver resultReceiver); - void hideSoftInput(int flags, in ResultReceiver resultReceiver); + oneway void hideSoftInput(int flags, in ResultReceiver resultReceiver); - void changeInputMethodSubtype(in InputMethodSubtype subtype); + void removeSoftInputMessages(); + + oneway void changeInputMethodSubtype(in InputMethodSubtype subtype); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index d0048bf440a9e..14841af70f436 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1199,7 +1199,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurId = info.getId(); mCurToken = new Binder(); try { - if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); + if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD); } catch (RemoteException e) { @@ -1237,14 +1237,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mMethodMap) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { + IInputMethod prevMethod = mCurMethod; mCurMethod = IInputMethod.Stub.asInterface(service); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); unbindCurrentMethodLocked(false, false); return; } - // Remove commands relating to the previous service. Otherwise WindowManagerService - // will reject the command because the token attached to these messages is invalid. + // Remove messages relating to the previous service. Otherwise WindowManagerService + // will throw a BadTokenException because the old token is being removed. + if (prevMethod != null) { + try { + prevMethod.removeSoftInputMessages(); + } catch (RemoteException e) { + } + } mCaller.removeMessages(MSG_SHOW_SOFT_INPUT); mCaller.removeMessages(MSG_HIDE_SOFT_INPUT); if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); @@ -2309,8 +2316,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + msg.arg1 + ", " + args.arg2 + ")"); - ((IInputMethod)args.arg1).showSoftInput(msg.arg1, - (ResultReceiver)args.arg2); + ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); } catch (RemoteException e) { } args.recycle(); @@ -2320,8 +2326,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " + args.arg2 + ")"); - ((IInputMethod)args.arg1).hideSoftInput(0, - (ResultReceiver)args.arg2); + ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2); } catch (RemoteException e) { } args.recycle(); From 53536a3adac0a95d36ca27cf177ebd0d5c5a8b6d Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Thu, 14 Mar 2013 13:23:52 -0700 Subject: [PATCH 27/76] Respawn app_main if ADDR_COMPAT_LAYOUT is not set Bug: 8358800 Change-Id: Id81aa26d74dfd16a49f0ee5926ea2cdb3c2106d8 --- cmds/app_process/app_main.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index 6fe358c7ee54e..de7931dfa28e3 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -128,8 +129,28 @@ static void setArgv0(const char *argv0, const char *newArgv0) strlcpy(const_cast(argv0), newArgv0, strlen(argv0)); } -int main(int argc, const char* const argv[]) +int main(int argc, char* const argv[]) { +#ifdef __arm__ + /* + * b/7188322 - Temporarily revert to the compat memory layout + * to avoid breaking third party apps. + * + * THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE. + * + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466 + * changes the kernel mapping from bottom up to top-down. + * This breaks some programs which improperly embed + * an out of date copy of Android's linker. + */ + int current = personality(0xFFFFFFFF); + if ((current & ADDR_COMPAT_LAYOUT) == 0) { + personality(current | ADDR_COMPAT_LAYOUT); + execv("/system/bin/app_process", argv); + return -1; + } +#endif + // These are global variables in ProcessState.cpp mArgC = argc; mArgV = argv; From 287255c7dd0637f99f8c7f3a1b3732cf2545e32a Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Fri, 15 Mar 2013 11:38:44 -0700 Subject: [PATCH 28/76] Catch BadTokenException and continue. BadTokenException is a normal consequence of swapping IMEs while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue. This race condition cannot be avoided without an unacceptable lock down of InputMethodManagerService. Fixes bug 8387663. Fixes bug 8263462. Change-Id: I2c21573cf972145ab08e66604cdb9344139a3f31 --- .../IInputMethodWrapper.java | 8 +---- .../InputMethodService.java | 14 +++++--- .../android/internal/view/IInputMethod.aidl | 36 +++++++++---------- .../server/InputMethodManagerService.java | 30 +++++----------- 4 files changed, 35 insertions(+), 53 deletions(-) diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index d59c7b819df38..11282305dd41c 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -54,7 +54,7 @@ class IInputMethodWrapper extends IInputMethod.Stub implements HandlerCaller.Callback { private static final String TAG = "InputMethodWrapper"; private static final boolean DEBUG = false; - + private static final int DO_DUMP = 1; private static final int DO_ATTACH_TOKEN = 10; private static final int DO_SET_INPUT_CONTEXT = 20; @@ -284,12 +284,6 @@ class IInputMethodWrapper extends IInputMethod.Stub flags, resultReceiver)); } - @Override - public void removeSoftInputMessages() { - mCaller.removeMessages(DO_SHOW_SOFT_INPUT); - mCaller.removeMessages(DO_HIDE_SOFT_INPUT); - } - public void changeInputMethodSubtype(InputMethodSubtype subtype) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE, subtype)); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 288cefff55df4..5a9cde170b709 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -39,7 +39,6 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; -import android.util.Slog; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -49,6 +48,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; +import android.view.WindowManager.BadTokenException; import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; @@ -352,7 +352,6 @@ public class InputMethodService extends AbstractInputMethodService { * Take care of attaching the given window token provided by the system. */ public void attachToken(IBinder token) { - Slog.i(TAG, "attachToken: Existing token=" + mToken + " new token=" + token); if (mToken == null) { mToken = token; mWindow.setToken(token); @@ -419,11 +418,16 @@ public class InputMethodService extends AbstractInputMethodService { * Handle a request by the system to show the soft input area. */ public void showSoftInput(int flags, ResultReceiver resultReceiver) { - if (true || DEBUG) Slog.v(TAG, "showSoftInput()"); + if (DEBUG) Log.v(TAG, "showSoftInput()"); boolean wasVis = isInputViewShown(); mShowInputFlags = 0; if (onShowInputRequested(flags, false)) { - showWindow(true); + try { + showWindow(true); + } catch (BadTokenException e) { + if (DEBUG) Log.v(TAG, "BadTokenException: IME is done."); + mWindowVisible = false; + } } // If user uses hard keyboard, IME button should always be shown. boolean showing = onEvaluateInputViewShown(); @@ -1390,7 +1394,7 @@ public class InputMethodService extends AbstractInputMethodService { } public void showWindow(boolean showInput) { - if (true || DEBUG) Slog.v(TAG, "Showing window: showInput=" + showInput + if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested + " mWindowAdded=" + mWindowAdded + " mWindowCreated=" + mWindowCreated diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index c2a7fc73b0c33..5db860b1950a8 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -33,28 +33,26 @@ import com.android.internal.view.IInputMethodSession; * Service). * {@hide} */ -interface IInputMethod { - oneway void attachToken(IBinder token); - - oneway void bindInput(in InputBinding binding); - - oneway void unbindInput(); +oneway interface IInputMethod { + void attachToken(IBinder token); - oneway void startInput(in IInputContext inputContext, in EditorInfo attribute); + void bindInput(in InputBinding binding); - oneway void restartInput(in IInputContext inputContext, in EditorInfo attribute); + void unbindInput(); - oneway void createSession(IInputMethodCallback callback); - - oneway void setSessionEnabled(IInputMethodSession session, boolean enabled); - - oneway void revokeSession(IInputMethodSession session); - - oneway void showSoftInput(int flags, in ResultReceiver resultReceiver); - - oneway void hideSoftInput(int flags, in ResultReceiver resultReceiver); + void startInput(in IInputContext inputContext, in EditorInfo attribute); - void removeSoftInputMessages(); + void restartInput(in IInputContext inputContext, in EditorInfo attribute); - oneway void changeInputMethodSubtype(in InputMethodSubtype subtype); + void createSession(IInputMethodCallback callback); + + void setSessionEnabled(IInputMethodSession session, boolean enabled); + + void revokeSession(IInputMethodSession session); + + void showSoftInput(int flags, in ResultReceiver resultReceiver); + + void hideSoftInput(int flags, in ResultReceiver resultReceiver); + + void changeInputMethodSubtype(in InputMethodSubtype subtype); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 14841af70f436..f6c9d8273513c 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1237,24 +1237,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mMethodMap) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { - IInputMethod prevMethod = mCurMethod; mCurMethod = IInputMethod.Stub.asInterface(service); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); unbindCurrentMethodLocked(false, false); return; } - // Remove messages relating to the previous service. Otherwise WindowManagerService - // will throw a BadTokenException because the old token is being removed. - if (prevMethod != null) { - try { - prevMethod.removeSoftInputMessages(); - } catch (RemoteException e) { - } - } - mCaller.removeMessages(MSG_SHOW_SOFT_INPUT); - mCaller.removeMessages(MSG_HIDE_SOFT_INPUT); - if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); + if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); if (mCurClient != null) { @@ -1700,7 +1689,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown"); + if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(flags, resultReceiver); } } finally { @@ -1724,8 +1713,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean res = false; if (mCurMethod != null) { - if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken, - new RuntimeException("here").fillInStackTrace()); + if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken); executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, resultReceiver)); @@ -1797,13 +1785,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { - if (true ||DEBUG) Slog.v(TAG, - "Not hiding: explicit show not cancelled by non-explicit hide"); + if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); return false; } if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { - if (true ||DEBUG) Slog.v(TAG, - "Not hiding: forced show not cancelled by not-always hide"); + if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); return false; } boolean res; @@ -2314,7 +2300,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_SHOW_SOFT_INPUT: args = (SomeArgs)msg.obj; try { - if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + msg.arg1 + ", " + args.arg2 + ")"); ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); } catch (RemoteException e) { @@ -2324,7 +2310,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_SOFT_INPUT: args = (SomeArgs)msg.obj; try { - if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " + if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " + args.arg2 + ")"); ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2); } catch (RemoteException e) { @@ -2334,7 +2320,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_ATTACH_TOKEN: args = (SomeArgs)msg.obj; try { - if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); + if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); } catch (RemoteException e) { } From eaf2e6841d0734c6f7bbed03adb3ffceff084c48 Mon Sep 17 00:00:00 2001 From: Robert Greenwalt Date: Fri, 15 Mar 2013 10:48:46 -0700 Subject: [PATCH 29/76] Don't update routes if Dhcp fails. bug:8377625 Change-Id: I11d2c29728078813bfb1245cc46e8cce2b307a2c --- core/java/android/net/DhcpStateMachine.java | 2 +- core/jni/android_net_NetUtils.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index f1402829d8961..4194c9df885a1 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -359,7 +359,7 @@ public class DhcpStateMachine extends StateMachine { } else if (dhcpAction == DhcpAction.RENEW) { if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName); success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults); - dhcpResults.updateFromDhcpRequest(mDhcpResults); + if (success) dhcpResults.updateFromDhcpRequest(mDhcpResults); } if (success) { if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName); diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index f5f22b2d2aae5..faae11ec3bb6b 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -136,6 +136,10 @@ static jboolean android_net_utils_runDhcpCommon(JNIEnv* env, jobject clazz, jstr result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength, dns, server, &lease, vendorInfo, domains); } + if (result != 0) { + ALOGD("dhcp_do_request failed"); + } + env->ReleaseStringUTFChars(ifname, nameStr); if (result == 0) { env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear); From 7bce462afcd484c51760ffc2fdcc49e55f5f64b6 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Thu, 14 Mar 2013 16:31:34 -0700 Subject: [PATCH 30/76] Don't use ADDR_COMPAT_LAYOUT on the emulator For the emulator, we want people to see memory as it actually is, not how we're hacking around buggy apps. Don't set ADDR_COMPAT_LAYOUT on the emulator. For reasons that I don't understand, personality(ADDR_COMPAT_LAYOUT) does not persist across an exec on the emulator. app_main gets into a tight loop restarting itself because of this. This change also works around that bug. Change-Id: Ia73a7d2d623c25cf39d248145d97307945d554da --- cmds/app_process/app_main.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index de7931dfa28e3..b54774ee18f07 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -143,11 +144,15 @@ int main(int argc, char* const argv[]) * This breaks some programs which improperly embed * an out of date copy of Android's linker. */ - int current = personality(0xFFFFFFFF); - if ((current & ADDR_COMPAT_LAYOUT) == 0) { - personality(current | ADDR_COMPAT_LAYOUT); - execv("/system/bin/app_process", argv); - return -1; + char value[PROPERTY_VALUE_MAX]; + property_get("ro.kernel.qemu", value, ""); + if (strcmp(value, "1") != 0) { + int current = personality(0xFFFFFFFF); + if ((current & ADDR_COMPAT_LAYOUT) == 0) { + personality(current | ADDR_COMPAT_LAYOUT); + execv("/system/bin/app_process", argv); + return -1; + } } #endif From 7f738a60ebbfe8ebca44d44f209896f68bfbb23c Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Fri, 15 Mar 2013 11:38:29 -0700 Subject: [PATCH 31/76] Never call app_main more than once Different kernels seem to handle ADDR_COMPAT_LAYOUT differently, sometimes passing it to its children, sometimes not. If it's not passed to its child successfully, we can end up in a restart loop. Instead of testing for the presence of ADDR_COMPAT_LAYOUT, use an environment variable instead, which is handled more predictably. Bug: 8392487 Change-Id: Ia531dd2abb4e1cd46f3430d844e644f53581f530 --- cmds/app_process/app_main.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp index b54774ee18f07..80df56873a3a3 100644 --- a/cmds/app_process/app_main.cpp +++ b/cmds/app_process/app_main.cpp @@ -12,10 +12,10 @@ #include #include #include -#include #include #include +#include #include #include @@ -144,12 +144,11 @@ int main(int argc, char* const argv[]) * This breaks some programs which improperly embed * an out of date copy of Android's linker. */ - char value[PROPERTY_VALUE_MAX]; - property_get("ro.kernel.qemu", value, ""); - if (strcmp(value, "1") != 0) { + if (getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) { int current = personality(0xFFFFFFFF); if ((current & ADDR_COMPAT_LAYOUT) == 0) { personality(current | ADDR_COMPAT_LAYOUT); + setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1); execv("/system/bin/app_process", argv); return -1; } From 288319f6ec55c12ec11f457d1efa881b70133088 Mon Sep 17 00:00:00 2001 From: Irfan Sheriff Date: Mon, 18 Mar 2013 13:52:22 -0700 Subject: [PATCH 32/76] Fix tethering Using sendMessageAtFrontofQueue() causes issues here since there can be a start that is deferred and it basically has no effect. Use defer which is more clean and obvious here. Bug: 8410547 Change-Id: I4fbf299283847e7c3009b87a81cb56e765b0e1e1 --- wifi/java/android/net/wifi/WifiStateMachine.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 8cdfe032d0c7c..b57910f59838c 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -3584,6 +3584,8 @@ public class WifiStateMachine extends StateMachine { setWifiApState(WIFI_AP_STATE_DISABLING); stopTethering(); transitionTo(mUntetheringState); + // More work to do after untethering + deferMessage(message); break; default: return NOT_HANDLED; @@ -3610,15 +3612,11 @@ public class WifiStateMachine extends StateMachine { if (isWifiTethered(stateChange.active)) break; transitionTo(mSoftApStartedState); - // Needs to be first thing handled - sendMessageAtFrontOfQueue(CMD_STOP_AP); break; case CMD_TETHER_NOTIFICATION_TIMED_OUT: if (message.arg1 == mTetherToken) { loge("Failed to get tether update, force stop access point"); transitionTo(mSoftApStartedState); - // Needs to be first thing handled - sendMessageAtFrontOfQueue(CMD_STOP_AP); } break; case CMD_START_SUPPLICANT: From 339f4d23406f910a3efa1a6c10673ac334c18b17 Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Mon, 18 Mar 2013 17:00:18 -0700 Subject: [PATCH 33/76] Work around saveLayer clipping/transform deferring issues bug:8409891 Disables deferring Change-Id: I93498a4a45a5bfe01143faa154926c6a138db8f9 --- libs/hwui/OpenGLRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index e576f76f8660f..66681e0a461e3 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1805,7 +1805,7 @@ status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, // All the usual checks and setup operations (quickReject, setupDraw, etc.) // will be performed by the display list itself if (displayList && displayList->isRenderable()) { - if (CC_UNLIKELY(mCaches.drawDeferDisabled)) { + if (true || CC_UNLIKELY(mCaches.drawDeferDisabled)) { // NOTE: temporary workaround ReplayStateStruct replayStruct(*this, dirty, replayFlags); displayList->replay(replayStruct, 0); return replayStruct.mDrawGlStatus; From 11322434c6ee279927ee337f4413002f3f8c2423 Mon Sep 17 00:00:00 2001 From: Irfan Sheriff Date: Tue, 19 Mar 2013 14:32:11 -0700 Subject: [PATCH 34/76] Fix reading sleep policy Bug: 8418823 Change-Id: I432b0a1a7a13dcdd834f6a1c943b06937444f406 --- services/java/com/android/server/wifi/WifiController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java index 4d7c4345837c4..6e6b8ccc538b9 100644 --- a/services/java/com/android/server/wifi/WifiController.java +++ b/services/java/com/android/server/wifi/WifiController.java @@ -166,7 +166,7 @@ class WifiController extends StateMachine { registerForStayAwakeModeChange(handler); readWifiIdleTime(); registerForWifiIdleTimeChange(handler); - readStayAwakeConditions(); + readWifiSleepPolicy(); registerForWifiSleepPolicyChange(handler); } From 27389c9c111f44cad0016bc49c63f012aabc53fe Mon Sep 17 00:00:00 2001 From: Craig Mautner Date: Tue, 19 Mar 2013 16:54:35 -0700 Subject: [PATCH 35/76] Clear mWindowAdded when window was not added. DO NOT MERGE If ViewRootImpl throws BadTokenException when adding a window, clear the indication that a window has been added. That way when the window is destroyed it doesn't try to clean it up. Fixes bug 8409506. Change-Id: I270740762f21ed4ec7f235344a3adaeaa033c483 --- core/java/android/inputmethodservice/InputMethodService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 5a9cde170b709..2b15afdde9eaa 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -427,6 +427,7 @@ public class InputMethodService extends AbstractInputMethodService { } catch (BadTokenException e) { if (DEBUG) Log.v(TAG, "BadTokenException: IME is done."); mWindowVisible = false; + mWindowAdded = false; } } // If user uses hard keyboard, IME button should always be shown. From aa5214fe9baf072438d83f2ba0abe303b22696cc Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Thu, 21 Mar 2013 14:23:46 -0700 Subject: [PATCH 36/76] Fix issue #8436951 JB-MR2: Compatibility: Audible, Pandora, ... ...Tune-In does not work since JWQ77 Instead of crashing the app for it being bad, just go ahead and show a notification for it anyway that we make up. You get what you deserve. :p Change-Id: I92e32b9ff8835dabde63f8e08e991f72de0d0a92 --- core/res/res/values/strings.xml | 9 ++++ core/res/res/values/symbols.xml | 2 + .../com/android/server/am/ServiceRecord.java | 41 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 447d94c781019..54447e3541b74 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2984,6 +2984,15 @@ Some system functions may not work + + %1$s + running + + %1$s + is currently running + OK diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9ec33bb4bb467..80e77dd6e187f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -330,6 +330,8 @@ + + diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 1ac6bdfb850f0..8ff1c7d5fba79 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -16,6 +16,9 @@ package com.android.server.am; +import android.app.PendingIntent; +import android.net.Uri; +import android.provider.Settings; import com.android.internal.os.BatteryStatsImpl; import com.android.server.NotificationManagerService; @@ -368,6 +371,44 @@ class ServiceRecord extends Binder { return; } try { + if (foregroundNoti.icon == 0) { + // It is not correct for the caller to supply a notification + // icon, but this used to be able to slip through, so for + // those dirty apps give it the app's icon. + foregroundNoti.icon = appInfo.icon; + if (foregroundNoti.contentView == null) { + // In this case the app may not have specified a + // content view... so we'll give them something to show. + CharSequence appName = appInfo.loadLabel( + ams.mContext.getPackageManager()); + if (appName == null) { + appName = appInfo.packageName; + } + Context ctx = null; + try { + ctx = ams.mContext.createPackageContext( + appInfo.packageName, 0); + Intent runningIntent = new Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + runningIntent.setData(Uri.fromParts("package", + appInfo.packageName, null)); + PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0, + runningIntent, PendingIntent.FLAG_UPDATE_CURRENT); + foregroundNoti.setLatestEventInfo(ctx, + ams.mContext.getString( + com.android.internal.R.string + .app_running_notification_title, + appName), + ams.mContext.getString( + com.android.internal.R.string + .app_running_notification_text, + appName), + pi); + } catch (PackageManager.NameNotFoundException e) { + foregroundNoti.icon = 0; + } + } + } if (foregroundNoti.icon == 0) { // Notifications whose icon is 0 are defined to not show // a notification, silently ignoring it. We don't want to From 7289e341b185a88663c86109df9ee6976a587f96 Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Thu, 21 Mar 2013 14:39:04 -0700 Subject: [PATCH 37/76] Isolate tiling clip state from snapshot bug:8409891 Snapshots frequently have their clip overwritten due to applying deferred state - now, store tiling clip information in a separate rect, outside of the snapshot so it isn't overwritten. Change-Id: I21ca4c45dcd802eae99e8de86f11525196777ccb --- libs/hwui/DisplayList.cpp | 6 +++--- libs/hwui/OpenGLRenderer.cpp | 12 ++++++------ libs/hwui/OpenGLRenderer.h | 2 +- libs/hwui/Snapshot.cpp | 11 +++++++++++ libs/hwui/Snapshot.h | 2 ++ 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 55ddd17f5cb64..d985ad00e8494 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -351,9 +351,9 @@ void DisplayList::outputViewProperties(const int level) { level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix)); } } - if (mAlpha < 1 && !mCaching) { - if (!mHasOverlappingRendering) { - ALOGD("%*sSetAlpha %.2f", level * 2, "", mAlpha); + if (mAlpha < 1) { + if (mCaching || !mHasOverlappingRendering) { + ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha); } else { int flags = SkCanvas::kHasAlphaLayer_SaveFlag; if (mClipChildren) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index bc28d655aec4a..2cf7183360d16 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -193,6 +193,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top, mSaveCount = 1; mSnapshot->setClip(left, top, right, bottom); + mTilingClip.set(left, top, right, bottom); mDirtyClip = true; updateLayers(); @@ -206,8 +207,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top, // invoked during the frame mSuppressTiling = mCaches.hasRegisteredFunctors(); - mTilingSnapshot = mSnapshot; - startTiling(mTilingSnapshot, true); + startTiling(mSnapshot, true); debugOverdraw(true, true); @@ -252,9 +252,9 @@ void OpenGLRenderer::syncState() { void OpenGLRenderer::startTiling(const sp& s, bool opaque) { if (!mSuppressTiling) { - Rect* clip = mTilingSnapshot->clipRect; + Rect* clip = &mTilingClip; if (s->flags & Snapshot::kFlagFboTarget) { - clip = &s->layer->clipRect; + clip = &(s->layer->clipRect); } startTiling(*clip, s->height, opaque); @@ -480,10 +480,10 @@ void OpenGLRenderer::debugOverdraw(bool enable, bool clear) { void OpenGLRenderer::renderOverdraw() { if (mCaches.debugOverdraw && getTargetFbo() == 0) { - const Rect* clip = mTilingSnapshot->clipRect; + const Rect* clip = &mTilingClip; mCaches.enableScissor(); - mCaches.setScissor(clip->left, mTilingSnapshot->height - clip->bottom, + mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom, clip->right - clip->left, clip->bottom - clip->top); mCaches.stencil.enableDebugTest(2); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 71bd6bb124f06..7bb93957bc034 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -947,7 +947,7 @@ private: // Current state sp mSnapshot; // State used to define the clipping region - sp mTilingSnapshot; + Rect mTilingClip; // Used to draw textured quads TextureVertex mMeshVertices[4]; diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index 923913e864a93..d26ee3884433f 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + #include "Snapshot.h" #include @@ -199,5 +201,14 @@ bool Snapshot::isIgnored() const { return invisible || empty; } +void Snapshot::dump() const { + ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d", + this, flags, previous.get(), height, isIgnored(), clipRegion && !clipRegion->isEmpty()); + ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f", + clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom); + ALOGD(" Transform (at %p):", transform); + transform->dump(); +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index ffd47295b6181..cc6d0cda003ef 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -228,6 +228,8 @@ public: */ float alpha; + void dump() const; + private: void ensureClipRegion(); void copyClipRectFromRegion(); From 35523121a807b02cee2da1bdd585bcd853e382fb Mon Sep 17 00:00:00 2001 From: Fabrice Di Meglio Date: Wed, 27 Mar 2013 12:41:22 -0700 Subject: [PATCH 38/76] Fix bug #8487785 Notification shade has text overlapping the icon - follow up to the fix for bug #8480245 ViewGroup layout margins can be wrong in RTL mode - deal with "RTL compatibility mode": if left/right margins are not defined and if we haev some start/end ones then use the start/end ones. Change-Id: I98fe3276de2bd14f60a1c423a47569a68046f7be --- core/java/android/view/ViewGroup.java | 82 ++++++++++++++++++++------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 98edeaebaf765..dd36022dfbfaf 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -5890,6 +5890,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private boolean mNeedResolution = false; private boolean mIsRtlCompatibilityMode = true; + private static int UNDEFINED_MARGIN = DEFAULT_MARGIN_RELATIVE; + + private boolean mLeftMarginUndefined = false; + private boolean mRightMarginUndefined = false; + /** * Creates a new set of layout parameters. The values are extracted from * the supplied attributes set and context. @@ -5916,16 +5921,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } else { leftMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginLeft, - DEFAULT_MARGIN_RESOLVED); - topMargin = a.getDimensionPixelSize( - R.styleable.ViewGroup_MarginLayout_layout_marginTop, - DEFAULT_MARGIN_RESOLVED); + UNDEFINED_MARGIN); + if (leftMargin == UNDEFINED_MARGIN) { + mLeftMarginUndefined = true; + leftMargin = DEFAULT_MARGIN_RESOLVED; + } rightMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginRight, + UNDEFINED_MARGIN); + if (rightMargin == UNDEFINED_MARGIN) { + mRightMarginUndefined = true; + rightMargin = DEFAULT_MARGIN_RESOLVED; + } + + topMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginTop, DEFAULT_MARGIN_RESOLVED); bottomMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginBottom, DEFAULT_MARGIN_RESOLVED); + startMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginStart, DEFAULT_MARGIN_RELATIVE); @@ -5949,6 +5964,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public MarginLayoutParams(int width, int height) { super(width, height); + mLeftMarginUndefined = true; + mRightMarginUndefined = true; + mNeedResolution = false; mIsRtlCompatibilityMode = false; } @@ -5969,6 +5987,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager this.startMargin = source.startMargin; this.endMargin = source.endMargin; + this.mLeftMarginUndefined = source.mLeftMarginUndefined; + this.mRightMarginUndefined = source.mRightMarginUndefined; + this.mNeedResolution = source.mNeedResolution; this.mIsRtlCompatibilityMode = source.mIsRtlCompatibilityMode; @@ -5981,6 +6002,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public MarginLayoutParams(LayoutParams source) { super(source); + mLeftMarginUndefined = true; + mRightMarginUndefined = true; + mNeedResolution = false; mIsRtlCompatibilityMode = false; } @@ -6005,6 +6029,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager topMargin = top; rightMargin = right; bottomMargin = bottom; + mLeftMarginUndefined = false; + mRightMarginUndefined = false; mNeedResolution = isMarginRelative(); } @@ -6147,30 +6173,42 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // No relative margin or pre JB-MR1 case or no need to resolve, just dont do anything // Will use the left and right margins if no relative margin is defined. - if (!isMarginRelative() || !mNeedResolution || mIsRtlCompatibilityMode) return; + if (!isMarginRelative() || !mNeedResolution) return; // Proceed with resolution doResolveMargins(); } private void doResolveMargins() { - // We have some relative margins (either the start one or the end one or both). So use - // them and override what has been defined for left and right margins. If either start - // or end margin is not defined, just set it to default "0". - switch(mLayoutDirection) { - case View.LAYOUT_DIRECTION_RTL: - leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ? - endMargin : DEFAULT_MARGIN_RESOLVED; - rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ? - startMargin : DEFAULT_MARGIN_RESOLVED; - break; - case View.LAYOUT_DIRECTION_LTR: - default: - leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ? - startMargin : DEFAULT_MARGIN_RESOLVED; - rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ? - endMargin : DEFAULT_MARGIN_RESOLVED; - break; + + if (mIsRtlCompatibilityMode) { + // if left or right margins are not defined and if we have some start or end margin + // defined then use those start and end margins. + if (mLeftMarginUndefined && startMargin > DEFAULT_MARGIN_RELATIVE) { + leftMargin = startMargin; + } + if (mRightMarginUndefined && endMargin > DEFAULT_MARGIN_RELATIVE) { + rightMargin = endMargin; + } + } else { + // We have some relative margins (either the start one or the end one or both). So use + // them and override what has been defined for left and right margins. If either start + // or end margin is not defined, just set it to default "0". + switch(mLayoutDirection) { + case View.LAYOUT_DIRECTION_RTL: + leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ? + endMargin : DEFAULT_MARGIN_RESOLVED; + rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ? + startMargin : DEFAULT_MARGIN_RESOLVED; + break; + case View.LAYOUT_DIRECTION_LTR: + default: + leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ? + startMargin : DEFAULT_MARGIN_RESOLVED; + rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ? + endMargin : DEFAULT_MARGIN_RESOLVED; + break; + } } mNeedResolution = false; } From a202afa7eccde123d89d0dbbf39131ec77501d95 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Wed, 27 Mar 2013 12:22:27 -0700 Subject: [PATCH 39/76] Fix an action bar menu order of operations bug As options menu invalidations in PhoneWindow are deferred, it was possible for a call to open the overflow menu (which involves an extra chance to prepare the menu) to happen before the pending menu invalidation was handled. Process any pending menu invalidations before attempting to show the overflow menu in response to one of these events. Bug 7209308 Change-Id: Id50f7f6ad5d3a631745207fa0bad8c356690e16c --- .../src/com/android/internal/policy/impl/PhoneWindow.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 5ad305cbd2a2e..ad5e20bcab6c8 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -989,6 +989,13 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { final Callback cb = getCallback(); if (!mActionBar.isOverflowMenuShowing() || !toggleMenuMode) { if (cb != null && !isDestroyed() && mActionBar.getVisibility() == View.VISIBLE) { + // If we have a menu invalidation pending, do it now. + if (mInvalidatePanelMenuPosted && + (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) { + mDecor.removeCallbacks(mInvalidatePanelMenuRunnable); + mInvalidatePanelMenuRunnable.run(); + } + final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, true); // If we don't have a menu or we're waiting for a full content refresh, From eae774f2b266bcf97976cd73a55db4b0b4f6e92d Mon Sep 17 00:00:00 2001 From: Ben Cheng Date: Wed, 27 Mar 2013 17:36:13 -0700 Subject: [PATCH 40/76] Crank up the mOomMinFreeHigh values by 50%. Devices with 1GB of RAM can benefit from more aggressive OOM killer. BUG: 7972252 Change-Id: Id2b80c14a3cff195c17ef0d14c80b92394649d98 --- services/java/com/android/server/am/ProcessList.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java index 9e25e30245135..1a635a9a14a02 100644 --- a/services/java/com/android/server/am/ProcessList.java +++ b/services/java/com/android/server/am/ProcessList.java @@ -144,8 +144,8 @@ class ProcessList { // These are the high-end OOM level limits. This is appropriate for a // 1280x800 or larger screen with around 1GB RAM. Values are in KB. private final long[] mOomMinFreeHigh = new long[] { - 32768, 40960, 49152, - 57344, 65536, 81920 + 49152, 61440, 73728, + 86016, 98304, 122880 }; // The actual OOM killer memory levels we are using. private final long[] mOomMinFree = new long[mOomAdj.length]; From cd93fb986c9ac83e9f36068c56566e612c6f4194 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 28 Mar 2013 13:06:58 -0700 Subject: [PATCH 41/76] Update layers to clear them Bug #8489505 A layer should be updated even with an empty deferred display list. It is possible for a layer to request an update just to execute a clear which will be handled by LayerRenderer::prepareDirty(). Change-Id: Iffb98bd71d6caa8d4a701df98197676f9423c0c2 --- libs/hwui/Layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 2998535c99169..1a1bfe1b1bdb7 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -161,7 +161,7 @@ void Layer::defer() { } void Layer::flush() { - if (deferredList && !deferredList->isEmpty()) { + if (deferredList) { renderer->setViewport(layer.getWidth(), layer.getHeight()); renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); From fb47d839518fecf764392abdafd9bd002379cb9b Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Fri, 29 Mar 2013 12:37:16 -0700 Subject: [PATCH 42/76] Prevent crash when a single layer is enqueued several times for updates Bug #8504687 Change-Id: I9b01bbc4e3f37af23dfe5e68d3d03ad3d238b94a --- libs/hwui/Layer.cpp | 19 +++++++++++++++++++ libs/hwui/Layer.h | 1 + libs/hwui/OpenGLRenderer.cpp | 35 ++++++++++++++++------------------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 1a1bfe1b1bdb7..dc2b672abcd07 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -158,6 +158,9 @@ void Layer::defer() { dirtyRect.right, dirtyRect.bottom, !isBlend()); displayList->defer(deferredState, 0); + + deferredUpdateScheduled = false; + displayList = NULL; } void Layer::flush() { @@ -176,5 +179,21 @@ void Layer::flush() { } } +void Layer::render() { + renderer->setViewport(layer.getWidth(), layer.getHeight()); + renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, + !isBlend()); + + renderer->drawDisplayList(displayList, dirtyRect, DisplayList::kReplayFlag_ClipChildren); + + renderer->finish(); + renderer = NULL; + + dirtyRect.setEmpty(); + + deferredUpdateScheduled = false; + displayList = NULL; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 0e00191d8654f..27e0cf169e437 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -275,6 +275,7 @@ struct Layer { void defer(); void flush(); + void render(); /** * Bounds of the layer. diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 2903bcd0951a2..3022fed0b4e7a 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -540,13 +540,7 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { } if (CC_UNLIKELY(inFrame || mCaches.drawDeferDisabled)) { - OpenGLRenderer* renderer = layer->renderer; - renderer->setViewport(layer->layer.getWidth(), layer->layer.getHeight()); - renderer->prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, - !layer->isBlend()); - renderer->drawDisplayList(layer->displayList, dirty, - DisplayList::kReplayFlag_ClipChildren); - renderer->finish(); + layer->render(); } else { layer->defer(); } @@ -556,13 +550,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { startTiling(mSnapshot); } - if (CC_UNLIKELY(inFrame || mCaches.drawDeferDisabled)) { - dirty.setEmpty(); - layer->renderer = NULL; - } - - layer->deferredUpdateScheduled = false; - layer->displayList = NULL; layer->debugDrawUpdate = mCaches.debugLayersUpdates; return true; @@ -609,11 +596,12 @@ void OpenGLRenderer::flushLayers() { // Note: it is very important to update the layers in reverse order for (int i = count - 1; i >= 0; i--) { sprintf(layerName, "Layer #%d", i); - startMark(layerName); { - Layer* layer = mLayerUpdates.itemAt(i); - layer->flush(); - mCaches.resourceCache.decrementRefcount(layer); - } + startMark(layerName); + + Layer* layer = mLayerUpdates.itemAt(i); + layer->flush(); + mCaches.resourceCache.decrementRefcount(layer); + endMark(); } @@ -626,6 +614,15 @@ void OpenGLRenderer::flushLayers() { void OpenGLRenderer::pushLayerUpdate(Layer* layer) { if (layer) { + // Make sure we don't introduce duplicates. + // SortedVector would do this automatically but we need to respect + // the insertion order. The linear search is not an issue since + // this list is usually very short (typically one item, at most a few) + for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { + if (mLayerUpdates.itemAt(i) == layer) { + return; + } + } mLayerUpdates.push_back(layer); mCaches.resourceCache.incrementRefcount(layer); } From 068463337f827a75c5f591cf5d112122d0fc17d9 Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Thu, 28 Mar 2013 17:27:50 -0700 Subject: [PATCH 43/76] Finish any event in the current input queue When the pipelining optimization was added we never updated handleImeFinishedEvent to look through the whole queue of items, so it was only looking at the head. Bug: 8498214 Change-Id: I79c62392a93b47e3e1eab3f4fe54a5c999dfb566 --- core/java/android/view/ViewRootImpl.java | 114 ++++++++++++++--------- 1 file changed, 69 insertions(+), 45 deletions(-) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7b34ce1cff958..7790f92540dfa 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -258,12 +258,12 @@ public final class ViewRootImpl implements ViewParent, QueuedInputEvent mPendingInputEventHead; QueuedInputEvent mPendingInputEventTail; int mPendingInputEventCount; - QueuedInputEvent mCurrentInputEventHead; - QueuedInputEvent mCurrentInputEventTail; - int mCurrentInputEventCount; + QueuedInputEvent mActiveInputEventHead; + QueuedInputEvent mActiveInputEventTail; + int mActiveInputEventCount; boolean mProcessInputEventsScheduled; String mPendingInputEventQueueLengthCounterName = "pq"; - String mCurrentInputEventQueueLengthCounterName = "cq"; + String mActiveInputEventQueueLengthCounterName = "aq"; boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -661,7 +661,7 @@ public final class ViewRootImpl implements ViewParent, } mPendingInputEventQueueLengthCounterName = "pq:" + attrs.getTitle(); - mCurrentInputEventQueueLengthCounterName = "cq:" + attrs.getTitle(); + mActiveInputEventQueueLengthCounterName = "aq:" + attrs.getTitle(); } } } @@ -4443,7 +4443,7 @@ public final class ViewRootImpl implements ViewParent, void doProcessInputEvents() { // Handle all of the available pending input events. Currently this will immediately // process all of the events it can until it encounters one that must go through the IME. - // After that it will continue adding events to the current input queue but will wait for a + // After that it will continue adding events to the active input queue but will wait for a // response from the IME, regardless of whether that particular event needs it or not, in // order to guarantee ordering consistency. This could be slightly improved by only // queueing events whose source has previously encountered something that needs to be @@ -4466,18 +4466,18 @@ public final class ViewRootImpl implements ViewParent, if (result == EVENT_HANDLED || result == EVENT_NOT_HANDLED) { finishInputEvent(q, result == EVENT_HANDLED); } else if (result == EVENT_PENDING_IME) { - enqueueCurrentInputEvent(q); + enqueueActiveInputEvent(q); } else { q.mFlags |= QueuedInputEvent.FLAG_DELIVER_POST_IME; // If the IME decided not to handle this event, and we have no events already being // handled by the IME, go ahead and handle this one and then continue to the next // input event. Otherwise, queue it up and handle it after whatever in front of it // in the queue has been handled. - if (mCurrentInputEventHead == null) { + if (mActiveInputEventHead == null) { result = deliverInputEventPostIme(q); finishInputEvent(q, result == EVENT_HANDLED); } else { - enqueueCurrentInputEvent(q); + enqueueActiveInputEvent(q); } } } @@ -4490,29 +4490,54 @@ public final class ViewRootImpl implements ViewParent, } } - private void enqueueCurrentInputEvent(QueuedInputEvent q) { - if (mCurrentInputEventHead == null) { - mCurrentInputEventHead = q; - mCurrentInputEventTail = q; + private void enqueueActiveInputEvent(QueuedInputEvent q) { + if (mActiveInputEventHead == null) { + mActiveInputEventHead = q; + mActiveInputEventTail = q; } else { - mCurrentInputEventTail.mNext = q; - mCurrentInputEventTail = q; + mActiveInputEventTail.mNext = q; + mActiveInputEventTail = q; } - mCurrentInputEventCount += 1; - Trace.traceCounter(Trace.TRACE_TAG_INPUT, mCurrentInputEventQueueLengthCounterName, - mCurrentInputEventCount); + mActiveInputEventCount += 1; + Trace.traceCounter(Trace.TRACE_TAG_INPUT, mActiveInputEventQueueLengthCounterName, + mActiveInputEventCount); } - private QueuedInputEvent dequeueCurrentInputEvent() { - QueuedInputEvent q = mCurrentInputEventHead; - mCurrentInputEventHead = q.mNext; - if (mCurrentInputEventHead == null) { - mCurrentInputEventTail = null; + private QueuedInputEvent dequeueActiveInputEvent() { + return dequeueActiveInputEvent(mActiveInputEventHead); + } + + + private QueuedInputEvent dequeueActiveInputEvent(QueuedInputEvent q) { + QueuedInputEvent curr = mActiveInputEventHead; + QueuedInputEvent prev = null; + while (curr != null && curr != q) { + prev = curr; + curr = curr.mNext; + } + if (curr != null) { + if (mActiveInputEventHead == curr) { + mActiveInputEventHead = curr.mNext; + } else { + prev.mNext = curr.mNext; + } + if (mActiveInputEventTail == curr) { + mActiveInputEventTail = prev; + } + curr.mNext = null; + + mActiveInputEventCount -= 1; + Trace.traceCounter(Trace.TRACE_TAG_INPUT, mActiveInputEventQueueLengthCounterName, + mActiveInputEventCount); + } + return curr; + } + + private QueuedInputEvent findActiveInputEvent(int seq) { + QueuedInputEvent q = mActiveInputEventHead; + while (q != null && q.mEvent.getSequenceNumber() != seq) { + q = q.mNext; } - q.mNext = null; - mCurrentInputEventCount -= 1; - Trace.traceCounter(Trace.TRACE_TAG_INPUT, mCurrentInputEventQueueLengthCounterName, - mCurrentInputEventCount); return q; } @@ -4532,36 +4557,35 @@ public final class ViewRootImpl implements ViewParent, } void handleImeFinishedEvent(int seq, boolean handled) { - QueuedInputEvent q = mCurrentInputEventHead; - if (q != null && q.mEvent.getSequenceNumber() == seq) { - dequeueCurrentInputEvent(); + QueuedInputEvent q = findActiveInputEvent(seq); + if (q != null) { if (DEBUG_IMF) { Log.v(TAG, "IME finished event: seq=" + seq + " handled=" + handled + " event=" + q); } - if (!handled) { + if (handled) { + dequeueActiveInputEvent(q); + finishInputEvent(q, true); + } else { + q.mFlags |= QueuedInputEvent.FLAG_DELIVER_POST_IME; + } + + + // Flush all of the input events that are no longer waiting on the IME + while (mActiveInputEventHead != null && (mActiveInputEventHead.mFlags & + QueuedInputEvent.FLAG_DELIVER_POST_IME) != 0) { + q = dequeueActiveInputEvent(); // If the window doesn't currently have input focus, then drop // this event. This could be an event that came back from the // IME dispatch but the window has lost focus in the meantime. + handled = false; if (!mAttachInfo.mHasWindowFocus && !isTerminalInputEvent(q.mEvent)) { Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); } else { - final int result = deliverInputEventPostIme(q); - if (result == EVENT_HANDLED) { - handled = true; - } + handled = (deliverInputEventPostIme(q) == EVENT_HANDLED); } - } - - finishInputEvent(q, handled); - - // Flush all of the input events that are no longer waiting on the IME - while (mCurrentInputEventHead != null && (mCurrentInputEventHead.mFlags & - QueuedInputEvent.FLAG_DELIVER_POST_IME) != 0) { - q = dequeueCurrentInputEvent(); - final int result = deliverInputEventPostIme(q); - finishInputEvent(q, result == EVENT_HANDLED); + finishInputEvent(q, handled); } } else { if (DEBUG_IMF) { From e8cde4cbb9c5ca2edf9a0b110118d755ffb5a2e0 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Wed, 3 Apr 2013 14:39:19 -0700 Subject: [PATCH 44/76] Clear mCurSender when mCurChannel is modified. This fixed an issue where an InputEventSender might outlive its usefulness and continue to be used well after it should have been disposed or recreated. Also improves the queue management somewhat. Bug: 8493879 Change-Id: I7e0b6a3c43cbe72f8762991f5d36560feebd214b --- .../view/inputmethod/InputMethodManager.java | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7f9969cfcc5c0..855b6d4fc126d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -325,7 +325,8 @@ public final class InputMethodManager { PendingEvent mPendingEventPool; int mPendingEventPoolSize; - PendingEvent mFirstPendingEvent; + PendingEvent mPendingEventHead; + PendingEvent mPendingEventTail; // ----------------------------------------------------------- @@ -366,18 +367,14 @@ public final class InputMethodManager { if (mBindSequence < 0 || mBindSequence != res.sequence) { Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence + ", given seq=" + res.sequence); - if (res.channel != null) { + if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); } return; } - - flushPendingEventsLocked(); + + setInputChannelLocked(res.channel); mCurMethod = res.method; - if (mCurChannel != null) { - mCurChannel.dispose(); - } - mCurChannel = res.channel; mCurId = res.id; mBindSequence = res.sequence; } @@ -719,20 +716,26 @@ public final class InputMethodManager { */ void clearBindingLocked() { clearConnectionLocked(); - flushPendingEventsLocked(); + setInputChannelLocked(null); mBindSequence = -1; mCurId = null; mCurMethod = null; - if (mCurSender != null) { - mCurSender.dispose(); - mCurSender = null; - } - if (mCurChannel != null) { - mCurChannel.dispose(); - mCurChannel = null; + } + + void setInputChannelLocked(InputChannel channel) { + if (mCurChannel != channel) { + if (mCurSender != null) { + flushPendingEventsLocked(); + mCurSender.dispose(); + mCurSender = null; + } + if (mCurChannel != null) { + mCurChannel.dispose(); + } + mCurChannel = channel; } } - + /** * Reset all of the state associated with a served view being connected * to an input method @@ -1174,15 +1177,12 @@ public final class InputMethodManager { if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); if (res != null) { if (res.id != null) { + setInputChannelLocked(res.channel); mBindSequence = res.sequence; mCurMethod = res.method; - if (mCurChannel != null) { - mCurChannel.dispose(); - } - mCurChannel = res.channel; mCurId = res.id; } else { - if (res.channel != null) { + if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); } if (mCurMethod == null) { @@ -1655,8 +1655,13 @@ public final class InputMethodManager { private void enqueuePendingEventLocked( long startTime, int seq, String inputMethodId, FinishedEventCallback callback) { PendingEvent p = obtainPendingEventLocked(startTime, seq, inputMethodId, callback); - p.mNext = mFirstPendingEvent; - mFirstPendingEvent = p; + if (mPendingEventTail != null) { + mPendingEventTail.mNext = p; + mPendingEventTail = p; + } else { + mPendingEventHead = p; + mPendingEventTail = p; + } Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, seq, 0, p); msg.setAsynchronous(true); @@ -1664,12 +1669,15 @@ public final class InputMethodManager { } private PendingEvent dequeuePendingEventLocked(int seq) { - PendingEvent p = mFirstPendingEvent; + PendingEvent p = mPendingEventHead; if (p == null) { return null; } if (p.mSeq == seq) { - mFirstPendingEvent = p.mNext; + mPendingEventHead = p.mNext; + if (mPendingEventHead == null) { + mPendingEventTail = null; + } } else { PendingEvent prev; do { @@ -1680,6 +1688,9 @@ public final class InputMethodManager { } } while (p.mSeq != seq); prev.mNext = p.mNext; + if (mPendingEventTail == p) { + mPendingEventTail = prev; + } } p.mNext = null; return p; @@ -1716,25 +1727,13 @@ public final class InputMethodManager { private void flushPendingEventsLocked() { mH.removeMessages(MSG_EVENT_TIMEOUT); - PendingEvent curr, prev, next; - curr = mFirstPendingEvent; - prev = null; - while (curr != null) { - next = curr.mNext; - curr.mNext = prev; - prev = curr; - curr = next; - } - curr = prev; - prev = null; - while (curr != null) { - Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, curr.mSeq, 0, curr); + + PendingEvent p = mPendingEventHead; + while (p != null) { + Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, p.mSeq, 0, p); msg.setAsynchronous(true); mH.sendMessage(msg); - next = curr.mNext; - curr.mNext = prev; - prev = curr; - curr = next; + p = p.mNext; } } From 5ecacd667cc4a047ef9a4d89d218059230fa26e7 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Thu, 4 Apr 2013 10:47:52 -0700 Subject: [PATCH 45/76] Uphold common ordering expectations around action bar Home/Up dispatch Some apps aren't particularly happy if a stray key event is dispatched to a newly created window before its menu is prepared, causing the action bar's Home/Up event to be dispatched. Ignore these clicks on the Home/Up button before the menu goes through its initial prepare step. Note that it is still possible (and valid!) for Home/Up to be dispatched even if the app chose to return false from onCreateOptionsMenu or similar. Bug 7085070 Change-Id: If4b7d5f8c5a08ce8a094f1919347604d78ddedfb --- .../android/internal/widget/ActionBarView.java | 11 +++++++++-- .../android/internal/policy/impl/PhoneWindow.java | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index d1db230f507c4..b99b39ab575e7 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -36,7 +36,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -125,6 +124,7 @@ public class ActionBarView extends AbsActionBarView { private boolean mWasHomeEnabled; // Was it enabled before action view expansion? private MenuBuilder mOptionsMenu; + private boolean mMenuPrepared; private ActionBarContextView mContextView; @@ -164,7 +164,10 @@ public class ActionBarView extends AbsActionBarView { private final OnClickListener mUpClickListener = new OnClickListener() { public void onClick(View v) { - mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem); + if (mMenuPrepared) { + // Only invoke the window callback if the options menu has been initialized. + mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem); + } } }; @@ -402,6 +405,10 @@ public class ActionBarView extends AbsActionBarView { mCallback = callback; } + public void setMenuPrepared() { + mMenuPrepared = true; + } + public void setMenu(Menu menu, MenuPresenter.Callback cb) { if (menu == mOptionsMenu) return; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index ad5e20bcab6c8..6b28e8e68777c 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -380,6 +380,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { st.createdPanelView = cb.onCreatePanelView(st.featureId); } + final boolean isActionBarMenu = + (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR); + + if (isActionBarMenu && mActionBar != null) { + // Enforce ordering guarantees around events so that the action bar never + // dispatches menu-related events before the panel is prepared. + mActionBar.setMenuPrepared(); + } + if (st.createdPanelView == null) { // Init the panel state's menu--return false if init failed if (st.menu == null || st.refreshMenuContent) { @@ -389,7 +398,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } - if (mActionBar != null) { + if (isActionBarMenu && mActionBar != null) { if (mActionMenuPresenterCallback == null) { mActionMenuPresenterCallback = new ActionMenuPresenterCallback(); } @@ -405,7 +414,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Ditch the menu created above st.setMenu(null); - if (mActionBar != null) { + if (isActionBarMenu && mActionBar != null) { // Don't show it in the action bar either mActionBar.setMenu(null, mActionMenuPresenterCallback); } @@ -430,7 +439,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) { - if (mActionBar != null) { + if (isActionBarMenu && mActionBar != null) { // The app didn't want to show the menu for now but it still exists. // Clear it out of the action bar. mActionBar.setMenu(null, mActionMenuPresenterCallback); From 9c48aae04b271c703992a4ac3a372dba661e4dba Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Thu, 4 Apr 2013 13:19:10 -0700 Subject: [PATCH 46/76] PackageParser: ignore android:required on older apps Don't honor on older apps. Lots of apps in the wild are improperly using this, and we don't want to break them. Bug: 8528162 Change-Id: I6e0a10bc9feac58d13ef624239c6b91e9fc34590 --- core/java/android/content/pm/PackageParser.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4835d05468110..49cea3af4c3d8 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1398,6 +1398,17 @@ public class PackageParser { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } + /* + * b/8528162: Ignore the attribute if + * targetSdkVersion < JELLY_BEAN_MR2. There are lots of apps in the wild + * which are improperly using this attribute, even though it never worked. + */ + if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) { + for (int i = 0; i < pkg.requestedPermissionsRequired.size(); i++) { + pkg.requestedPermissionsRequired.set(i, Boolean.TRUE); + } + } + return pkg; } From 0983cb91e628743a9926f3a1f6c67a100040fb5e Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Fri, 22 Mar 2013 18:29:23 -0700 Subject: [PATCH 47/76] New NotificationListenerService. This is the best and only way for apps to listen for notifications: create a NotificationListenerService, wait for the NoMan to bind to you (as a result of the user checking a box somewhere in Settings and agreeing to a scary dialog box), and you'll start receiving notification posted and dismissed callbacks. Your service, while enabled, will also be able to clear one or all notifications. Use this power wisely. This change moves StatusBarNotification out of com.android.internal into android.service.notification. [Internal customers, including System UI and early users of the system-only listener binder API, will need to be updated.] Bug: 8199624 Change-Id: I1be46f823d4b3ddc901109ec1e085cd6deb740c2 --- Android.mk | 2 +- CleanSpec.mk | 1 + api/current.txt | 33 ++ .../android/app/INotificationManager.aidl | 12 +- core/java/android/provider/Settings.java | 16 + .../notification}/INotificationListener.aidl | 4 +- .../NotificationListenerService.java | 138 +++++++ .../notification}/StatusBarNotification.aidl | 2 +- .../notification}/StatusBarNotification.java | 37 +- .../internal/statusbar/IStatusBar.aidl | 2 +- .../internal/statusbar/IStatusBarService.aidl | 2 +- core/res/AndroidManifest.xml | 8 + core/res/res/values/strings.xml | 8 + core/res/res/values/symbols.xml | 1 + .../systemui/statusbar/BaseStatusBar.java | 2 +- .../systemui/statusbar/CommandQueue.java | 2 +- .../systemui/statusbar/NotificationData.java | 3 +- .../statusbar/phone/PhoneStatusBar.java | 2 +- .../systemui/statusbar/phone/Ticker.java | 5 +- .../statusbar/policy/LocationController.java | 5 - .../statusbar/tablet/TabletStatusBar.java | 2 +- .../statusbar/tablet/TabletTicker.java | 4 +- .../systemui/statusbar/tv/TvStatusBar.java | 2 +- .../server/NotificationManagerService.java | 368 +++++++++++++++--- .../server/StatusBarManagerService.java | 2 +- .../statusbartest/NotificationTestList.java | 3 - 26 files changed, 573 insertions(+), 93 deletions(-) rename core/java/android/{app => service/notification}/INotificationListener.aidl (89%) create mode 100644 core/java/android/service/notification/NotificationListenerService.java rename core/java/{com/android/internal/statusbar => android/service/notification}/StatusBarNotification.aidl (94%) rename core/java/{com/android/internal/statusbar => android/service/notification}/StatusBarNotification.java (80%) diff --git a/Android.mk b/Android.mk index 2ad7a725fa6f5..e70f9f3628123 100644 --- a/Android.mk +++ b/Android.mk @@ -69,7 +69,6 @@ LOCAL_SRC_FILES += \ core/java/android/app/IAlarmManager.aidl \ core/java/android/app/IBackupAgent.aidl \ core/java/android/app/IInstrumentationWatcher.aidl \ - core/java/android/app/INotificationListener.aidl \ core/java/android/app/INotificationManager.aidl \ core/java/android/app/IProcessObserver.aidl \ core/java/android/app/ISearchManager.aidl \ @@ -148,6 +147,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IUpdateLock.aidl \ core/java/android/os/IUserManager.aidl \ core/java/android/os/IVibratorService.aidl \ + core/java/android/service/notification/INotificationListener.aidl \ core/java/android/service/dreams/IDreamManager.aidl \ core/java/android/service/dreams/IDreamService.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index fc6386644c005..4debdc23ce8d5 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -159,6 +159,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodSession.*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/com/android/internal/view/IInputMethodCallback.*) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ diff --git a/api/current.txt b/api/current.txt index 998c8051db77a..9487f892c14ac 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22,6 +22,7 @@ package android { field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"; field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD"; + field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"; field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS"; field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; @@ -20834,6 +20835,38 @@ package android.service.dreams { } +package android.service.notification { + + public abstract class NotificationListenerService extends android.app.Service { + ctor public NotificationListenerService(); + method public final void clearAllNotifications(); + method public final void clearNotification(java.lang.String, java.lang.String, int); + method public android.os.IBinder onBind(android.content.Intent); + method public abstract void onNotificationPosted(android.service.notification.StatusBarNotification); + method public abstract void onNotificationRemoved(android.service.notification.StatusBarNotification); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; + } + + public class StatusBarNotification implements android.os.Parcelable { + ctor public StatusBarNotification(java.lang.String, java.lang.String, int, java.lang.String, int, int, int, android.app.Notification, android.os.UserHandle, long); + ctor public StatusBarNotification(android.os.Parcel); + method public android.service.notification.StatusBarNotification clone(); + method public int describeContents(); + method public int getUserId(); + method public boolean isClearable(); + method public boolean isOngoing(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final int id; + field public final android.app.Notification notification; + field public final java.lang.String pkg; + field public final long postTime; + field public final java.lang.String tag; + field public final android.os.UserHandle user; + } + +} + package android.service.textservice { public abstract class SpellCheckerService extends android.app.Service { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 3d9b2ae835183..92ec3adf0698a 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -17,12 +17,12 @@ package android.app; -import android.app.INotificationListener; import android.app.ITransientNotification; +import android.service.notification.StatusBarNotification; import android.app.Notification; +import android.content.ComponentName; import android.content.Intent; - -import com.android.internal.statusbar.StatusBarNotification; +import android.service.notification.INotificationListener; /** {@hide} */ interface INotificationManager @@ -41,7 +41,9 @@ interface INotificationManager StatusBarNotification[] getActiveNotifications(String callingPkg); StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count); - void registerListener(in INotificationListener listener, String pkg, int userid); + void registerListener(in INotificationListener listener, in ComponentName component, int userid); void unregisterListener(in INotificationListener listener, int userid); -} + void clearNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); + void clearAllNotificationsFromListener(in INotificationListener token); +} \ No newline at end of file diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 03ee9eb5928a2..7c73dadcc8855 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -655,6 +655,22 @@ public final class Settings { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS"; + /** + * Activity Action: Show Notification listener settings. + *

+ * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + *

+ * Input: Nothing. + *

+ * Output: Nothing. + * @see android.service.notification.NotificationListenerService + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS + = "android.settings.NOTIFICATION_LISTENER_SETTINGS"; + // End of Intent actions for Settings /** diff --git a/core/java/android/app/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl similarity index 89% rename from core/java/android/app/INotificationListener.aidl rename to core/java/android/service/notification/INotificationListener.aidl index f010a2a508300..425fdc113a344 100644 --- a/core/java/android/app/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.app; +package android.service.notification; -import com.android.internal.statusbar.StatusBarNotification; +import android.service.notification.StatusBarNotification; /** @hide */ oneway interface INotificationListener diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java new file mode 100644 index 0000000000000..86bab2a23063d --- /dev/null +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import android.annotation.SdkConstant; +import android.app.INotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +public abstract class NotificationListenerService extends Service { + // TAG = "NotificationListenerService[MySubclass]" + private final String TAG = NotificationListenerService.class.getSimpleName() + + "[" + getClass().getSimpleName() + "]"; + + private INotificationListenerWrapper mWrapper = null; + + private INotificationManager mNoMan; + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE + = "android.service.notification.NotificationListenerService"; + + /** + * Implement this method to learn about new notifications as they are posted by apps. + * + * @param sbn A data structure encapsulating the original {@link android.app.Notification} + * object as well as its identifying information (tag and id) and source + * (package name). + */ + public abstract void onNotificationPosted(StatusBarNotification sbn); + + /** + * Implement this method to learn when notifications are removed. + *

+ * This might occur because the user has dismissed the notification using system UI (or another + * notification listener) or because the app has withdrawn the notification. + * + * @param sbn A data structure encapsulating the original {@link android.app.Notification} + * object as well as its identifying information (tag and id) and source + * (package name). + */ + public abstract void onNotificationRemoved(StatusBarNotification sbn); + + private final INotificationManager getNotificationInterface() { + if (mNoMan == null) { + mNoMan = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + } + return mNoMan; + } + + /** + * Inform the notification manager about dismissal of a single notification. + *

+ * Use this if your listener has a user interface that allows the user to dismiss individual + * notifications, similar to the behavior of Android's status bar and notification panel. + * It should be called after the user dismisses a single notification using your UI; + * upon being informed, the notification manager will actually remove the notification + * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback. + *

+ * Note: If your listener allows the user to fire a notification's + * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call + * this method at that time if the Notification in question has the + * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set. + * + * @param pkg Package of the notifying app. + * @param tag Tag of the notification as specified by the notifying app in + * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. + * @param id ID of the notification as specified by the notifying app in + * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}. + */ + public final void clearNotification(String pkg, String tag, int id) { + try { + getNotificationInterface().clearNotificationFromListener(mWrapper, pkg, tag, id); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + /** + * Inform the notification manager about dismissal of all notifications. + *

+ * Use this if your listener has a user interface that allows the user to dismiss all + * notifications, similar to the behavior of Android's status bar and notification panel. + * It should be called after the user invokes the "dismiss all" function of your UI; + * upon being informed, the notification manager will actually remove all active notifications + * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks. + * + * {@see #clearNotification(String, String, int)} + */ + public final void clearAllNotifications() { + try { + getNotificationInterface().clearAllNotificationsFromListener(mWrapper); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + + @Override + public IBinder onBind(Intent intent) { + if (mWrapper == null) { + mWrapper = new INotificationListenerWrapper(); + } + return mWrapper; + } + + private class INotificationListenerWrapper extends INotificationListener.Stub { + @Override + public void onNotificationPosted(StatusBarNotification sbn) { + NotificationListenerService.this.onNotificationPosted(sbn); + } + @Override + public void onNotificationRemoved(StatusBarNotification sbn) { + NotificationListenerService.this.onNotificationRemoved(sbn); + } + } +} diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl b/core/java/android/service/notification/StatusBarNotification.aidl similarity index 94% rename from core/java/com/android/internal/statusbar/StatusBarNotification.aidl rename to core/java/android/service/notification/StatusBarNotification.aidl index bd9e89ce8a42e..ba8197206dc35 100644 --- a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl +++ b/core/java/android/service/notification/StatusBarNotification.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.statusbar; +package android.service.notification; parcelable StatusBarNotification; diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java similarity index 80% rename from core/java/com/android/internal/statusbar/StatusBarNotification.java rename to core/java/android/service/notification/StatusBarNotification.java index 23e87fcd91829..ef5f8c4ac911d 100644 --- a/core/java/com/android/internal/statusbar/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.statusbar; +package android.service.notification; import android.app.Notification; import android.os.Parcel; @@ -23,34 +23,54 @@ import android.os.UserHandle; /** * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including - * the IStatusBar (in System UI). + * the status bar and any {@link android.service.notification.NotificationListenerService}s. */ public class StatusBarNotification implements Parcelable { + /** The package of the app that posted the notification. */ public final String pkg; - public final String basePkg; + /** The id supplied to {@link android.app.NotificationManager#notify}. */ public final int id; + /** The tag supplied to {@link android.app.NotificationManager#notify}, or null if no tag + * was specified. */ public final String tag; + + /** The notifying app's calling uid. @hide */ public final int uid; + /** The notifying app's base package. @hide */ + public final String basePkg; + /** @hide */ public final int initialPid; // TODO: make this field private and move callers to an accessor that // ensures sourceUser is applied. + + /** The {@link android.app.Notification} supplied to + * {@link android.app.NotificationManager#notify}. */ public final Notification notification; - public final int score; + /** The {@link android.os.UserHandle} for whom this notification is intended. */ public final UserHandle user; + /** The time (in {@link System#currentTimeMillis} time) the notification was posted, + * which may be different than {@link android.app.Notification#when}. + */ public final long postTime; - /** This is temporarily needed for the JB MR1 PDK. */ + /** @hide */ + public final int score; + + /** This is temporarily needed for the JB MR1 PDK. + * @hide */ @Deprecated public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score, Notification notification) { this(pkg, id, tag, uid, initialPid, score, notification, UserHandle.OWNER); } + /** @hide */ public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score, Notification notification, UserHandle user) { this(pkg, null, id, tag, uid, initialPid, score, notification, user); } + /** @hide */ public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid, int initialPid, int score, Notification notification, UserHandle user) { this(pkg, basePkg, id, tag, uid, initialPid, score, notification, user, @@ -147,10 +167,17 @@ public class StatusBarNotification implements Parcelable { this.score, this.notification); } + /** Convenience method to check the notification's flags for + * {@link Notification#FLAG_ONGOING_EVENT}. + */ public boolean isOngoing() { return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; } + /** Convenience method to check the notification's flags for + * either {@link Notification#FLAG_ONGOING_EVENT} or + * {@link Notification#FLAG_NO_CLEAR}. + */ public boolean isClearable() { return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 780f5b3504dde..58b15e28a6b75 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -17,7 +17,7 @@ package com.android.internal.statusbar; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarNotification; +import android.service.notification.StatusBarNotification; /** @hide */ oneway interface IStatusBar diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 04e5bc97d39c4..c98ba8d554280 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -19,7 +19,7 @@ package com.android.internal.statusbar; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; -import com.android.internal.statusbar.StatusBarNotification; +import android.service.notification.StatusBarNotification; /** @hide */ interface IStatusBarService diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 666d1c66e1d32..c5df0a2694c96 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2193,6 +2193,14 @@ android:description="@string/permdesc_accessNotifications" android:protectionLevel="signature|system" /> + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 4a159671a7397..6bf640379eb19 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1816,6 +1816,11 @@ Allows the app to retrieve, examine, and clear notifications, including those posted by other apps. + + bind to a notification listener service + + Allows the holder to bind to the top-level interface of a notification listener service. Should never be needed for normal apps. + @@ -3508,6 +3513,9 @@ Wallpaper Change wallpaper + + Notification listener /data/eri.xml diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ced0851cd11ad..bb35bab1cc200 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1638,6 +1638,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 7bdcf6ee74923..5b911c12c1790 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -17,11 +17,11 @@ package com.android.systemui.statusbar; +import android.service.notification.StatusBarNotification; import android.content.res.Configuration; 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 com.android.internal.widget.SizeAdaptiveLayout; import com.android.systemui.R; import com.android.systemui.SearchPanelView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 752bb0cdbfac2..cbbaab3dcb875 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -20,10 +20,10 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.service.notification.StatusBarNotification; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; -import com.android.internal.statusbar.StatusBarNotification; /** * This class takes the functions from IStatusBar that come in on diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index c82f25008f2d5..886ed772d9cde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -16,12 +16,11 @@ package com.android.systemui.statusbar; -import android.app.Notification; +import android.service.notification.StatusBarNotification; import android.os.IBinder; import android.view.View; import android.widget.ImageView; -import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; import java.util.Comparator; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 9f5457339e888..52f552b84c00e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -26,6 +26,7 @@ import android.app.ActivityManagerNative; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.service.notification.StatusBarNotification; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -76,7 +77,6 @@ import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java index ecc70d6ca63bf..976dd01c9c6ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.service.notification.StatusBarNotification; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -23,10 +24,7 @@ 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; @@ -35,7 +33,6 @@ import android.widget.ImageSwitcher; import java.util.ArrayList; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarNotification; import com.android.internal.util.CharSequences; import com.android.systemui.R; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java index 0944b40347164..68d048d4f355d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java @@ -28,16 +28,11 @@ import android.content.IntentFilter; import android.location.LocationManager; import android.os.UserHandle; import android.provider.Settings; -import android.util.Slog; -import android.view.View; -import android.widget.ImageView; // private NM API import android.app.INotificationManager; -import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; -import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; public class LocationController extends BroadcastReceiver { private static final String TAG = "StatusBar.LocationController"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 3d6bfe7afbb77..05bba897399f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -23,6 +23,7 @@ import android.app.ActivityManagerNative; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; +import android.service.notification.StatusBarNotification; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -58,7 +59,6 @@ import android.widget.ScrollView; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java index 08598747421de..725d9e6ab954c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletTicker.java @@ -21,9 +21,9 @@ import java.util.Arrays; import android.animation.LayoutTransition; import android.app.Notification; import android.app.PendingIntent; +import android.service.notification.StatusBarNotification; import android.content.Context; import android.content.res.Resources; -import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -37,11 +37,9 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.FrameLayout; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; import com.android.systemui.statusbar.StatusBarIconView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 413cc786c1d87..dc5de027f7853 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.tv; +import android.service.notification.StatusBarNotification; import com.android.internal.statusbar.StatusBarIcon; -import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.statusbar.BaseStatusBar; import android.os.IBinder; diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 44d730ceb946c..cfb892f489312 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -26,16 +26,17 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.INotificationManager; -import android.app.INotificationListener; import android.app.ITransientNotification; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -57,6 +58,9 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; +import android.service.notification.INotificationListener; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.AtomicFile; @@ -68,8 +72,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; -import com.android.internal.statusbar.StatusBarNotification; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -121,6 +123,8 @@ public class NotificationManagerService extends INotificationManager.Stub private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true; private static final boolean ENABLE_BLOCKED_TOASTS = true; + private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":"; + final Context mContext; final IActivityManager mAm; final UserManager mUserManager; @@ -163,8 +167,18 @@ public class NotificationManagerService extends INotificationManager.Stub private final AppOpsManager mAppOps; - private ArrayList mListeners = new ArrayList(); - private ArrayList mEnabledListenersForCurrentUser = new ArrayList(); + // contains connections to all connected listeners, including app services + // and system listeners + private ArrayList mListeners + = new ArrayList(); + // things that will be put into mListeners as soon as they're ready + private ArrayList mServicesBinding = new ArrayList(); + // lists the component names of all enabled (and therefore connected) listener + // app services for the current user only + private HashSet mEnabledListenersForCurrentUser + = new HashSet(); + // Just the packages from mEnabledListenersForCurrentUser + private HashSet mEnabledListenerPackageNames = new HashSet(); // Notification control database. For now just contains disabled packages. private AtomicFile mPolicyFile; @@ -181,27 +195,42 @@ public class NotificationManagerService extends INotificationManager.Stub private class NotificationListenerInfo implements DeathRecipient { INotificationListener listener; - String pkg; + ComponentName component; int userid; boolean isSystem; + ServiceConnection connection; - public NotificationListenerInfo(INotificationListener listener, String pkg, int userid, - boolean isSystem) { + public NotificationListenerInfo(INotificationListener listener, ComponentName component, + int userid, boolean isSystem) { this.listener = listener; - this.pkg = pkg; + this.component = component; this.userid = userid; this.isSystem = isSystem; + this.connection = null; + } + + public NotificationListenerInfo(INotificationListener listener, ComponentName component, + int userid, ServiceConnection connection) { + this.listener = listener; + this.component = component; + this.userid = userid; + this.isSystem = false; + this.connection = connection; } boolean enabledAndUserMatches(StatusBarNotification sbn) { final int nid = sbn.getUserId(); - if (!(isSystem || isEnabledForUser(nid))) return false; + if (!isEnabledForCurrentUser()) { + return false; + } if (this.userid == UserHandle.USER_ALL) return true; return (nid == UserHandle.USER_ALL || nid == this.userid); } public void notifyPostedIfUserMatch(StatusBarNotification sbn) { - if (!enabledAndUserMatches(sbn)) return; + if (!enabledAndUserMatches(sbn)) { + return; + } try { listener.onNotificationPosted(sbn); } catch (RemoteException ex) { @@ -220,15 +249,17 @@ public class NotificationManagerService extends INotificationManager.Stub @Override public void binderDied() { - unregisterListener(this.listener, this.userid); + if (connection == null) { + // This is not a service; it won't be recreated. We can give up this connection. + unregisterListener(this.listener, this.userid); + } } /** convenience method for looking in mEnabledListenersForCurrentUser */ - public boolean isEnabledForUser(int userid) { - for (int i=0; i toAdd; + final int currentUser = ActivityManager.getCurrentUser(); + + synchronized (mNotificationList) { + // unbind and remove all existing listeners + toRemove = mListeners.toArray(toRemove); + + toAdd = new ArrayList(); + final HashSet newEnabled = new HashSet(); + final HashSet newPackages = new HashSet(); + + // decode the list of components + if (flat != null) { + String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + for (int i=0; i=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (name.equals(info.component) + && info.userid == userid) { + // cut old connections + if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener); + mListeners.remove(i); + if (info.connection != null) { + mContext.unbindService(info.connection); + } + } + } + + Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); + intent.setComponent(name); + + intent.putExtra(Intent.EXTRA_CLIENT_LABEL, + com.android.internal.R.string.notification_listener_binding_label); + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( + mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0)); + + try { + if (DBG) Slog.v(TAG, "binding: " + intent); + if (!mContext.bindServiceAsUser(intent, + new ServiceConnection() { + INotificationListener mListener; + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mNotificationList) { + mServicesBinding.remove(servicesBindingTag); + try { + mListener = INotificationListener.Stub.asInterface(service); + NotificationListenerInfo info = new NotificationListenerInfo( + mListener, name, userid, this); + service.linkToDeath(info, 0); + mListeners.add(info); + } catch (RemoteException e) { + // already dead + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Slog.v(TAG, "notification listener connection lost: " + name); + } + }, + Context.BIND_AUTO_CREATE, + new UserHandle(userid))) + { + mServicesBinding.remove(servicesBindingTag); + Slog.w(TAG, "Unable to bind listener service: " + intent); + return; + } + } catch (SecurityException ex) { + Slog.e(TAG, "Unable to bind listener service: " + intent, ex); + return; + } + } + } + + /** + * Remove a listener binder directly + */ @Override public void unregisterListener(INotificationListener listener, int userid) { // no need to check permissions; if your listener binder is in the list, @@ -513,12 +689,39 @@ public class NotificationManagerService extends INotificationManager.Stub for (int i=N-1; i>=0; i--) { final NotificationListenerInfo info = mListeners.get(i); if (info.listener == listener && info.userid == userid) { - mListeners.remove(listener); + mListeners.remove(i); + if (info.connection != null) { + mContext.unbindService(info.connection); + } } } } } + /** + * Remove a listener service for the given user by ComponentName + */ + private void unregisterListenerService(ComponentName name, int userid) { + checkCallerIsSystem(); + + synchronized (mNotificationList) { + final int N = mListeners.size(); + for (int i=N-1; i>=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (name.equals(info.component) + && info.userid == userid) { + mListeners.remove(i); + if (info.connection != null) { + mContext.unbindService(info.connection); + } + } + } + } + } + + /** + * asynchronously notify all listeners about a new notification + */ private void notifyPostedLocked(NotificationRecord n) { final StatusBarNotification sbn = n.sbn; for (final NotificationListenerInfo info : mListeners) { @@ -530,6 +733,9 @@ public class NotificationManagerService extends INotificationManager.Stub } } + /** + * asynchronously notify all listeners about a removed notification + */ private void notifyRemovedLocked(NotificationRecord n) { final StatusBarNotification sbn = n.sbn; for (final NotificationListenerInfo info : mListeners) { @@ -541,6 +747,57 @@ public class NotificationManagerService extends INotificationManager.Stub } } + // -- APIs to support listeners clicking/clearing notifications -- + + private NotificationListenerInfo checkListenerToken(INotificationListener listener) { + final IBinder token = listener.asBinder(); + final int N = mListeners.size(); + for (int i=0; i 0)) { for (String pkgName : pkgList) { cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart, UserHandle.USER_ALL); + if (mEnabledListenerPackageNames.contains(pkgName)) { + anyListenersInvolved = true; + } } } + + if (anyListenersInvolved) { + // make sure we're still bound to any of our + // listeners who may have just upgraded + rebindListenerServices(); + } } else if (action.equals(Intent.ACTION_SCREEN_ON)) { // Keep track of screen on/off state, but do not turn off the notification light // until user passes through the lock screen or views the notification. @@ -795,7 +1063,7 @@ public class NotificationManagerService extends INotificationManager.Stub = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); private final Uri ENABLED_NOTIFICATION_LISTENERS_URI - = Settings.System.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); SettingsObserver(Handler handler) { super(handler); @@ -804,9 +1072,9 @@ public class NotificationManagerService extends INotificationManager.Stub void observe() { ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, - false, this); + false, this, UserHandle.USER_ALL); resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, - false, this); + false, this, UserHandle.USER_ALL); update(null); } @@ -825,19 +1093,7 @@ public class NotificationManagerService extends INotificationManager.Stub } } if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { - String pkglist = Settings.Secure.getString( - mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - mEnabledListenersForCurrentUser.clear(); - if (pkglist != null) { - String[] pkgs = pkglist.split(";"); - for (int i=0; i Date: Thu, 4 Apr 2013 22:45:12 -0700 Subject: [PATCH 48/76] Correctly manage the lifecycle of IME InputChannels. InputChannels are normally duplicated when sent to a remote process over Binder but this does not happen if the recipient is running within the system server process. This causes problems for KeyGuard because the InputMethodManagerService may accidentally dispose the channel that KeyGuard is using. Fixed the lifecycle of InputChannels that are managed by the IME framework. We now return a duplicate of the channel to the application and then take care to dispose of the duplicate when necessary. In particular, InputBindResult disposes its InputChannel automatically when returned through Binder (using PARCELABLE_WRITE_RETURN_VALUE). Bug: 8493879 Change-Id: I08ec3d13268c76f3b56706b4523508bcefa3be79 --- core/java/android/os/Binder.java | 10 +++++++- core/java/android/view/InputChannel.java | 12 ++++++++- .../internal/view/InputBindResult.java | 2 +- core/jni/android_view_InputChannel.cpp | 11 ++++++++ include/androidfw/InputTransport.h | 3 +++ libs/androidfw/InputTransport.cpp | 5 ++++ .../server/InputMethodManagerService.java | 25 +++++++++++++------ 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 16b48354a07b6..e9e755141b180 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -152,7 +152,15 @@ public class Binder implements IBinder { * not return until the current process is exiting. */ public static final native void joinThreadPool(); - + + /** + * Returns true if the specified interface is a proxy. + * @hide + */ + public static final boolean isProxy(IInterface iface) { + return iface.asBinder() != iface; + } + /** * Default constructor initializes the object. */ diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java index a797176d799db..40ee1ad3196a0 100644 --- a/core/java/android/view/InputChannel.java +++ b/core/java/android/view/InputChannel.java @@ -54,6 +54,7 @@ public final class InputChannel implements Parcelable { private native void nativeTransferTo(InputChannel other); private native void nativeReadFromParcel(Parcel parcel); private native void nativeWriteToParcel(Parcel parcel); + private native void nativeDup(InputChannel target); private native String nativeGetName(); @@ -64,7 +65,7 @@ public final class InputChannel implements Parcelable { */ public InputChannel() { } - + @Override protected void finalize() throws Throwable { try { @@ -125,6 +126,15 @@ public final class InputChannel implements Parcelable { nativeTransferTo(outParameter); } + /** + * Duplicates the input channel. + */ + public InputChannel dup() { + InputChannel target = new InputChannel(); + nativeDup(target); + return target; + } + @Override public int describeContents() { return Parcelable.CONTENTS_FILE_DESCRIPTOR; diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index 9143c61d3172d..14afe21d0619a 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -84,7 +84,7 @@ public final class InputBindResult implements Parcelable { dest.writeStrongInterface(method); if (channel != null) { dest.writeInt(1); - channel.writeToParcel(dest, 0); + channel.writeToParcel(dest, flags); } else { dest.writeInt(0); } diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp index 9c44a599a7a83..9fa9fe4914def 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -246,6 +246,15 @@ static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) return name; } +static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (nativeInputChannel) { + android_view_InputChannel_setNativeInputChannel(env, otherObj, + new NativeInputChannel(nativeInputChannel->getInputChannel()->dup())); + } +} + // ---------------------------------------------------------------------------- static JNINativeMethod gInputChannelMethods[] = { @@ -262,6 +271,8 @@ static JNINativeMethod gInputChannelMethods[] = { (void*)android_view_InputChannel_nativeWriteToParcel }, { "nativeGetName", "()Ljava/lang/String;", (void*)android_view_InputChannel_nativeGetName }, + { "nativeDup", "(Landroid/view/InputChannel;)V", + (void*)android_view_InputChannel_nativeDup }, }; #define FIND_CLASS(var, className) \ diff --git a/include/androidfw/InputTransport.h b/include/androidfw/InputTransport.h index 5706bcecfa4b2..87129958bf1a3 100644 --- a/include/androidfw/InputTransport.h +++ b/include/androidfw/InputTransport.h @@ -168,6 +168,9 @@ public: */ status_t receiveMessage(InputMessage* msg); + /* Returns a new object that has a duplicate of this channel's fd. */ + sp dup() const; + private: String8 mName; int mFd; diff --git a/libs/androidfw/InputTransport.cpp b/libs/androidfw/InputTransport.cpp index 351c6662eb225..498389eac3f61 100644 --- a/libs/androidfw/InputTransport.cpp +++ b/libs/androidfw/InputTransport.cpp @@ -219,6 +219,11 @@ status_t InputChannel::receiveMessage(InputMessage* msg) { return OK; } +sp InputChannel::dup() const { + int fd = ::dup(getFd()); + return fd >= 0 ? new InputChannel(getName(), fd) : NULL; +} + // --- InputPublisher --- diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 3b541ec3963b7..a28c387c66f69 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1070,7 +1070,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(getAppShowFlags(), null); } - return new InputBindResult(session.session, session.channel, mCurId, mCurSeq); + return new InputBindResult(session.session, + session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq); } InputBindResult startInputLocked(IInputMethodClient client, @@ -2357,13 +2358,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return true; case MSG_CREATE_SESSION: { args = (SomeArgs)msg.obj; + IInputMethod method = (IInputMethod)args.arg1; InputChannel channel = (InputChannel)args.arg2; try { - ((IInputMethod)args.arg1).createSession(channel, - (IInputSessionCallback)args.arg3); + method.createSession(channel, (IInputSessionCallback)args.arg3); } catch (RemoteException e) { } finally { - if (channel != null) { + // Dispose the channel if the input method is not local to this process + // because the remote proxy will get its own copy when unparceled. + if (channel != null && Binder.isProxy(method)) { channel.dispose(); } } @@ -2404,16 +2407,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // There is nothing interesting about the last client dying. } return true; - case MSG_BIND_METHOD: + case MSG_BIND_METHOD: { args = (SomeArgs)msg.obj; + IInputMethodClient client = (IInputMethodClient)args.arg1; + InputBindResult res = (InputBindResult)args.arg2; try { - ((IInputMethodClient)args.arg1).onBindMethod( - (InputBindResult)args.arg2); + client.onBindMethod(res); } catch (RemoteException e) { Slog.w(TAG, "Client died receiving input method " + args.arg2); + } finally { + // Dispose the channel if the input method is not local to this process + // because the remote proxy will get its own copy when unparceled. + if (res.channel != null && Binder.isProxy(client)) { + res.channel.dispose(); + } } args.recycle(); return true; + } case MSG_SET_ACTIVE: try { ((ClientState)msg.obj).client.setActive(msg.arg1 != 0); From c86d5e7dcad163170eebfbf16d194084e6d6ee58 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Mon, 8 Apr 2013 16:05:00 -0700 Subject: [PATCH 49/76] Check outInfo is not null Only call keystore if outInfo is not null Bug: 8568820 Change-Id: I24ab625ad02b8510da489911064453de642b6748 --- .../server/pm/PackageManagerService.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index ca7bba21e9b2d..3d7dd6383f7d5 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -8647,14 +8647,16 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.writeLPr(); } } - // A user ID was deleted here. Go through all users and remove it from - // KeyStore. - final int appId = outInfo.removedAppId; - if (appId != -1) { - final KeyStore keyStore = KeyStore.getInstance(); - if (keyStore != null) { - for (final int userId : sUserManager.getUserIds()) { - keyStore.clearUid(UserHandle.getUid(userId, appId)); + if (outInfo != null) { + // A user ID was deleted here. Go through all users and remove it + // from KeyStore. + final int appId = outInfo.removedAppId; + if (appId != -1) { + final KeyStore keyStore = KeyStore.getInstance(); + if (keyStore != null) { + for (final int userId : sUserManager.getUserIds()) { + keyStore.clearUid(UserHandle.getUid(userId, appId)); + } } } } From ffd6c490e6e074b6f86b4edf050e53665eff2dd8 Mon Sep 17 00:00:00 2001 From: Eric Rowe Date: Mon, 8 Apr 2013 15:00:27 -0700 Subject: [PATCH 50/76] Change dropbox tag for native crashes. Change-Id: Id2738fc758c3d144e5fdd2c7f1c8617dad129338 --- .../java/com/android/server/am/ActivityManagerService.java | 6 +++--- .../java/com/android/server/am/NativeCrashListener.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 7710f136702d7..26a20b9312bbc 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -8350,13 +8350,13 @@ public final class ActivityManagerService extends ActivityManagerNative final String processName = app == null ? "system_server" : (r == null ? "unknown" : r.processName); - handleApplicationCrashInner(r, processName, crashInfo); + handleApplicationCrashInner("crash", r, processName, crashInfo); } /* Native crash reporting uses this inner version because it needs to be somewhat * decoupled from the AM-managed cleanup lifecycle */ - void handleApplicationCrashInner(ProcessRecord r, String processName, + void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) { EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(), UserHandle.getUserId(Binder.getCallingUid()), processName, @@ -8366,7 +8366,7 @@ public final class ActivityManagerService extends ActivityManagerNative crashInfo.throwFileName, crashInfo.throwLineNumber); - addErrorToDropBox("crash", r, processName, null, null, null, null, null, crashInfo); + addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo); crashApplication(r, crashInfo); } diff --git a/services/java/com/android/server/am/NativeCrashListener.java b/services/java/com/android/server/am/NativeCrashListener.java index e83433fd65e99..0688c50e863c6 100644 --- a/services/java/com/android/server/am/NativeCrashListener.java +++ b/services/java/com/android/server/am/NativeCrashListener.java @@ -82,7 +82,7 @@ class NativeCrashListener extends Thread { ci.stackTrace = mCrashReport; if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()"); - mAm.handleApplicationCrashInner(mApp, mApp.processName, ci); + mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci); if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned"); } catch (Exception e) { Slog.e(TAG, "Unable to report native crash", e); From c45da6d7cee73da07b205b20ee9b8aff8788c7e5 Mon Sep 17 00:00:00 2001 From: Tim Murray Date: Wed, 10 Apr 2013 11:19:54 -0700 Subject: [PATCH 51/76] Add GC thread to RSGL. bug 8585185 Change-Id: I06df29c3be831d6cd53fa8913ba3731f29c137d1 --- graphics/java/android/renderscript/RenderScriptGL.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graphics/java/android/renderscript/RenderScriptGL.java b/graphics/java/android/renderscript/RenderScriptGL.java index 52034b19e7062..fad883873458e 100644 --- a/graphics/java/android/renderscript/RenderScriptGL.java +++ b/graphics/java/android/renderscript/RenderScriptGL.java @@ -198,6 +198,9 @@ public class RenderScriptGL extends RenderScript { } mMessageThread = new MessageThread(this); mMessageThread.start(); + mGCThread = new GCThread(this); + mGCThread.start(); + } /** From fb01e4b634a4126141c361fc641ec03f872826ba Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Wed, 10 Apr 2013 12:33:15 -0700 Subject: [PATCH 52/76] Don't draw synchronously in onResume() this could cause a dead-lock if the applicaltion's draw implementation blocks until something happenson the main ui thread. note: we're still doing this synchronous draw in onWindowResize() because that's what the previous implementation did. Technically, it has the same problem. Bug: 8586305 Change-Id: I782568289cc9419346aeea73775d86faa28b3058 --- opengl/java/android/opengl/GLSurfaceView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index daa5d1344a604..ab7ceb61dbce2 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -1507,7 +1507,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mPaused = false; updateState(); if (mRenderMode == RENDERMODE_WHEN_DIRTY) { - executeDraw(); + requestRender(); } } From 90d8491df8c1d564647a33fe88619211881a409d Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Wed, 10 Apr 2013 11:27:08 -0700 Subject: [PATCH 53/76] Permit null adapters in Spinners Bug 8538144 Change-Id: I83f0ae78ce15f46827fb721cd0df1972b7d9d198 --- core/java/android/widget/Spinner.java | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index b6895a59e0a8b..e3de0b9e75f0f 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -494,20 +494,23 @@ public class Spinner extends AbsSpinner implements OnClickListener { // Make selected view and position it mFirstPosition = mSelectedPosition; - View sel = makeAndAddView(mSelectedPosition); - int width = sel.getMeasuredWidth(); - int selectedOffset = childrenLeft; - final int layoutDirection = getLayoutDirection(); - final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); - switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.CENTER_HORIZONTAL: - selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2); - break; - case Gravity.RIGHT: - selectedOffset = childrenLeft + childrenWidth - width; - break; + + if (mAdapter != null) { + View sel = makeAndAddView(mSelectedPosition); + int width = sel.getMeasuredWidth(); + int selectedOffset = childrenLeft; + final int layoutDirection = getLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.CENTER_HORIZONTAL: + selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2); + break; + case Gravity.RIGHT: + selectedOffset = childrenLeft + childrenWidth - width; + break; + } + sel.offsetLeftAndRight(selectedOffset); } - sel.offsetLeftAndRight(selectedOffset); // Flush any cached views that did not get reused above mRecycler.clear(); From 6853ac8340450fad8c1acc72a72d102afbaf8701 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 11 Apr 2013 19:21:32 -0700 Subject: [PATCH 54/76] Ensure looper quits after all other messages are handled. Bug: 8596303 Change-Id: Ie8bfbbe810f5ade2afd870c8e675ce1353a80e5d --- core/java/android/os/Looper.java | 12 ++++++++++-- core/java/android/os/MessageQueue.java | 11 ++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 363a1bfd4c554..fa28765206f0b 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -201,8 +201,16 @@ public final class Looper { /** * Quits the looper. - * - * Causes the {@link #loop} method to terminate as soon as possible. + *

+ * Causes the {@link #loop} method to terminate as soon as all remaining messages + * in the message queue that are already due to be delivered have been handled. + * However delayed messages with due times in the future may not be handled before + * the loop terminates. + *

+ * Any attempt to post messages to the queue after {@link #quit} has been called + * will fail. For example, the {@link Handler#sendMessage(Message)} method will + * return false when the looper is being terminated. + *

*/ public void quit() { mQueue.quit(); diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index e0d40c96aafa7..c058bfce28e65 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -132,11 +132,6 @@ public final class MessageQueue { nativePollOnce(mPtr, nextPollTimeoutMillis); synchronized (this) { - if (mQuiting) { - dispose(); - return null; - } - // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; @@ -170,6 +165,12 @@ public final class MessageQueue { nextPollTimeoutMillis = -1; } + // Process the quit message now that all pending messages have been handled. + if (mQuiting) { + dispose(); + return null; + } + // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. From 397372f774d33cec979c7d97ee8b1a1866569b03 Mon Sep 17 00:00:00 2001 From: Jim Miller Date: Wed, 10 Apr 2013 18:15:30 -0700 Subject: [PATCH 55/76] Fix keyguard pattern invalidate bug The framework used to invalidate both the previous invalidate region and the current one. However, with change I9fb96f99 this is no longer the case (it was always true for software rendering, but never showed because most platforms use acceleration). The fix is to invalidate the union of the old region and the new region ourselves. Fixes bug 8503031 Change-Id: If303e0892772dd9d6915efa55118d054a742c1a5 --- .../internal/widget/LockPatternView.java | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 1b088b3c81ac9..b066d703360a5 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -38,7 +38,6 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import com.android.internal.R; -import com.android.internal.widget.LockPatternView.Cell; import java.util.ArrayList; import java.util.List; @@ -51,7 +50,6 @@ import java.util.List; * "correct" states. */ public class LockPatternView extends View { - private static final String TAG = "LockPatternView"; // Aspect to use when rendering this view private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h) @@ -63,9 +61,6 @@ public class LockPatternView extends View { private Paint mPaint = new Paint(); private Paint mPathPaint = new Paint(); - // TODO: make this common with PhoneWindow - static final int STATUS_BAR_HEIGHT = 25; - /** * How many milliseconds we spend animating each circle of a lock pattern * if the animating mode is set. The entire animation should take this @@ -124,6 +119,7 @@ public class LockPatternView extends View { private final Path mCurrentPath = new Path(); private final Rect mInvalidate = new Rect(); + private final Rect mTmpInvalidateRect = new Rect(); private int mBitmapWidth; private int mBitmapHeight; @@ -132,7 +128,6 @@ public class LockPatternView extends View { private final Matrix mArrowMatrix = new Matrix(); private final Matrix mCircleMatrix = new Matrix(); - /** * Represents a cell in the 3 X 3 matrix of the unlock pattern view. */ @@ -680,8 +675,9 @@ public class LockPatternView extends View { private void handleActionMove(MotionEvent event) { // Handle all recent motion events so we don't skip any cells even when the device // is busy... + final float radius = (mSquareWidth * mDiameterFactor * 0.5f); final int historySize = event.getHistorySize(); - Rect invalidateRect = mInvalidate; + mTmpInvalidateRect.setEmpty(); boolean invalidateNow = false; for (int i = 0; i < historySize + 1; i++) { final float x = i < historySize ? event.getHistoricalX(i) : event.getX(); @@ -702,30 +698,30 @@ public class LockPatternView extends View { if (mPatternInProgress && patternSize > 0) { final ArrayList pattern = mPattern; final Cell lastCell = pattern.get(patternSize - 1); - float startX = getCenterXForColumn(lastCell.column); - float startY = getCenterYForRow(lastCell.row); + float lastCellCenterX = getCenterXForColumn(lastCell.column); + float lastCellCenterY = getCenterYForRow(lastCell.row); - // Adjust for current position. Radius accounts for line width. - final float radius = (mSquareWidth * mDiameterFactor * 0.5f); - float left = Math.min(startX, x) - radius; - float right = Math.max(startX, x) + radius; - float top = Math.min(startY, y) - radius; - float bottom = Math.max(startY, y) + radius; + // Adjust for drawn segment from last cell to (x,y). Radius accounts for line width. + float left = Math.min(lastCellCenterX, x) - radius; + float right = Math.max(lastCellCenterX, x) + radius; + float top = Math.min(lastCellCenterY, y) - radius; + float bottom = Math.max(lastCellCenterY, y) + radius; // Invalidate between the pattern's new cell and the pattern's previous cell - if (hitCell != null && patternSize >= 2) { + if (hitCell != null) { final float width = mSquareWidth * 0.5f; final float height = mSquareHeight * 0.5f; - final float x2 = getCenterXForColumn(hitCell.column); - final float y2 = getCenterYForRow(hitCell.row); - left = Math.min(x2, left - width); - right = Math.max(x2, right + width); - top = Math.min(y2, top - height); - bottom = Math.max(y2, bottom + height); + final float hitCellCenterX = getCenterXForColumn(hitCell.column); + final float hitCellCenterY = getCenterYForRow(hitCell.row); + + left = Math.min(hitCellCenterX - width, left); + right = Math.max(hitCellCenterX + width, right); + top = Math.min(hitCellCenterY - height, top); + bottom = Math.max(hitCellCenterY + height, bottom); } // Invalidate between the pattern's last cell and the previous location - invalidateRect.union(Math.round(left), Math.round(top), + mTmpInvalidateRect.union(Math.round(left), Math.round(top), Math.round(right), Math.round(bottom)); } } @@ -734,8 +730,9 @@ public class LockPatternView extends View { // To save updates, we only invalidate if the user moved beyond a certain amount. if (invalidateNow) { - invalidate(invalidateRect); - invalidateRect.setEmpty(); + mInvalidate.union(mTmpInvalidateRect); + invalidate(mInvalidate); + mInvalidate.set(mTmpInvalidateRect); } } From 22921988c2bd5214af15d517dd358b4f10e2524c Mon Sep 17 00:00:00 2001 From: Jason Sams Date: Sat, 13 Apr 2013 19:48:36 -0700 Subject: [PATCH 56/76] Revert GC thread changes This is not quite a straight revery, some manual edits were necessary. The original CL didn't undergo sufficient design review or testing. Revert until the regressions can be sorted out. Bug 8585185 This reverts commit 6dacf8355a0692b52c49f603f43317772cb36175 This reverts commit f8c033db1edf36a0ab09568c3142054f0be2d1a1 Change-Id: Ie7215bdf881332e822603547e92f810f595077fc --- .../java/android/renderscript/Allocation.java | 18 +---- .../java/android/renderscript/BaseObj.java | 6 -- .../android/renderscript/RenderScript.java | 72 ------------------- .../android/renderscript/RenderScriptGL.java | 3 - .../java/android/renderscript/ScriptC.java | 2 - 5 files changed, 2 insertions(+), 99 deletions(-) diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index ea29b7dc49cc3..5d1990a4b85e9 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -277,21 +277,13 @@ public class Allocation extends BaseObj { throw new RSIllegalArgumentException("Invalid usage combination."); } } - if (t != null) { - // don't need to account for USAGE_SHARED Allocations - if ((usage & USAGE_SHARED) == 0) { - int numBytes = t.getCount() * t.getElement().getBytesSize(); - rs.addAllocSizeForGC(numBytes); - mGCSize = numBytes; - } - } + mType = t; mUsage = usage; if (t != null) { updateCacheInfo(t); } - } private void validateIsInt32() { @@ -355,12 +347,6 @@ public class Allocation extends BaseObj { mType.updateFromNative(); updateCacheInfo(mType); } - // don't need to account for USAGE_SHARED Allocations - if ((mUsage & USAGE_SHARED) == 0) { - int numBytes = mType.getCount() * mType.getElement().getBytesSize(); - mRS.addAllocSizeForGC(numBytes); - mGCSize = numBytes; - } } /** @@ -1264,7 +1250,6 @@ public class Allocation extends BaseObj { if (type.getID(rs) == 0) { throw new RSInvalidStateException("Bad Type"); } - int id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, 0); if (id == 0) { throw new RSRuntimeException("Allocation creation failed."); @@ -1414,6 +1399,7 @@ public class Allocation extends BaseObj { return alloc; } + int id = rs.nAllocationCreateFromBitmap(t.getID(rs), mips.mID, b, usage); if (id == 0) { throw new RSRuntimeException("Load failed."); diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java index c2ebc9f79d6a3..f464f9bb1025a 100644 --- a/graphics/java/android/renderscript/BaseObj.java +++ b/graphics/java/android/renderscript/BaseObj.java @@ -71,9 +71,6 @@ public class BaseObj { private int mID; private boolean mDestroyed; private String mName; - - int mGCSize; - RenderScript mRS; /** @@ -138,9 +135,6 @@ public class BaseObj { throw new RSInvalidStateException("Object already destroyed."); } mDestroyed = true; - if (mGCSize != 0) { - mRS.removeAllocSizeForGC(mGCSize); - } mRS.nObjDestroy(mID); } diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index 33639dc57d4fb..6f614c3767e9c 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -18,9 +18,7 @@ package android.renderscript; import java.io.File; import java.lang.reflect.Field; -import java.util.concurrent.locks.*; -import android.app.ActivityManager; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -804,8 +802,6 @@ public class RenderScript { int mContext; @SuppressWarnings({"FieldCanBeLocal"}) MessageThread mMessageThread; - GCThread mGCThread; - Element mElement_U8; Element mElement_I8; @@ -1095,60 +1091,6 @@ public class RenderScript { } } - static class GCThread extends Thread { - RenderScript mRS; - boolean mRun = true; - - long currentSize = 0; - long targetSize; // call System.gc after 512MB of allocs - - final Lock lock = new ReentrantLock(); - final Condition cond = lock.newCondition(); - - GCThread(RenderScript rs) { - super("RSGCThread"); - mRS = rs; - - } - - public void run() { - ActivityManager am = (ActivityManager)mRS.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); - ActivityManager.MemoryInfo meminfo = new ActivityManager.MemoryInfo(); - am.getMemoryInfo(meminfo); - targetSize = (long)(meminfo.totalMem * .5f); - - while(mRun) { - System.gc(); - lock.lock(); - try { - cond.awaitUninterruptibly(); - } finally { - lock.unlock(); - } - } - - Log.d(LOG_TAG, "GCThread exiting."); - } - - public synchronized void addAllocSize(long bytes) { - currentSize += bytes; - if (currentSize >= targetSize) { - lock.lock(); - try { - cond.signal(); - } finally { - lock.unlock(); - } - } - } - - public synchronized void removeAllocSize(long bytes) { - currentSize -= bytes; - } - - } - - RenderScript(Context ctx) { if (ctx != null) { mApplicationContext = ctx.getApplicationContext(); @@ -1171,15 +1113,6 @@ public class RenderScript { return create(ctx, sdkVersion, ContextType.NORMAL); } - void addAllocSizeForGC(int bytes) { - mGCThread.addAllocSize(bytes); - } - - void removeAllocSizeForGC(int bytes) { - mGCThread.removeAllocSize(bytes); - } - - /** * Create a basic RenderScript context. * @@ -1196,9 +1129,7 @@ public class RenderScript { throw new RSDriverException("Failed to create RS context."); } rs.mMessageThread = new MessageThread(rs); - rs.mGCThread = new GCThread(rs); rs.mMessageThread.start(); - rs.mGCThread.start(); return rs; } @@ -1253,11 +1184,8 @@ public class RenderScript { validate(); nContextDeinitToClient(mContext); mMessageThread.mRun = false; - mGCThread.mRun = false; - mGCThread.addAllocSize(0); try { mMessageThread.join(); - mGCThread.join(); } catch(InterruptedException e) { } diff --git a/graphics/java/android/renderscript/RenderScriptGL.java b/graphics/java/android/renderscript/RenderScriptGL.java index fad883873458e..52034b19e7062 100644 --- a/graphics/java/android/renderscript/RenderScriptGL.java +++ b/graphics/java/android/renderscript/RenderScriptGL.java @@ -198,9 +198,6 @@ public class RenderScriptGL extends RenderScript { } mMessageThread = new MessageThread(this); mMessageThread.start(); - mGCThread = new GCThread(this); - mGCThread.start(); - } /** diff --git a/graphics/java/android/renderscript/ScriptC.java b/graphics/java/android/renderscript/ScriptC.java index 2f69775a20ae9..221f76060bba5 100644 --- a/graphics/java/android/renderscript/ScriptC.java +++ b/graphics/java/android/renderscript/ScriptC.java @@ -60,8 +60,6 @@ public class ScriptC extends Script { throw new RSRuntimeException("Loading of ScriptC script failed."); } setID(id); - mGCSize = 2 * 1024 * 1024; - rs.addAllocSizeForGC(mGCSize); } /** From 0a80ff3de103077e04e73738d28bf036272902af Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Mon, 15 Apr 2013 12:10:36 -0700 Subject: [PATCH 57/76] Fully setup newly bound service before state management. If the connected service is not entirely setup when calling the method for handling a change in the current user state we get a potential NPE since the management method may have discarded the service, thus nullifying the connection to it. Now the service is fully configured before calling the state change management method. bug:8600489 Change-Id: Ib0bf7c6d575e15c620da419d43ece22f4187fd34 --- .../server/accessibility/AccessibilityManagerService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 110c4dadc201c..ac4f970d59883 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1817,11 +1817,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { addServiceLocked(this, userState); if (userState.mBindingServices.contains(mComponentName)) { userState.mBindingServices.remove(mComponentName); - onUserStateChangedLocked(userState); try { - mServiceInterface.setConnection(this, mId); + mServiceInterface.setConnection(this, mId); + onUserStateChangedLocked(userState); } catch (RemoteException re) { - Slog.w(LOG_TAG, "Error while setting connection for service: " + service, re); + Slog.w(LOG_TAG, "Error while setting connection for service: " + + service, re); + binderDied(); } } else { binderDied(); From fae36f3d2a3606dbda1dbcff4e87aaa18bb4a40d Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Mon, 15 Apr 2013 12:28:42 -0700 Subject: [PATCH 58/76] Crash on non-eng builds due to lacking null check. On eng builds we have an event consistency verifier to log any inconsistent event stream states due to mishandling of intercepted events by an accessibility service. On non-eng builds this verifier is null and a null check was lacking. bug:8616711 Change-Id: Ib083a405dfa8340025090a65e50155eb10526a90 --- .../server/accessibility/AccessibilityManagerService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index ac4f970d59883..128a49f348875 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2501,7 +2501,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public void flush() { synchronized (mLock) { cancelAllPendingEventsLocked(); - mSentEventsVerifier.reset(); + if (mSentEventsVerifier != null) { + mSentEventsVerifier.reset(); + } } } From 38eded77a5fa88c6878968701fd9916d94bbec4f Mon Sep 17 00:00:00 2001 From: Irfan Sheriff Date: Mon, 15 Apr 2013 15:26:01 -0700 Subject: [PATCH 59/76] Fix CTS failure due to invalid EAP update Update EAP only when it has valid entry Bug: 8604987 Change-Id: Ib6b10bd06c26ee91c4ecd3a231b509d728725dfc --- wifi/java/android/net/wifi/WifiConfigStore.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index d3d5b1bb48b7d..23a4e716a4b61 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -1111,7 +1111,8 @@ class WifiConfigStore { break setVariables; } - if (config.enterpriseConfig != null) { + if (config.enterpriseConfig != null && + config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) { WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; From 18ed3c85825275116d6f23a709b67a43402f30bb Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 15 Apr 2013 15:36:53 -0700 Subject: [PATCH 60/76] Maybe fix issue #8620910: Win_sdk build failed and unable to create... ...the sdk platform repo Change-Id: Ib6cd7c0dfb9b6217ae79af3e2ac25fe0442996e3 --- libs/androidfw/AssetManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index e0d96c93c600d..b08c36bd208fb 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -33,7 +33,9 @@ #include #include #include +#ifdef HAVE_ANDROID_OS #include +#endif #include #include From 1c4bf946641b6ccfa95140ba4ea98569683a2b81 Mon Sep 17 00:00:00 2001 From: Irfan Sheriff Date: Tue, 16 Apr 2013 13:52:32 -0700 Subject: [PATCH 61/76] Fix enabling networks after driver stop Bug: 8630194 Change-Id: If897fc1fe54bf2f35362b8482e56d98f80a3130c --- wifi/java/android/net/wifi/WifiStateMachine.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index a5d98cc1f4d4c..2c3df9516a179 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -2383,6 +2383,8 @@ public class WifiStateMachine extends StateMachine { mWifiNative.disconnect(); transitionTo(mScanModeState); } else { + /* Driver stop may have disabled networks, enable right after start */ + mWifiConfigStore.enableAllNetworks(); mWifiNative.reconnect(); // Status pulls in the current supplicant state and network connection state // events over the monitor connection. This helps framework sync up with From 225a0220f4c040ccdf168e2b1004f009f515a6d7 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 18 Apr 2013 15:17:48 -0700 Subject: [PATCH 62/76] Fix change of behavior in Looper.quit(). It seems some applications rely on Looper.quit() terminating the loop immediately without processing all messages. Rather than risk breaking them, make the safer behavior optional. Also take care to properly drain the message queue before quitting so that all of the Message instances are recycled. This may help release storage sooner in case the Looper doesn't get GC'd promptly and its remaining queue of undelivered messages sticks around. Improve docs on runWithScissors. Bug: 8596303 Change-Id: I8cbeb6f7a5f6b8e618b5109f87a03defc1486b9f Conflicts: opengl/java/android/opengl/GLSurfaceView.java --- api/current.txt | 2 + core/java/android/os/Handler.java | 15 ++++-- core/java/android/os/HandlerThread.java | 53 ++++++++++++++++--- core/java/android/os/Looper.java | 35 +++++++++--- core/java/android/os/MessageQueue.java | 46 +++++++++++++++- opengl/java/android/opengl/GLSurfaceView.java | 17 +++--- 6 files changed, 140 insertions(+), 28 deletions(-) diff --git a/api/current.txt b/api/current.txt index 309a235bbbd5d..d0aad13bb1915 100644 --- a/api/current.txt +++ b/api/current.txt @@ -17062,6 +17062,7 @@ package android.os { method public int getThreadId(); method protected void onLooperPrepared(); method public boolean quit(); + method public boolean quitSafely(); } public abstract interface IBinder { @@ -17102,6 +17103,7 @@ package android.os { method public static void prepare(); method public static void prepareMainLooper(); method public void quit(); + method public void quitSafely(); method public void setMessageLogging(android.util.Printer); } diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 94de4487b645c..14d8f0765f9d8 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -413,27 +413,32 @@ public class Handler { /** * Runs the specified task synchronously. - * + *

* If the current thread is the same as the handler thread, then the runnable * runs immediately without being enqueued. Otherwise, posts the runnable * to the handler and waits for it to complete before returning. - * + *

* This method is dangerous! Improper use can result in deadlocks. * Never call this method while any locks are held or use it in a * possibly re-entrant manner. - * + *

* This method is occasionally useful in situations where a background thread * must synchronously await completion of a task that must run on the * handler's thread. However, this problem is often a symptom of bad design. * Consider improving the design (if possible) before resorting to this method. - * + *

* One example of where you might want to use this method is when you just * set up a Handler thread and need to perform some initialization steps on * it before continuing execution. - * + *

* If timeout occurs then this method returns false but the runnable * will remain posted on the handler and may already be in progress or * complete at a later time. + *

+ * When using this method, be sure to use {@link Looper#quitSafely} when + * quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely. + * (TODO: We should fix this by making MessageQueue aware of blocking runnables.) + *

* * @param r The Runnable that will be executed synchronously. * @param timeout The timeout in milliseconds, or 0 to wait indefinitely. diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java index daf1f59335205..2904105ef7827 100644 --- a/core/java/android/os/HandlerThread.java +++ b/core/java/android/os/HandlerThread.java @@ -48,6 +48,7 @@ public class HandlerThread extends Thread { protected void onLooperPrepared() { } + @Override public void run() { mTid = Process.myTid(); Looper.prepare(); @@ -83,12 +84,25 @@ public class HandlerThread extends Thread { } return mLooper; } - + /** - * Ask the currently running looper to quit. If the thread has not - * been started or has finished (that is if {@link #getLooper} returns - * null), then false is returned. Otherwise the looper is asked to - * quit and true is returned. + * Quits the handler thread's looper. + *

+ * Causes the handler thread's looper to terminate without processing any + * more messages in the message queue. + *

+ * Any attempt to post messages to the queue after the looper is asked to quit will fail. + * For example, the {@link Handler#sendMessage(Message)} method will return false. + *

+ * Using this method may be unsafe because some messages may not be delivered + * before the looper terminates. Consider using {@link #quitSafely} instead to ensure + * that all pending work is completed in an orderly manner. + *

+ * + * @return True if the looper looper has been asked to quit or false if the + * thread had not yet started running. + * + * @see #quitSafely */ public boolean quit() { Looper looper = getLooper(); @@ -98,7 +112,34 @@ public class HandlerThread extends Thread { } return false; } - + + /** + * Quits the handler thread's looper safely. + *

+ * Causes the handler thread's looper to terminate as soon as all remaining messages + * in the message queue that are already due to be delivered have been handled. + * Pending delayed messages with due times in the future will not be delivered. + *

+ * Any attempt to post messages to the queue after the looper is asked to quit will fail. + * For example, the {@link Handler#sendMessage(Message)} method will return false. + *

+ * If the thread has not been started or has finished (that is if + * {@link #getLooper} returns null), then false is returned. + * Otherwise the looper is asked to quit and true is returned. + *

+ * + * @return True if the looper looper has been asked to quit or false if the + * thread had not yet started running. + */ + public boolean quitSafely() { + Looper looper = getLooper(); + if (looper != null) { + looper.quitSafely(); + return true; + } + return false; + } + /** * Returns the identifier of this thread. See Process.myTid(). */ diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index fa28765206f0b..d5cf771a22bc1 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -202,18 +202,37 @@ public final class Looper { /** * Quits the looper. *

- * Causes the {@link #loop} method to terminate as soon as all remaining messages - * in the message queue that are already due to be delivered have been handled. - * However delayed messages with due times in the future may not be handled before - * the loop terminates. + * Causes the {@link #loop} method to terminate without processing any + * more messages in the message queue. *

- * Any attempt to post messages to the queue after {@link #quit} has been called - * will fail. For example, the {@link Handler#sendMessage(Message)} method will - * return false when the looper is being terminated. + * Any attempt to post messages to the queue after the looper is asked to quit will fail. + * For example, the {@link Handler#sendMessage(Message)} method will return false. + *

+ * Using this method may be unsafe because some messages may not be delivered + * before the looper terminates. Consider using {@link #quitSafely} instead to ensure + * that all pending work is completed in an orderly manner. *

+ * + * @see #quitSafely */ public void quit() { - mQueue.quit(); + mQueue.quit(false); + } + + /** + * Quits the looper safely. + *

+ * Causes the {@link #loop} method to terminate as soon as all remaining messages + * in the message queue that are already due to be delivered have been handled. + * However pending delayed messages with due times in the future will not be + * delivered before the loop terminates. + *

+ * Any attempt to post messages to the queue after the looper is asked to quit will fail. + * For example, the {@link Handler#sendMessage(Message)} method will return false. + *

+ */ + public void quitSafely() { + mQueue.quit(true); } /** diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index c058bfce28e65..bf7e5caf7024c 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -219,7 +219,7 @@ public final class MessageQueue { } } - void quit() { + void quit(boolean safe) { if (!mQuitAllowed) { throw new RuntimeException("Main thread not allowed to quit."); } @@ -229,6 +229,12 @@ public final class MessageQueue { return; } mQuiting = true; + + if (safe) { + removeAllFutureMessagesLocked(); + } else { + removeAllMessagesLocked(); + } } nativeWake(mPtr); } @@ -473,4 +479,42 @@ public final class MessageQueue { } } } + + private void removeAllMessagesLocked() { + Message p = mMessages; + while (p != null) { + Message n = p.next; + p.recycle(); + p = n; + } + mMessages = null; + } + + private void removeAllFutureMessagesLocked() { + final long now = SystemClock.uptimeMillis(); + Message p = mMessages; + if (p != null) { + if (p.when > now) { + removeAllMessagesLocked(); + } else { + Message n; + for (;;) { + n = p.next; + if (n == null) { + return; + } + if (n.when > now) { + break; + } + p = n; + } + p.next = null; + do { + p = n; + n = p.next; + p.recycle(); + } while (n != null); + } + } + } } diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index ab7ceb61dbce2..22f010689aa4f 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -246,7 +246,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback if (mGLThread != null) { // GLThread may still be running if this view was never // attached to a window. - mGLThread.requestExitAndWait(); + mGLThread.quitSafely(); } } finally { super.finalize(); @@ -627,7 +627,11 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback Log.d(TAG, "onDetachedFromWindow"); } if (mGLThread != null) { - mGLThread.requestExitAndWait(); + mGLThread.quitSafely(); + try { + mGLThread.join(); + } catch (InterruptedException ex) { + } } mDetached = true; super.onDetachedFromWindow(); @@ -1607,12 +1611,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback }, 0); } - public void requestExitAndWait() { - getLooper().quit(); - try { - this.join(); - } catch (InterruptedException e) { - } + public int getRenderMode() { + mGLHandler.runWithScissors(mGetRenderModeRunnable, 0); + return mGetRenderModeRunnable.renderMode; } } // class GLThread From 989a40a304c137e7fc37cb0ca53ed3df5d72be17 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 18 Apr 2013 13:52:58 -0700 Subject: [PATCH 63/76] don't trigger a render when setting the rendermode also don't cache the rendermode, to mimic the older behaviour. Bug: 8656076 Change-Id: Id9383852bed073927db2364f7ac30a1be28b4cd8 Conflicts: opengl/java/android/opengl/GLSurfaceView.java --- opengl/java/android/opengl/GLSurfaceView.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 22f010689aa4f..dac3506e09232 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -184,7 +184,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private int mDebugFlags; private int mEGLContextClientVersion; private boolean mPreserveEGLContextOnPause; - private int mUserRenderMode; /** * The renderer only renders @@ -258,7 +257,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback // underlying surface is created and destroyed SurfaceHolder holder = getHolder(); holder.addCallback(this); - mUserRenderMode = RENDERMODE_CONTINUOUSLY; } /** @@ -516,7 +514,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback default: throw new IllegalArgumentException("renderMode"); } - mUserRenderMode = renderMode; mGLThread.setRenderMode(renderMode); } @@ -528,7 +525,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * @see #RENDERMODE_WHEN_DIRTY */ public int getRenderMode() { - return mUserRenderMode; + return mGLThread.getRenderMode(); } /** @@ -608,9 +605,13 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback if (LOG_ATTACH_DETACH) { Log.d(TAG, "onAttachedToWindow reattach =" + mDetached); } + + final int renderMode = mGLThread != null ? + mGLThread.mRenderMode : RENDERMODE_CONTINUOUSLY; + if (mDetached && (mRenderer != null)) { mGLThread = new GLThread(mThisWeakRef); - mGLThread.setRenderMode(mUserRenderMode); + mGLThread.setRenderMode(renderMode); mGLThread.start(); } mDetached = false; @@ -1265,6 +1266,17 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private Handler mGLHandler; private Choreographer mChoreographer; + // this runnable could be called often, so we keep an instance around + class GetRenderModeRunnable implements Runnable { + volatile public int renderMode; + @Override + public void run() { + renderMode = doGetRenderMode(); + } + }; + + private final GetRenderModeRunnable mGetRenderModeRunnable = new GetRenderModeRunnable(); + /* * Set once at thread construction time, nulled out when the parent view is garbage * called. This weak reference allows the GLSurfaceView to be garbage collected while @@ -1515,7 +1527,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } - private void doWindowResize(final int width, final int height) { + private void doWindowResize(int width, int height) { // we were not drawing yet. Update the window size and // state and attempt to draw a frame. mSizeChanged = (mWidth != width || mHeight != height); @@ -1526,9 +1538,13 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback executeDraw(); } - private void doSetRenderMode(final int renderMode) { + private void doSetRenderMode(int renderMode) { mRenderMode = renderMode; - requestRender(); + } + + + private int doGetRenderMode() { + return mRenderMode; } /* From 428b063a2e1f5c9bb8caf560e770ea5f9d5fde02 Mon Sep 17 00:00:00 2001 From: Irfan Sheriff Date: Thu, 25 Apr 2013 23:22:41 -0700 Subject: [PATCH 64/76] Fix network reload when config is restored With scan mode opted in, supplicant connection is not shut down even when wifi is turned off. This is a problem since networks need to be reloaded when wifi is turned off and turned on and this currently happens only on a supplicant connection. Fix to handle this for scan mode state. Bug: 8714796 Change-Id: I7d66c39fb51018fb52e783341222cea993993893 --- wifi/java/android/net/wifi/WifiConfigStore.java | 2 +- wifi/java/android/net/wifi/WifiStateMachine.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index 23a4e716a4b61..9418de1f7c6a8 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -156,7 +156,7 @@ class WifiConfigStore { * Fetch the list of configured networks * and enable all stored networks in supplicant. */ - void initialize() { + void loadAndEnableAllNetworks() { if (DBG) log("Loading config and enabling all networks"); loadConfiguredNetworks(); enableAllNetworks(); diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 2c3df9516a179..9cae2cb9e32f8 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -1111,6 +1111,7 @@ public class WifiStateMachine extends StateMachine { pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt); pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled); pw.println("Supplicant status " + mWifiNative.status()); + pw.println("mEnableBackgroundScan " + mEnableBackgroundScan); pw.println(); mWifiConfigStore.dump(fd, pw, args); } @@ -2121,7 +2122,7 @@ public class WifiStateMachine extends StateMachine { mLastSignalLevel = -1; mWifiInfo.setMacAddress(mWifiNative.getMacAddress()); - mWifiConfigStore.initialize(); + mWifiConfigStore.loadAndEnableAllNetworks(); initializeWpsDetails(); sendSupplicantConnectionChangedBroadcast(true); @@ -2657,9 +2658,13 @@ public class WifiStateMachine extends StateMachine { public void exit() { if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) { setWifiState(WIFI_STATE_ENABLED); + // Load and re-enable networks when going back to enabled state + // This is essential for networks to show up after restore + mWifiConfigStore.loadAndEnableAllNetworks(); mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P); + } else { + mWifiConfigStore.enableAllNetworks(); } - mWifiConfigStore.enableAllNetworks(); mWifiNative.reconnect(); } @Override From b5a75b20aa276932dc2cb5131e7d7720e1e41822 Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 8 May 2013 15:57:06 -0400 Subject: [PATCH 65/76] Exempt the notification panel from animation lockout. We do a lot of launch-app-then-collapse, and it's a game to see whether the app's window activity animation starts before the panel has a chance to finish collapsing. The winning move here is not to play. Requires change I2773601d in f/b. Bug: 8666124 Change-Id: I3e3f1c5a4a505ad7d487c804139445ffd499d8d4 --- .../systemui/statusbar/phone/StatusBarWindowView.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index f526f0c6f988e..5620e1b35ab4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -24,6 +24,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewRootImpl; import android.widget.FrameLayout; import android.widget.ScrollView; import android.widget.TextSwitcher; @@ -63,6 +64,13 @@ public class StatusBarWindowView extends FrameLayout mExpandHelper = new ExpandHelper(mContext, latestItems, minHeight, maxHeight); mExpandHelper.setEventSource(this); mExpandHelper.setScrollView(mScrollView); + + // We really need to be able to animate while window animations are going on + // so that activities may be started asynchronously from panel animations + final ViewRootImpl root = getViewRootImpl(); + if (root != null) { + root.setDrawDuringWindowsAnimating(true); + } } @Override From e1c0cbcd55ffffac4b5be78d8ed8d25315353af1 Mon Sep 17 00:00:00 2001 From: Matthew Xie Date: Wed, 8 May 2013 19:26:57 -0700 Subject: [PATCH 66/76] Donot bind to GATT service when BLE is not supported bug 8664724 Change-Id: I9b9222cd5877babcded73798a5d1ff13fd10e791 --- core/java/android/bluetooth/BluetoothAdapter.java | 6 +++++- core/java/android/bluetooth/BluetoothDevice.java | 4 ++++ .../com/android/server/BluetoothManagerService.java | 12 ++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index cfbfb48d4a1fe..7ec73ef741357 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1472,9 +1472,13 @@ public final class BluetoothAdapter { try { IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); + if (iGatt == null) { + // BLE is not supported + return false; + } + UUID uuid = UUID.randomUUID(); GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids); - iGatt.registerClient(new ParcelUuid(uuid), wrapper); if (wrapper.scanStarted()) { mLeScanClients.put(callback, wrapper); diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 3c1ec90f19c3a..79a5ffea46ab2 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1187,6 +1187,10 @@ public final class BluetoothDevice implements Parcelable { IBluetoothManager managerService = adapter.getBluetoothManager(); try { IBluetoothGatt iGatt = managerService.getBluetoothGatt(); + if (iGatt == null) { + // BLE is not supported + return null; + } BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this); gatt.connect(autoConnect, callback); return gatt; diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java index ea7b696f68b51..f7a7fdf5a41a6 100644 --- a/services/java/com/android/server/BluetoothManagerService.java +++ b/services/java/com/android/server/BluetoothManagerService.java @@ -31,6 +31,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -1092,10 +1093,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (isUp) { // connect to GattService - Intent i = new Intent(IBluetoothGatt.class.getName()); - if (!mContext.bindServiceAsUser(i, mConnection, Context.BIND_AUTO_CREATE, - UserHandle.CURRENT)) { - Log.e(TAG, "Fail to bind to: " + IBluetoothGatt.class.getName()); + if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_BLUETOOTH_LE)) { + Intent i = new Intent(IBluetoothGatt.class.getName()); + if (!mContext.bindServiceAsUser(i, mConnection, Context.BIND_AUTO_CREATE, + UserHandle.CURRENT)) { + Log.e(TAG, "Fail to bind to: " + IBluetoothGatt.class.getName()); + } } } else { //If Bluetooth is off, send service down event to proxy objects, and unbind From d8bc100ae11a307a4a8c519b5f5c7fe6473a8837 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 9 May 2013 13:41:11 -0700 Subject: [PATCH 67/76] Fix bug managing wifi display routes. We could sometimes crash due to some inconsistencies in the way the wifi display routes were updates when connecting, disconnecting or scanning wifi displays. Bug: 8837094 Change-Id: I10c7ccb163ec33c4ea107dfcb5074741049fe955 --- media/java/android/media/MediaRouter.java | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 61c55a58e5efe..9e8aeeb8a6e81 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -783,25 +783,21 @@ public class MediaRouter { for (int i = 0; i < newDisplays.length; i++) { final WifiDisplay d = newDisplays[i]; - final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays); - if (oldRemembered == null) { - addRouteStatic(makeWifiDisplayRoute(d, - findMatchingDisplay(d, availableDisplays) != null)); + final boolean available = findMatchingDisplay(d, availableDisplays) != null; + RouteInfo route = findWifiDisplayRoute(d); + if (route == null) { + route = makeWifiDisplayRoute(d, available); + addRouteStatic(route); wantScan = true; } else { - final boolean available = findMatchingDisplay(d, availableDisplays) != null; - final RouteInfo route = findWifiDisplayRoute(d); updateWifiDisplayRoute(route, d, available, newStatus); } if (d.equals(activeDisplay)) { - final RouteInfo activeRoute = findWifiDisplayRoute(d); - if (activeRoute != null) { - selectRouteStatic(activeRoute.getSupportedTypes(), activeRoute); + selectRouteStatic(route.getSupportedTypes(), route); - // Don't scan if we're already connected to a wifi display, - // the scanning process can cause a hiccup with some configurations. - blockScan = true; - } + // Don't scan if we're already connected to a wifi display, + // the scanning process can cause a hiccup with some configurations. + blockScan = true; } } for (int i = 0; i < oldDisplays.length; i++) { From ae01017f4d94c8fe0b96f7504cc6ef6384e0e5c8 Mon Sep 17 00:00:00 2001 From: Robert Greenwalt Date: Thu, 16 May 2013 12:48:20 -0700 Subject: [PATCH 68/76] Add min delay between disable and enable of wifi We sometimes have issues if disable, enable seq happens to fast. This should only slow down those fast cases. bug:8715336 Change-Id: I4f7fe9708b7c3c2300c441511838e8a70eaad5d1 --- core/java/android/provider/Settings.java | 6 ++ .../android/server/wifi/WifiController.java | 86 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2725f6ae9dda1..f74238518e98f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4915,6 +4915,12 @@ public final class Settings { */ public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name"; + /** + * The min time between wifi disable and wifi enable + * @hide + */ + public static final String WIFI_REENABLE_DELAY_MS = "wifi_reenable_delay"; + /** * The number of milliseconds to delay when checking for data stalls during * non-aggressive detection. (screen is turned off.) diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java index 6e6b8ccc538b9..52aaac71cd664 100644 --- a/services/java/com/android/server/wifi/WifiController.java +++ b/services/java/com/android/server/wifi/WifiController.java @@ -34,6 +34,7 @@ import android.net.wifi.WifiStateMachine; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.os.WorkSource; import android.provider.Settings; import android.util.Slog; @@ -70,6 +71,13 @@ class WifiController extends StateMachine { */ private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */ + /** + * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}. This is the default value if a + * Settings.Global value is not present. This is the minimum time after wifi is disabled + * we'll act on an enable. Enable requests received before this delay will be deferred. + */ + private static final long DEFAULT_REENABLE_DELAY_MS = 500; + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); private static final String ACTION_DEVICE_IDLE = @@ -86,6 +94,8 @@ class WifiController extends StateMachine { */ private final WorkSource mTmpWorkSource = new WorkSource(); + private long mReEnableDelayMillis; + private static final int BASE = Protocol.BASE_WIFI_CONTROLLER; static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1; @@ -98,6 +108,7 @@ class WifiController extends StateMachine { static final int CMD_WIFI_TOGGLED = BASE + 8; static final int CMD_AIRPLANE_TOGGLED = BASE + 9; static final int CMD_SET_AP = BASE + 10; + static final int CMD_DEFERRED_TOGGLE = BASE + 11; private DefaultState mDefaultState = new DefaultState(); private StaEnabledState mStaEnabledState = new StaEnabledState(); @@ -168,6 +179,7 @@ class WifiController extends StateMachine { registerForWifiIdleTimeChange(handler); readWifiSleepPolicy(); registerForWifiSleepPolicyChange(handler); + readWifiReEnableDelay(); } private void readStayAwakeConditions() { @@ -186,6 +198,11 @@ class WifiController extends StateMachine { Settings.Global.WIFI_SLEEP_POLICY_NEVER); } + private void readWifiReEnableDelay() { + mReEnableDelayMillis = Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS); + } + /** * Observes settings changes to scan always mode. */ @@ -335,6 +352,7 @@ class WifiController extends StateMachine { case CMD_WIFI_TOGGLED: case CMD_AIRPLANE_TOGGLED: case CMD_EMERGENCY_MODE_CHANGED: + case CMD_DEFERRED_TOGGLE: break; default: throw new RuntimeException("WifiController.handleMessage " + msg.what); @@ -345,9 +363,17 @@ class WifiController extends StateMachine { } class ApStaDisabledState extends State { + private int mDeferredEnableSerialNumber = 0; + private boolean mHaveDeferredEnable = false; + private long mDisabledTimestamp; + @Override public void enter() { mWifiStateMachine.setSupplicantRunning(false); + // Supplicant can't restart right away, so not the time we switched off + mDisabledTimestamp = SystemClock.elapsedRealtime(); + mDeferredEnableSerialNumber++; + mHaveDeferredEnable = false; } @Override public boolean processMessage(Message msg) { @@ -355,6 +381,14 @@ class WifiController extends StateMachine { case CMD_WIFI_TOGGLED: case CMD_AIRPLANE_TOGGLED: if (mSettingsStore.isWifiToggleEnabled()) { + if (doDeferEnable(msg)) { + if (mHaveDeferredEnable) { + // have 2 toggles now, inc serial number an ignore both + mDeferredEnableSerialNumber++; + } + mHaveDeferredEnable = !mHaveDeferredEnable; + break; + } if (mDeviceIdle == false) { transitionTo(mDeviceActiveState); } else { @@ -374,12 +408,30 @@ class WifiController extends StateMachine { transitionTo(mApEnabledState); } break; + case CMD_DEFERRED_TOGGLE: + if (msg.arg1 != mDeferredEnableSerialNumber) break; + sendMessage((Message)(msg.obj)); + break; default: return NOT_HANDLED; } return HANDLED; } + private boolean doDeferEnable(Message msg) { + long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp; + if (delaySoFar > mReEnableDelayMillis) { + return false; + } + + // need to defer this action. + Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE); + deferredMsg.obj = Message.obtain(msg); + deferredMsg.arg1 = ++mDeferredEnableSerialNumber; + sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar); + return true; + } + } class StaEnabledState extends State { @@ -421,11 +473,19 @@ class WifiController extends StateMachine { } class StaDisabledWithScanState extends State { + private int mDeferredEnableSerialNumber = 0; + private boolean mHaveDeferredEnable = false; + private long mDisabledTimestamp; + @Override public void enter() { mWifiStateMachine.setSupplicantRunning(true); mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE); mWifiStateMachine.setDriverStart(true); + // Supplicant can't restart right away, so not the time we switched off + mDisabledTimestamp = SystemClock.elapsedRealtime(); + mDeferredEnableSerialNumber++; + mHaveDeferredEnable = false; } @Override @@ -433,6 +493,14 @@ class WifiController extends StateMachine { switch (msg.what) { case CMD_WIFI_TOGGLED: if (mSettingsStore.isWifiToggleEnabled()) { + if (doDeferEnable(msg)) { + if (mHaveDeferredEnable) { + // have 2 toggles now, inc serial number and ignore both + mDeferredEnableSerialNumber++; + } + mHaveDeferredEnable = !mHaveDeferredEnable; + break; + } if (mDeviceIdle == false) { transitionTo(mDeviceActiveState); } else { @@ -457,11 +525,29 @@ class WifiController extends StateMachine { transitionTo(mApStaDisabledState); } break; + case CMD_DEFERRED_TOGGLE: + if (msg.arg1 != mDeferredEnableSerialNumber) break; + sendMessage((Message)(msg.obj)); + break; default: return NOT_HANDLED; } return HANDLED; } + + private boolean doDeferEnable(Message msg) { + long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp; + if (delaySoFar > mReEnableDelayMillis) { + return false; + } + // need to defer this action. + Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE); + deferredMsg.obj = Message.obtain(msg); + deferredMsg.arg1 = ++mDeferredEnableSerialNumber; + sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar); + return true; + } + } class ApEnabledState extends State { From cff74a08fb31184b385fd42d4a117a07cda60ba2 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Fri, 17 May 2013 12:07:21 -0700 Subject: [PATCH 69/76] Fix issue #9000597: PackageManager get NPE while running performance plan Change-Id: I2bf777e8b15704d21320f95059d94648122396a8 --- .../java/com/android/server/pm/PackageManagerService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 1a0455c7fb275..22ce2848884bf 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -8120,7 +8120,7 @@ public class PackageManagerService extends IPackageManager.Stub { allUsers = sUserManager.getUserIds(); perUserInstalled = new boolean[allUsers.length]; for (int i = 0; i < allUsers.length; i++) { - perUserInstalled[i] = ps.getInstalled(allUsers[i]); + perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false; } } boolean sysPkg = (isSystemApp(oldPackage)); @@ -8689,7 +8689,7 @@ public class PackageManagerService extends IPackageManager.Stub { allUsers = sUserManager.getUserIds(); perUserInstalled = new boolean[allUsers.length]; for (int i = 0; i < allUsers.length; i++) { - perUserInstalled[i] = ps.getInstalled(allUsers[i]); + perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false; } } From c9699994aa11b34b55b6321f19e8421fbcc84919 Mon Sep 17 00:00:00 2001 From: Robert Greenwalt Date: Mon, 20 May 2013 09:28:08 -0700 Subject: [PATCH 70/76] Fix off-by-1 error in timing If deferred action msg was delivered promptly it would end up looking like it too needed to be deferred and then nothing would end up getting done. bug:9014777 Change-Id: I243df70463ebb9333efd6df510f71f9d8d9bdf91 --- services/java/com/android/server/wifi/WifiController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java index 10f7f5a9dcde2..8e2bdaad41c4a 100644 --- a/services/java/com/android/server/wifi/WifiController.java +++ b/services/java/com/android/server/wifi/WifiController.java @@ -426,7 +426,7 @@ class WifiController extends StateMachine { private boolean doDeferEnable(Message msg) { long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp; - if (delaySoFar > mReEnableDelayMillis) { + if (delaySoFar >= mReEnableDelayMillis) { return false; } @@ -550,7 +550,7 @@ class WifiController extends StateMachine { private boolean doDeferEnable(Message msg) { long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp; - if (delaySoFar > mReEnableDelayMillis) { + if (delaySoFar >= mReEnableDelayMillis) { return false; } From 1b64daf1acc8ba2967f280d2d7980544b80851d6 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Fri, 17 May 2013 18:06:31 -0700 Subject: [PATCH 71/76] Taking into account data change for AbsListView when prefetching node infos. We are prefetching accessibility node infos to minimize the number of IPC calls when an accessibility service introspects the screen. It is however, possible that the view we are prefetching is a child of an AbsListView whose adapter changed its data but the AbsListView still did not perform a layout pass to sync its children with the new adapter state. This may lead to an exeption when trying to query for the state of a child's position. If the data of the adapter is changed and the layout pass still not performed, we return null for the AbsLIstView's children. When the layout pass completes we already notify the accessibliity layer so it will be able to refetch the children of the AbsListView. bug:8433433 Change-Id: I56313c721aef3848b15fad50027d068ba1d291f7 --- core/java/android/view/View.java | 38 +++++++++++++++++++++++ core/java/android/widget/AbsListView.java | 12 +++++++ 2 files changed, 50 insertions(+) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0cd7c6e1c6683..4e8005fafaa12 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4928,6 +4928,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see AccessibilityNodeInfo */ public AccessibilityNodeInfo createAccessibilityNodeInfo() { + if (mAccessibilityDelegate != null) { + return mAccessibilityDelegate.createAccessibilityNodeInfo(this); + } else { + return createAccessibilityNodeInfoInternal(); + } + } + + /** + * @see #createAccessibilityNodeInfo() + */ + AccessibilityNodeInfo createAccessibilityNodeInfoInternal() { AccessibilityNodeProvider provider = getAccessibilityNodeProvider(); if (provider != null) { return provider.createAccessibilityNodeInfo(View.NO_ID); @@ -18690,6 +18701,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) { return null; } + + /** + * Returns an {@link AccessibilityNodeInfo} representing the host view from the + * point of view of an {@link android.accessibilityservice.AccessibilityService}. + * This method is responsible for obtaining an accessibility node info from a + * pool of reusable instances and calling + * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on the host + * view to initialize the former. + *

+ * Note: The client is responsible for recycling the obtained + * instance by calling {@link AccessibilityNodeInfo#recycle()} to minimize object + * creation. + *

+ *

+ * The default implementation behaves as + * {@link View#createAccessibilityNodeInfo() View#createAccessibilityNodeInfo()} for + * the case of no accessibility delegate been set. + *

+ * @return A populated {@link AccessibilityNodeInfo}. + * + * @see AccessibilityNodeInfo + * + * @hide + */ + public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) { + return host.createAccessibilityNodeInfoInternal(); + } } private class MatchIdPredicate implements Predicate { diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 5a40368d59353..bf662924b7f31 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2212,6 +2212,18 @@ public abstract class AbsListView extends AdapterView implements Te } class ListItemAccessibilityDelegate extends AccessibilityDelegate { + @Override + public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) { + // If the data changed the children are invalid since the data model changed. + // Hence, we pretend they do not exist. After a layout the children will sync + // with the model at which point we notify that the accessibility state changed, + // so a service will be able to re-fetch the views. + if (mDataChanged) { + return null; + } + return super.createAccessibilityNodeInfo(host); + } + @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); From b11ef589c1d250982db8e6fd6d067bee8c5c88ee Mon Sep 17 00:00:00 2001 From: Robert Greenwalt Date: Mon, 20 May 2013 11:49:46 -0700 Subject: [PATCH 72/76] Add a time margin for deferred enable. We were getting delayed message delivered 1 ms before we expected. bug:9014777 Change-Id: I70e681ff6b4155d31097b43529478b39c816e70b --- services/java/com/android/server/wifi/WifiController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java index 8e2bdaad41c4a..228fabf67a046 100644 --- a/services/java/com/android/server/wifi/WifiController.java +++ b/services/java/com/android/server/wifi/WifiController.java @@ -78,6 +78,10 @@ class WifiController extends StateMachine { */ private static final long DEFAULT_REENABLE_DELAY_MS = 500; + // finding that delayed messages can sometimes be delivered earlier than expected + // probably rounding errors.. add a margin to prevent problems + private static final long DEFER_MARGIN_MS = 5; + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); private static final String ACTION_DEVICE_IDLE = @@ -437,7 +441,7 @@ class WifiController extends StateMachine { Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE); deferredMsg.obj = Message.obtain(msg); deferredMsg.arg1 = ++mDeferredEnableSerialNumber; - sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar); + sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS); return true; } @@ -561,7 +565,7 @@ class WifiController extends StateMachine { Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE); deferredMsg.obj = Message.obtain(msg); deferredMsg.arg1 = ++mDeferredEnableSerialNumber; - sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar); + sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS); return true; } From 962dac14425317f6232bf2beddc6e453efb59a02 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Fri, 17 May 2013 19:12:38 -0700 Subject: [PATCH 73/76] Do not report the fake UI automation service to clients. For UI test automation purposes we register a fake accessibility service and suspend all other services. When the UI automation serivce is unregistered we restore the suspended ones. Since the UI automation serivce is fake and incomplete, for example it has not resolve info, it should not be reported to clients as being installed or enabled. bug:8871034 Change-Id: I66792cd028159c1652d3c8a2982164821282ab24 --- .../AccessibilityManagerService.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 2b5544ba0be96..2f8250fbf1a23 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -95,6 +95,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -405,7 +406,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { synchronized (mLock) { final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); - return getUserStateLocked(resolvedUserId).mInstalledServices; + // The automation service is a fake one and should not be reported + // to clients as being installed - it really is not. + UserState userState = getUserStateLocked(resolvedUserId); + if (userState.mUiAutomationService != null) { + List installedServices = + new ArrayList(); + installedServices.addAll(userState.mInstalledServices); + installedServices.remove(userState.mUiAutomationService); + return installedServices; + } + return userState.mInstalledServices; } } @@ -415,9 +426,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { synchronized (mLock) { final int resolvedUserId = mSecurityPolicy .resolveCallingUserIdEnforcingPermissionsLocked(userId); + + // The automation service is a fake one and should not be reported + // to clients as being enabled. The automation service is always the + // only active one, if it exists. + UserState userState = getUserStateLocked(resolvedUserId); + if (userState.mUiAutomationService != null) { + return Collections.emptyList(); + } + result = mEnabledServicesForFeedbackTempList; result.clear(); - List services = getUserStateLocked(resolvedUserId).mBoundServices; + List services = userState.mBoundServices; while (feedbackType != 0) { final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType)); feedbackType &= ~feedbackTypeBit; From f4eef69721570d2f1eddaaa799f10c7785aafad3 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Tue, 21 May 2013 20:37:51 -0700 Subject: [PATCH 74/76] Add a queue of pending finished input events. Although in practice an application or IME is unlikely to fill up the input channel with finish events, it can happen when events are being delivered very rapidly. Handle this situation by queuing up the pending finish events until the socket becomes writable again. Bug: 9034301 Change-Id: I938a62a75d12106a19cff2d016ba7af0db877ecf --- core/jni/android_view_InputEventReceiver.cpp | 93 +++++++++++++++++--- 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index c350521a01164..8e1e04abeca1f 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include "android_os_MessageQueue.h" @@ -61,11 +62,20 @@ protected: virtual ~NativeInputEventReceiver(); private: + struct Finish { + uint32_t seq; + bool handled; + }; + jobject mReceiverWeakGlobal; InputConsumer mInputConsumer; sp mMessageQueue; PreallocatedInputEventFactory mInputEventFactory; bool mBatchedInputEventPending; + int mFdEvents; + Vector mFinishQueue; + + void setFdEvents(int events); const char* getInputChannelName() { return mInputConsumer.getChannel()->getName().string(); @@ -80,7 +90,7 @@ NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, const sp& messageQueue) : mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), mInputConsumer(inputChannel), mMessageQueue(messageQueue), - mBatchedInputEventPending(false) { + mBatchedInputEventPending(false), mFdEvents(0) { #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName()); #endif @@ -92,8 +102,7 @@ NativeInputEventReceiver::~NativeInputEventReceiver() { } status_t NativeInputEventReceiver::initialize() { - int receiveFd = mInputConsumer.getChannel()->getFd(); - mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL); + setFdEvents(ALOOPER_EVENT_INPUT); return OK; } @@ -102,7 +111,7 @@ void NativeInputEventReceiver::dispose() { ALOGD("channel '%s' ~ Disposing input event receiver.", getInputChannelName()); #endif - mMessageQueue->getLooper()->removeFd(mInputConsumer.getChannel()->getFd()); + setFdEvents(0); } status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) { @@ -112,12 +121,38 @@ status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) status_t status = mInputConsumer.sendFinishedSignal(seq, handled); if (status) { + if (status == WOULD_BLOCK) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Could not send finished signal immediately. " + "Enqueued for later.", getInputChannelName()); +#endif + Finish finish; + finish.seq = seq; + finish.handled = handled; + mFinishQueue.add(finish); + if (mFinishQueue.size() == 1) { + setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT); + } + return OK; + } ALOGW("Failed to send finished signal on channel '%s'. status=%d", getInputChannelName(), status); } return status; } +void NativeInputEventReceiver::setFdEvents(int events) { + if (mFdEvents != events) { + mFdEvents = events; + int fd = mInputConsumer.getChannel()->getFd(); + if (events) { + mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL); + } else { + mMessageQueue->getLooper()->removeFd(fd); + } + } +} + int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { #if DEBUG_DISPATCH_CYCLE @@ -130,16 +165,52 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) return 0; // remove the callback } - if (!(events & ALOOPER_EVENT_INPUT)) { - ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " - "events=0x%x", getInputChannelName(), events); + if (events & ALOOPER_EVENT_INPUT) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + status_t status = consumeEvents(env, false /*consumeBatches*/, -1); + mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); + return status == OK || status == NO_MEMORY ? 1 : 0; + } + + if (events & ALOOPER_EVENT_OUTPUT) { + for (size_t i = 0; i < mFinishQueue.size(); i++) { + const Finish& finish = mFinishQueue.itemAt(i); + status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled); + if (status) { + mFinishQueue.removeItemsAt(0, i); + + if (status == WOULD_BLOCK) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Sent %u queued finish events; %u left.", + getInputChannelName(), i, mFinishQueue.size()); +#endif + return 1; // keep the callback, try again later + } + + ALOGW("Failed to send finished signal on channel '%s'. status=%d", + getInputChannelName(), status); + if (status != DEAD_OBJECT) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + String8 message; + message.appendFormat("Failed to finish input event. status=%d", status); + jniThrowRuntimeException(env, message.string()); + mMessageQueue->raiseAndClearException(env, "finishInputEvent"); + } + return 0; // remove the callback + } + } +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Sent %u queued finish events; none left.", + getInputChannelName(), mFinishQueue.size()); +#endif + mFinishQueue.clear(); + setFdEvents(ALOOPER_EVENT_INPUT); return 1; } - JNIEnv* env = AndroidRuntime::getJNIEnv(); - status_t status = consumeEvents(env, false /*consumeBatches*/, -1); - mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); - return status == OK || status == NO_MEMORY ? 1 : 0; + ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", getInputChannelName(), events); + return 1; } status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, From 6f114b891dcf6fd6be39698b419fa66a88b19884 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Wed, 22 May 2013 19:41:45 -0700 Subject: [PATCH 75/76] Fix account filtering for specific package name Hacks to enable gms to see accounts were interfering with checking for explicit package name. Bug: 9095427 Change-Id: Iae65ea2a678d7403d023fa1a61c01347b77c3b5a --- .../server/accounts/AccountManagerService.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index 3b6393722e44a..0fbde37a20c51 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -2802,12 +2802,19 @@ public class AccountManagerService if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered; String requiredAccountType = ""; try { - for (String packageName : packages) { - PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0); + // If there's an explicit callingPackage specified, check if that package + // opted in to see restricted accounts. + if (callingPackage != null) { + PackageInfo pi = mPackageManager.getPackageInfo(callingPackage, 0); if (pi != null && pi.restrictedAccountType != null) { requiredAccountType = pi.restrictedAccountType; - // If it matches the package name of the original caller, use this choice. - if (callingPackage != null && packageName.equals(callingPackage)) { + } + } else { + // Otherwise check if the callingUid has a package that has opted in + for (String packageName : packages) { + PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0); + if (pi != null && pi.restrictedAccountType != null) { + requiredAccountType = pi.restrictedAccountType; break; } } From 8c1cb60b7879a4807f868fcd4baf2757dceec7ac Mon Sep 17 00:00:00 2001 From: Robert Greenwalt Date: Thu, 23 May 2013 18:33:06 -0700 Subject: [PATCH 76/76] Use the old interface when resetting connections The new one is often null when disconnected, so using the new fails. In other situations, it's the connections on the old network we want to reset anyway, so the old code when it would work would also do the wrong thing (unless new iface == old iface). bug:9112928 Change-Id: I1fcae89cc3aa9d712e516e7c97cece0b89869bd9 --- services/java/com/android/server/ConnectivityService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 3e19094bba71d..37a8cb85c05c2 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -2261,9 +2261,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault()); if (resetMask != 0 || resetDns) { - LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties(); - if (linkProperties != null) { - for (String iface : linkProperties.getAllInterfaceNames()) { + if (curLp != null) { + for (String iface : curLp.getAllInterfaceNames()) { if (TextUtils.isEmpty(iface) == false) { if (resetMask != 0) { if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")"); @@ -2285,6 +2284,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (DBG) loge("Exception resetting dns cache: " + e); } } + } else { + loge("Can't reset connection for type "+netType); } } }