diff options
12 files changed, 583 insertions, 259 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 9f24de8dfe65..beaf3cf939b6 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -101,11 +101,13 @@ import android.view.WindowManagerGlobal; import android.renderscript.RenderScriptCacheDir; import android.security.keystore.AndroidKeyStoreProvider; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; +import com.android.internal.os.SomeArgs; import com.android.internal.util.FastPrintWriter; import com.android.org.conscrypt.OpenSSLSocketImpl; import com.android.org.conscrypt.TrustedCertificateStore; @@ -163,6 +165,7 @@ public final class ActivityThread { private static final boolean DEBUG_SERVICE = false; private static final boolean DEBUG_MEMORY_TRIM = false; private static final boolean DEBUG_PROVIDER = false; + private static final boolean DEBUG_ORDER = false; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; private static final int LOG_AM_ON_PAUSE_CALLED = 30021; @@ -175,6 +178,10 @@ public final class ActivityThread { /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */ public static final int SERVICE_DONE_EXECUTING_STOP = 2; + // Details for pausing activity. + private static final int USER_LEAVING = 1; + private static final int DONT_REPORT = 2; + private ContextImpl mSystemContext; static IPackageManager sPackageManager; @@ -231,6 +238,12 @@ public final class ActivityThread { final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<ActivityClientRecord>(); Configuration mPendingConfiguration = null; + // Because we merge activity relaunch operations we can't depend on the ordering provided by + // the handler messages. We need to introduce secondary ordering mechanism, which will allow + // us to drop certain events, if we know that they happened before relaunch we already executed. + // This represents the order of receiving the request from AM. + @GuardedBy("mResourcesManager") + int mLifecycleSeq = 0; private final ResourcesManager mResourcesManager; @@ -319,6 +332,14 @@ public final class ActivityThread { WindowManager mPendingRemoveWindowManager; boolean mPreserveWindow; + // Set for relaunch requests, indicates the order number of the relaunch operation, so it + // can be compared with other lifecycle operations. + int relaunchSeq = 0; + + // Can only be accessed from the UI thread. This represents the latest processed message + // that is related to lifecycle events/ + int lastProcessedSeq = 0; + ActivityClientRecord() { parent = null; embeddedID = null; @@ -592,18 +613,25 @@ public final class ActivityThread { public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, boolean dontReport) { + int seq = getLifecycleSeq(); + if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this + + " operation received seq: " + seq); sendMessage( finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY, token, - (userLeaving ? 1 : 0) | (dontReport ? 2 : 0), - configChanges); + (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0), + configChanges, + seq); } public final void scheduleStopActivity(IBinder token, boolean showWindow, int configChanges) { - sendMessage( + int seq = getLifecycleSeq(); + if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this + + " operation received seq: " + seq); + sendMessage( showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE, - token, 0, configChanges); + token, 0, configChanges, seq); } public final void scheduleWindowVisibility(IBinder token, boolean showWindow) { @@ -618,8 +646,11 @@ public final class ActivityThread { public final void scheduleResumeActivity(IBinder token, int processState, boolean isForward, Bundle resumeArgs) { + int seq = getLifecycleSeq(); + if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this + + " operation received seq: " + seq); updateProcessState(processState, false); - sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0); + sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq); } public final void scheduleSendResult(IBinder token, List<ResultInfo> results) { @@ -1245,6 +1276,12 @@ public final class ActivityThread { } } + private int getLifecycleSeq() { + synchronized (mResourcesManager) { + return mLifecycleSeq++; + } + } + private class H extends Handler { public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; @@ -1373,29 +1410,34 @@ public final class ActivityThread { handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; - case PAUSE_ACTIVITY: + case PAUSE_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2, - (msg.arg1&2) != 0); + SomeArgs args = (SomeArgs) msg.obj; + handlePauseActivity((IBinder) args.arg1, false, + (args.argi1 & USER_LEAVING) != 0, args.argi2, + (args.argi1 & DONT_REPORT) != 0, args.argi3); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case PAUSE_ACTIVITY_FINISHING: + } break; + case PAUSE_ACTIVITY_FINISHING: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2, - (msg.arg1&1) != 0); + SomeArgs args = (SomeArgs) msg.obj; + handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0, + args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case STOP_ACTIVITY_SHOW: + } break; + case STOP_ACTIVITY_SHOW: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - handleStopActivity((IBinder)msg.obj, true, msg.arg2); + SomeArgs args = (SomeArgs) msg.obj; + handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case STOP_ACTIVITY_HIDE: + } break; + case STOP_ACTIVITY_HIDE: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - handleStopActivity((IBinder)msg.obj, false, msg.arg2); + SomeArgs args = (SomeArgs) msg.obj; + handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; + } break; case SHOW_WINDOW: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); handleWindowVisibility((IBinder)msg.obj, true); @@ -1408,7 +1450,9 @@ public final class ActivityThread { break; case RESUME_ACTIVITY: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - handleResumeActivity((IBinder) msg.obj, true, msg.arg1 != 0, true); + SomeArgs args = (SomeArgs) msg.obj; + handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true, + args.argi3); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case SEND_RESULT: @@ -1587,6 +1631,10 @@ public final class ActivityThread { handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj); break; } + Object obj = msg.obj; + if (obj instanceof SomeArgs) { + ((SomeArgs) obj).recycle(); + } if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); } @@ -2311,6 +2359,21 @@ public final class ActivityThread { mH.sendMessage(msg); } + private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) { + if (DEBUG_MESSAGES) Slog.v( + TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 + + "seq= " + seq); + Message msg = Message.obtain(); + msg.what = what; + SomeArgs args = SomeArgs.obtain(); + args.arg1 = obj; + args.argi1 = arg1; + args.argi2 = arg2; + args.argi3 = seq; + msg.obj = args; + mH.sendMessage(msg); + } + final void scheduleContextCleanup(ContextImpl context, String who, String what) { ContextCleanupInfo cci = new ContextCleanupInfo(); @@ -2516,7 +2579,7 @@ public final class ActivityThread { reportSizeConfigurations(r); Bundle oldState = r.state; handleResumeActivity(r.token, false, r.isForward, - !r.activity.mFinished && !r.startsNotResumed); + !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq); if (!r.activity.mFinished && r.startsNotResumed) { // The activity manager actually wants this one to start out @@ -3216,14 +3279,19 @@ public final class ActivityThread { } final void handleResumeActivity(IBinder token, - boolean clearHide, boolean isForward, boolean reallyResume) { + boolean clearHide, boolean isForward, boolean reallyResume, int seq) { + ActivityClientRecord r = mActivities.get(token); + if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) { + return; + } + // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration - ActivityClientRecord r = performResumeActivity(token, clearHide); + r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; @@ -3400,8 +3468,11 @@ public final class ActivityThread { } private void handlePauseActivity(IBinder token, boolean finished, - boolean userLeaving, int configChanges, boolean dontReport) { + boolean userLeaving, int configChanges, boolean dontReport, int seq) { ActivityClientRecord r = mActivities.get(token); + if (!checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) { + return; + } if (r != null) { //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); if (userLeaving) { @@ -3639,8 +3710,11 @@ public final class ActivityThread { } } - private void handleStopActivity(IBinder token, boolean show, int configChanges) { + private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) { ActivityClientRecord r = mActivities.get(token); + if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) { + return; + } r.activity.mConfigChangeFlags |= configChanges; StopInfo info = new StopInfo(); @@ -3669,6 +3743,20 @@ public final class ActivityThread { mSomeActivitiesChanged = true; } + private static boolean checkAndUpdateLifecycleSeq(int seq, ActivityClientRecord r, + String action) { + if (r == null) { + return true; + } + if (seq < r.lastProcessedSeq) { + if (DEBUG_ORDER) Slog.d(TAG, action + " for " + r + " ignored, because seq=" + seq + + " < mCurrentLifecycleSeq=" + r.lastProcessedSeq); + return false; + } + r.lastProcessedSeq = seq; + return true; + } + final void performRestartActivity(IBinder token) { ActivityClientRecord r = mActivities.get(token); if (r.stopped) { @@ -4070,7 +4158,10 @@ public final class ActivityThread { target.overrideConfig = overrideConfig; } target.pendingConfigChanges |= configChanges; + target.relaunchSeq = getLifecycleSeq(); } + if (DEBUG_ORDER) Slog.d(TAG, "relaunchActivity " + ActivityThread.this + + " operation received seq: " + target.relaunchSeq); } private void handleRelaunchActivity(ActivityClientRecord tmp) { @@ -4115,6 +4206,12 @@ public final class ActivityThread { } } + if (tmp.lastProcessedSeq > tmp.relaunchSeq) { + Slog.wtf(TAG, "For some reason target: " + tmp + " has lower sequence: " + + tmp.relaunchSeq + " than current sequence: " + tmp.lastProcessedSeq); + } else { + tmp.lastProcessedSeq = tmp.relaunchSeq; + } if (tmp.createdConfig != null) { // If the activity manager is passing us its current config, // assume that is really what we want regardless of what we @@ -4148,6 +4245,8 @@ public final class ActivityThread { r.activity.mConfigChangeFlags |= configChanges; r.onlyLocalRequest = tmp.onlyLocalRequest; r.mPreserveWindow = tmp.mPreserveWindow; + r.lastProcessedSeq = tmp.lastProcessedSeq; + r.relaunchSeq = tmp.relaunchSeq; Intent currentIntent = r.activity.mIntent; r.activity.mChangingConfigurations = true; diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 48a12ed5a025..65981121d9d6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -16,11 +16,13 @@ package com.android.systemui.recents.model; +import android.animation.ObjectAnimator; import android.app.ActivityManager; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.ColorDrawable; import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.misc.NamedCounter; @@ -193,10 +195,37 @@ public class TaskStack { BOTTOM(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, 192, new RectF(0, 0.7f, 1, 1), new RectF(0, 0.7f, 1, 1), new RectF(0, 0, 1, 0.3f)); + // Represents the view state of this dock state + public class ViewState { + public final int dockAreaAlpha; + public final ColorDrawable dockAreaOverlay; + private ObjectAnimator dockAreaOverlayAnimator; + + private ViewState(int alpha) { + dockAreaAlpha = alpha; + dockAreaOverlay = new ColorDrawable(0xFFffffff); + dockAreaOverlay.setAlpha(0); + } + + /** + * Creates a new alpha animation. + */ + public void startAlphaAnimation(int alpha, int duration) { + if (dockAreaOverlay.getAlpha() != alpha) { + if (dockAreaOverlayAnimator != null) { + dockAreaOverlayAnimator.cancel(); + } + dockAreaOverlayAnimator = ObjectAnimator.ofInt(dockAreaOverlay, "alpha", alpha); + dockAreaOverlayAnimator.setDuration(duration); + dockAreaOverlayAnimator.start(); + } + } + } + public final int createMode; - public final int dockAreaAlpha; - private final RectF touchArea; + public final ViewState viewState; private final RectF dockArea; + private final RectF touchArea; private final RectF stackArea; /** @@ -207,9 +236,9 @@ public class TaskStack { DockState(int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea, RectF stackArea) { this.createMode = createMode; - this.dockAreaAlpha = dockAreaAlpha; - this.touchArea = touchArea; + this.viewState = new ViewState(dockAreaAlpha); this.dockArea = dockArea; + this.touchArea = touchArea; this.stackArea = stackArea; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index f12042178f13..126612074a1b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -16,19 +16,17 @@ package com.android.systemui.recents.views; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.app.ActivityManager; import android.app.ActivityOptions; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -38,7 +36,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.WindowInsets; import android.view.WindowManagerGlobal; -import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; @@ -91,8 +88,12 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV RecentsViewTouchHandler mTouchHandler; DragView mDragView; - ColorDrawable mDockRegionOverlay; - ObjectAnimator mDockRegionOverlayAnimator; + TaskStack.DockState[] mVisibleDockStates = { + TaskStack.DockState.LEFT, + TaskStack.DockState.TOP, + TaskStack.DockState.RIGHT, + TaskStack.DockState.BOTTOM, + }; Interpolator mFastOutSlowInInterpolator; @@ -118,9 +119,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); mTouchHandler = new RecentsViewTouchHandler(this); - mDockRegionOverlay = new ColorDrawable(0xFFffffff); - mDockRegionOverlay.setAlpha(0); - mDockRegionOverlay.setCallback(this); } /** Sets the callbacks */ @@ -383,14 +381,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (mDockRegionOverlay.getAlpha() > 0) { - mDockRegionOverlay.draw(canvas); + for (int i = mVisibleDockStates.length - 1; i >= 0; i--) { + Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay; + if (d.getAlpha() > 0) { + d.draw(canvas); + } } } @Override protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || who == mDockRegionOverlay; + for (int i = mVisibleDockStates.length - 1; i >= 0; i--) { + Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay; + if (d == who) { + return true; + } + } + return super.verifyDrawable(who); } /** Notifies each task view of the user interaction. */ @@ -775,11 +782,17 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV mDragView = event.dragView; addView(mDragView); - updateDockRegion(TaskStack.DockState.NONE); + updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(), + TaskStack.DockState.NONE.viewState.dockAreaAlpha); } public final void onBusEvent(DragDockStateChangedEvent event) { - updateDockRegion(event.dockState); + if (event.dockState == TaskStack.DockState.NONE) { + updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(), + TaskStack.DockState.NONE.viewState.dockAreaAlpha); + } else { + updateVisibleDockRegions(new TaskStack.DockState[] {event.dockState}, -1); + } } public final void onBusEvent(final DragEndEvent event) { @@ -790,8 +803,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Remove the drag view removeView(mDragView); mDragView = null; - mDockRegionOverlay.setAlpha(0); - invalidate(); + updateVisibleDockRegions(null, -1); // Dock the new task if we are hovering over a valid dock state if (event.dockState != TaskStack.DockState.NONE) { @@ -818,7 +830,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV .start(); // Animate the overlay alpha back to 0 - updateDockRegionAlpha(0); + updateVisibleDockRegions(null, -1); } else { event.postAnimationTrigger.decrement(); } @@ -827,24 +839,26 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** * Updates the dock region to match the specified dock state. */ - private void updateDockRegion(TaskStack.DockState dockState) { - TaskStack.DockState boundsDockState = dockState; - if (dockState == TaskStack.DockState.NONE) { - // If the dock state is null, then use the bounds of the preferred dock state for this - // orientation - boundsDockState = mTouchHandler.getPreferredDockStateForCurrentOrientation(); + private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, int overrideAlpha) { + ArraySet<TaskStack.DockState> newDockStatesSet = new ArraySet<>(); + if (newDockStates != null) { + for (TaskStack.DockState dockState : newDockStates) { + newDockStatesSet.add(dockState); + } } - mDockRegionOverlay.setBounds( - boundsDockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight())); - updateDockRegionAlpha(dockState.dockAreaAlpha); - } - - private void updateDockRegionAlpha(int alpha) { - if (mDockRegionOverlayAnimator != null) { - mDockRegionOverlayAnimator.cancel(); + for (TaskStack.DockState dockState : mVisibleDockStates) { + TaskStack.DockState.ViewState viewState = dockState.viewState; + if (newDockStates == null || !newDockStatesSet.contains(dockState)) { + // This is no longer visible, so hide it + viewState.startAlphaAnimation(0, 150); + } else { + // This state is now visible, update the bounds and show it + int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha); + viewState.dockAreaOverlay.setBounds( + dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight())); + viewState.dockAreaOverlay.setCallback(this); + viewState.startAlphaAnimation(alpha, 150); + } } - mDockRegionOverlayAnimator = ObjectAnimator.ofInt(mDockRegionOverlay, "alpha", alpha); - mDockRegionOverlayAnimator.setDuration(150); - mDockRegionOverlayAnimator.start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java index 31ac755007f7..76399f4446ae 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -19,6 +19,7 @@ package com.android.systemui.recents.views; import android.content.res.Configuration; import android.graphics.Point; import android.view.MotionEvent; +import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent; import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; @@ -32,13 +33,17 @@ import com.android.systemui.recents.model.TaskStack; * Represents the dock regions for each orientation. */ class DockRegion { - public static TaskStack.DockState[] LANDSCAPE = { + public static TaskStack.DockState[] PHONE_LANDSCAPE = { TaskStack.DockState.LEFT, TaskStack.DockState.RIGHT }; - public static TaskStack.DockState[] PORTRAIT = { + public static TaskStack.DockState[] PHONE_PORTRAIT = { // We only allow docking to the top for now TaskStack.DockState.TOP }; + public static TaskStack.DockState[] TABLET_LANDSCAPE = { + TaskStack.DockState.LEFT, TaskStack.DockState.RIGHT + }; + public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT; } /** @@ -60,12 +65,17 @@ class RecentsViewTouchHandler { mRv = rv; } - public TaskStack.DockState getPreferredDockStateForCurrentOrientation() { + /** + * Returns the preferred dock states for the current orientation. + */ + public TaskStack.DockState[] getDockStatesForCurrentOrientation() { boolean isLandscape = mRv.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + RecentsConfiguration config = RecentsConfiguration.getInstance(); TaskStack.DockState[] dockStates = isLandscape ? - DockRegion.LANDSCAPE : DockRegion.PORTRAIT; - return dockStates[0]; + (config.isLargeScreen ? DockRegion.TABLET_LANDSCAPE : DockRegion.PHONE_LANDSCAPE) : + (config.isLargeScreen ? DockRegion.TABLET_PORTRAIT : DockRegion.PHONE_PORTRAIT); + return dockStates; } /** Touch preprocessing for handling below */ @@ -125,10 +135,9 @@ class RecentsViewTouchHandler { float y = evY - mDragView.getTopLeftOffset().y; // Update the dock state - TaskStack.DockState[] dockStates = isLandscape ? - DockRegion.LANDSCAPE : DockRegion.PORTRAIT; + TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation(); TaskStack.DockState foundDockState = TaskStack.DockState.NONE; - for (int i = 0; i < dockStates.length; i++) { + for (int i = dockStates.length - 1; i >= 0; i--) { TaskStack.DockState state = dockStates[i]; if (state.touchAreaContainsPoint(width, height, evX, evY)) { foundDockState = state; diff --git a/rs/java/android/renderscript/AllocationAdapter.java b/rs/java/android/renderscript/AllocationAdapter.java index 9bfd6ec48d4e..6d7e97ebb0fe 100644 --- a/rs/java/android/renderscript/AllocationAdapter.java +++ b/rs/java/android/renderscript/AllocationAdapter.java @@ -244,23 +244,23 @@ public class AllocationAdapter extends Allocation { /** * * - * Create an arbitrary window into the base allocation + * Create an arbitrary window into the base allocation. * The type describes the shape of the window. * * Any dimensions present in the type must be equal or smaller * to the dimensions in the source allocation. A dimension * present in the allocation that is not present in the type - * will be constrained away with the selectors + * will be constrained away with the selectors. * - * If a dimension is present in the type and allcation one of - * two things will happen + * If a dimension is present in both the type and allocation, one of + * two things will happen. * - * If the type is smaller than the allocation a window will be + * If the type is smaller than the allocation, a window will be * created, the selected value in the adapter for that dimension - * will act as the base address and the type will describe the + * will act as the base address, and the type will describe the * size of the view starting at that point. * - * If the type and allocation dimension are of the same size + * If the type and allocation dimension are of the same size, * then setting the selector for the dimension will be an error. */ static public AllocationAdapter createTyped(RenderScript rs, Allocation a, Type t) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 8bec7f7cad97..e4650094be31 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -535,7 +535,8 @@ public final class ActivityStackSupervisor implements DisplayListener { mHomeStack.moveHomeStackTaskToTop(homeStackTaskType); ActivityRecord r = getHomeActivity(); - if (r != null) { + // Only resume home activity if isn't finishing. + if (r != null && !r.finishing) { mService.setFocusedActivityLocked(r, reason); return resumeTopActivitiesLocked(mHomeStack, prev, null); } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 8f24b462f607..a54a61a1c68a 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -90,6 +90,10 @@ abstract public class ManagedServices { = new ArraySet<ComponentName>(); // Just the packages from mEnabledServicesForCurrentProfiles private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>(); + // List of packages in restored setting across all mUserProfiles, for quick + // filtering upon package updates. + private ArraySet<String> mRestoredPackages = new ArraySet<>(); + // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a // user change). @@ -108,6 +112,7 @@ abstract public class ManagedServices { mRestoreReceiver = new SettingRestoredReceiver(); IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED); context.registerReceiver(mRestoreReceiver, filter); + rebuildRestoredPackages(); } class SettingRestoredReceiver extends BroadcastReceiver { @@ -166,7 +171,7 @@ abstract public class ManagedServices { // By convention, restored settings are replicated to another settings // entry, named similarly but with a disambiguation suffix. - public static final String restoredSettingName(Config config) { + public static String restoredSettingName(Config config) { return config.secureSettingName + ":restored"; } @@ -184,7 +189,8 @@ abstract public class ManagedServices { restoredSettingName(mConfig), newValue, userid); - disableNonexistentServices(userid); + updateSettingsAccordingToInstalledServices(userid); + rebuildRestoredPackages(); } } } @@ -198,9 +204,11 @@ abstract public class ManagedServices { + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames); boolean anyServicesInvolved = false; + if (pkgList != null && (pkgList.length > 0)) { for (String pkgName : pkgList) { - if (mEnabledServicesPackageNames.contains(pkgName)) { + if (mEnabledServicesPackageNames.contains(pkgName) || + mRestoredPackages.contains(pkgName)) { anyServicesInvolved = true; } } @@ -209,7 +217,8 @@ abstract public class ManagedServices { if (anyServicesInvolved) { // if we're not replacing a package, clean up orphaned bits if (!queryReplace) { - disableNonexistentServices(); + updateSettingsAccordingToInstalledServices(); + rebuildRestoredPackages(); } // make sure we're still bound to any of our services who may have just upgraded rebindServices(); @@ -218,6 +227,7 @@ abstract public class ManagedServices { public void onUserSwitched(int user) { if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user); + rebuildRestoredPackages(); if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) { if (DEBUG) Slog.d(TAG, "Current profile IDs didn't change, skipping rebindServices()."); return; @@ -229,7 +239,7 @@ abstract public class ManagedServices { checkNotNull(service); final IBinder token = service.asBinder(); final int N = mServices.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { final ManagedServiceInfo info = mServices.get(i); if (info.service.asBinder() == token) return info; } @@ -252,92 +262,142 @@ abstract public class ManagedServices { } } + + private void rebuildRestoredPackages() { + mRestoredPackages.clear(); + String settingName = restoredSettingName(mConfig); + int[] userIds = mUserProfiles.getCurrentProfileIds(); + final int N = userIds.length; + for (int i = 0; i < N; ++i) { + ArraySet<ComponentName> names = loadComponentNamesFromSetting(settingName, userIds[i]); + if (names == null) + continue; + for (ComponentName name: names) { + mRestoredPackages.add(name.getPackageName()); + } + } + } + + + private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, int userId) { + final ContentResolver cr = mContext.getContentResolver(); + String settingValue = Settings.Secure.getStringForUser( + cr, + settingName, + userId); + if (TextUtils.isEmpty(settingValue)) + return null; + String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR); + ArraySet<ComponentName> result = new ArraySet<>(restored.length); + for (int i = 0; i < restored.length; i++) { + ComponentName value = ComponentName.unflattenFromString(restored[i]); + if (null != value) { + result.add(value); + } + } + return result; + } + + private void storeComponentsToSetting(Set<ComponentName> components, + String settingName, + int userId) { + String[] componentNames = null; + if (null != components) { + componentNames = new String[components.size()]; + int index = 0; + for (ComponentName c: components) { + componentNames[index++] = c.flattenToString(); + } + } + final String value = (componentNames == null) ? "" : + TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames); + final ContentResolver cr = mContext.getContentResolver(); + Settings.Secure.putStringForUser( + cr, + settingName, + value, + userId); + } + + /** * Remove access for any services that no longer exist. */ - private void disableNonexistentServices() { + private void updateSettingsAccordingToInstalledServices() { int[] userIds = mUserProfiles.getCurrentProfileIds(); final int N = userIds.length; - for (int i = 0 ; i < N; ++i) { - disableNonexistentServices(userIds[i]); + for (int i = 0; i < N; ++i) { + updateSettingsAccordingToInstalledServices(userIds[i]); } + rebuildRestoredPackages(); } - private void disableNonexistentServices(int userId) { - final ContentResolver cr = mContext.getContentResolver(); + private void updateSettingsAccordingToInstalledServices(int userId) { boolean restoredChanged = false; - if (mRestored == null) { - String restoredSetting = Settings.Secure.getStringForUser( - cr, - restoredSettingName(mConfig), - userId); - if (!TextUtils.isEmpty(restoredSetting)) { - if (DEBUG) Slog.d(TAG, "restored: " + restoredSetting); - String[] restored = restoredSetting.split(ENABLED_SERVICES_SEPARATOR); - mRestored = new ArraySet<String>(Arrays.asList(restored)); - } else { - mRestored = new ArraySet<String>(); + boolean currentChanged = false; + Set<ComponentName> restored = + loadComponentNamesFromSetting(restoredSettingName(mConfig), userId); + Set<ComponentName> current = + loadComponentNamesFromSetting(mConfig.secureSettingName, userId); + Set<ComponentName> installed = new ArraySet<>(); + + final PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( + new Intent(mConfig.serviceInterface), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + userId); + if (DEBUG) + Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices); + + for (int i = 0, count = installedServices.size(); i < count; i++) { + ResolveInfo resolveInfo = installedServices.get(i); + ServiceInfo info = resolveInfo.serviceInfo; + + ComponentName component = new ComponentName(info.packageName, info.name); + if (!mConfig.bindPermission.equals(info.permission)) { + Slog.w(TAG, "Skipping " + getCaption() + " service " + + info.packageName + "/" + info.name + + ": it does not require the permission " + + mConfig.bindPermission); + continue; } + installed.add(component); } - String flatIn = Settings.Secure.getStringForUser( - cr, - mConfig.secureSettingName, - userId); - if (!TextUtils.isEmpty(flatIn)) { - if (DEBUG) Slog.v(TAG, "flat before: " + flatIn); - PackageManager pm = mContext.getPackageManager(); - List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( - new Intent(mConfig.serviceInterface), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - userId); - if (DEBUG) Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices); - Set<ComponentName> installed = new ArraySet<ComponentName>(); - for (int i = 0, count = installedServices.size(); i < count; i++) { - ResolveInfo resolveInfo = installedServices.get(i); - ServiceInfo info = resolveInfo.serviceInfo; - - ComponentName component = new ComponentName(info.packageName, info.name); - if (!mConfig.bindPermission.equals(info.permission)) { - Slog.w(TAG, "Skipping " + getCaption() + " service " - + info.packageName + "/" + info.name - + ": it does not require the permission " - + mConfig.bindPermission); - restoredChanged |= mRestored.remove(component.flattenToString()); + + ArraySet<ComponentName> retained = new ArraySet<>(); + + for (ComponentName component : installed) { + if (null != restored) { + boolean wasRestored = restored.remove(component); + if (wasRestored) { + // Freshly installed package has service that was mentioned in restored setting. + if (DEBUG) + Slog.v(TAG, "Restoring " + component + " for user " + userId); + restoredChanged = true; + currentChanged = true; + retained.add(component); continue; } - installed.add(component); } - String flatOut = ""; - if (!installed.isEmpty()) { - String[] enabled = flatIn.split(ENABLED_SERVICES_SEPARATOR); - ArrayList<String> remaining = new ArrayList<String>(enabled.length); - for (int i = 0; i < enabled.length; i++) { - ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); - if (installed.contains(enabledComponent)) { - remaining.add(enabled[i]); - restoredChanged |= mRestored.remove(enabled[i]); - } - } - remaining.addAll(mRestored); - flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining); - } - if (DEBUG) Slog.v(TAG, "flat after: " + flatOut); - if (!flatIn.equals(flatOut)) { - Settings.Secure.putStringForUser(cr, - mConfig.secureSettingName, - flatOut, userId); - } - if (restoredChanged) { - if (DEBUG) Slog.d(TAG, "restored changed; rewriting"); - final String flatRestored = TextUtils.join(ENABLED_SERVICES_SEPARATOR, - mRestored.toArray()); - Settings.Secure.putStringForUser(cr, - restoredSettingName(mConfig), - flatRestored, - userId); + if (null != current) { + if (current.contains(component)) + retained.add(component); } } + + currentChanged |= ((current == null ? 0 : current.size()) != retained.size()); + + if (currentChanged) { + if (DEBUG) Slog.v(TAG, "List of " + getCaption() + " services was updated " + current); + storeComponentsToSetting(retained, mConfig.secureSettingName, userId); + } + + if (restoredChanged) { + if (DEBUG) Slog.v(TAG, + "List of " + getCaption() + " restored services was updated " + restored); + storeComponentsToSetting(restored, restoredSettingName(mConfig), userId); + } } /** @@ -349,18 +409,15 @@ abstract public class ManagedServices { final int[] userIds = mUserProfiles.getCurrentProfileIds(); final int nUserIds = userIds.length; - final SparseArray<String> flat = new SparseArray<String>(); + final SparseArray<ArraySet<ComponentName>> componentsByUser = new SparseArray<>(); for (int i = 0; i < nUserIds; ++i) { - flat.put(userIds[i], Settings.Secure.getStringForUser( - mContext.getContentResolver(), - mConfig.secureSettingName, - userIds[i])); + componentsByUser.put(userIds[i], + loadComponentNamesFromSetting(mConfig.secureSettingName, userIds[i])); } - ArrayList<ManagedServiceInfo> toRemove = new ArrayList<ManagedServiceInfo>(); - final SparseArray<ArrayList<ComponentName>> toAdd - = new SparseArray<ArrayList<ComponentName>>(); + final ArrayList<ManagedServiceInfo> toRemove = new ArrayList<>(); + final SparseArray<ArrayList<ComponentName>> toAdd = new SparseArray<>(); synchronized (mMutex) { // Unbind automatically bound services, retain system services. @@ -370,27 +427,25 @@ abstract public class ManagedServices { } } - final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>(); - final ArraySet<String> newPackages = new ArraySet<String>(); + final ArraySet<ComponentName> newEnabled = new ArraySet<>(); + final ArraySet<String> newPackages = new ArraySet<>(); for (int i = 0; i < nUserIds; ++i) { - final ArrayList<ComponentName> add = new ArrayList<ComponentName>(); + // decode the list of components + final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]); + if (null == userComponents) { + toAdd.put(userIds[i], new ArrayList<ComponentName>()); + continue; + } + + final ArrayList<ComponentName> add = new ArrayList<>(userComponents); toAdd.put(userIds[i], add); - // decode the list of components - String toDecode = flat.get(userIds[i]); - if (toDecode != null) { - String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR); - for (int j = 0; j < components.length; j++) { - final ComponentName component - = ComponentName.unflattenFromString(components[j]); - if (component != null) { - newEnabled.add(component); - add.add(component); - newPackages.add(component.getPackageName()); - } - } + newEnabled.addAll(userComponents); + for (int j = 0; j < userComponents.size(); j++) { + final ComponentName component = userComponents.valueAt(i); + newPackages.add(component.getPackageName()); } } mEnabledServicesForCurrentProfiles = newEnabled; @@ -416,7 +471,7 @@ abstract public class ManagedServices { } } - mLastSeenProfileIds = mUserProfiles.getCurrentProfileIds(); + mLastSeenProfileIds = userIds; } /** @@ -434,7 +489,7 @@ abstract public class ManagedServices { mServicesBinding.add(servicesBindingTag); final int N = mServices.size(); - for (int i=N-1; i>=0; i--) { + for (int i = N - 1; i >= 0; i--) { final ManagedServiceInfo info = mServices.get(i); if (name.equals(info.component) && info.userid == userid) { @@ -469,39 +524,39 @@ abstract public class ManagedServices { try { if (DEBUG) Slog.v(TAG, "binding: " + intent); - if (!mContext.bindServiceAsUser(intent, - new ServiceConnection() { - IInterface mService; - - @Override - public void onServiceConnected(ComponentName name, IBinder binder) { - boolean added = false; - ManagedServiceInfo info = null; - synchronized (mMutex) { - mServicesBinding.remove(servicesBindingTag); - try { - mService = asInterface(binder); - info = newServiceInfo(mService, name, - userid, false /*isSystem*/, this, targetSdkVersion); - binder.linkToDeath(info, 0); - added = mServices.add(info); - } catch (RemoteException e) { - // already dead - } - } - if (added) { - onServiceAdded(info); - } + ServiceConnection serviceConnection = new ServiceConnection() { + IInterface mService; + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + boolean added = false; + ManagedServiceInfo info = null; + synchronized (mMutex) { + mServicesBinding.remove(servicesBindingTag); + try { + mService = asInterface(binder); + info = newServiceInfo(mService, name, + userid, false /*isSystem*/, this, targetSdkVersion); + binder.linkToDeath(info, 0); + added = mServices.add(info); + } catch (RemoteException e) { + // already dead } + } + if (added) { + onServiceAdded(info); + } + } - @Override - public void onServiceDisconnected(ComponentName name) { - Slog.v(TAG, getCaption() + " connection lost: " + name); - } - }, + @Override + public void onServiceDisconnected(ComponentName name) { + Slog.v(TAG, getCaption() + " connection lost: " + name); + } + }; + if (!mContext.bindServiceAsUser(intent, + serviceConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, - new UserHandle(userid))) - { + new UserHandle(userid))) { mServicesBinding.remove(servicesBindingTag); Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent); return; @@ -519,7 +574,7 @@ abstract public class ManagedServices { private void unregisterService(ComponentName name, int userid) { synchronized (mMutex) { final int N = mServices.size(); - for (int i=N-1; i>=0; i--) { + for (int i = N - 1; i >= 0; i--) { final ManagedServiceInfo info = mServices.get(i); if (name.equals(info.component) && info.userid == userid) { @@ -548,7 +603,7 @@ abstract public class ManagedServices { ManagedServiceInfo serviceInfo = null; synchronized (mMutex) { final int N = mServices.size(); - for (int i=N-1; i>=0; i--) { + for (int i = N - 1; i >= 0; i--) { final ManagedServiceInfo info = mServices.get(i); if (info.service.asBinder() == service.asBinder() && info.userid == userid) { @@ -622,6 +677,7 @@ abstract public class ManagedServices { if (DEBUG) Slog.d(TAG, "Setting changed: mSecureSettingsUri=" + mSecureSettingsUri + " / uri=" + uri); rebindServices(); + rebuildRestoredPackages(); } } } diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java index 334f708b88a9..df44b11fffa5 100644 --- a/services/net/java/android/net/dhcp/DhcpAckPacket.java +++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java @@ -46,7 +46,7 @@ class DhcpAckPacket extends DhcpPacket { return s + " ACK: your new IP " + mYourIp + ", netmask " + mSubnetMask + - ", gateway " + mGateway + dnsServers + + ", gateways " + mGateways + dnsServers + ", lease time " + mLeaseTime; } @@ -79,7 +79,7 @@ class DhcpAckPacket extends DhcpPacket { } addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask); - addTlv(buffer, DHCP_ROUTER, mGateway); + addTlv(buffer, DHCP_ROUTER, mGateways); addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName); addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress); addTlv(buffer, DHCP_DNS_SERVER, mDnsServers); diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 1a728a0ca261..28cb114ccf6b 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -356,21 +356,22 @@ public class DhcpClient extends BaseDhcpStateMachine { public void run() { maybeLog("Receive thread started"); while (!stopped) { + int length = 0; // Or compiler can't tell it's initialized if a parse error occurs. try { - int length = Os.read(mPacketSock, mPacket, 0, mPacket.length); + length = Os.read(mPacketSock, mPacket, 0, mPacket.length); DhcpPacket packet = null; packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); - if (packet != null) { - maybeLog("Received packet: " + packet); - sendMessage(CMD_RECEIVED_PACKET, packet); - } else if (PACKET_DBG) { - Log.d(TAG, - "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length)); - } + maybeLog("Received packet: " + packet); + sendMessage(CMD_RECEIVED_PACKET, packet); } catch (IOException|ErrnoException e) { if (!stopped) { Log.e(TAG, "Read error", e); } + } catch (DhcpPacket.ParseException e) { + Log.e(TAG, "Can't parse packet: " + e.getMessage()); + if (PACKET_DBG) { + Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length)); + } } } maybeLog("Receive thread stopped"); diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java index 7ca7100bbabc..99154ef0498c 100644 --- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java +++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java @@ -48,7 +48,7 @@ class DhcpOfferPacket extends DhcpPacket { } return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask + - dnsServers + ", gateway " + mGateway + + dnsServers + ", gateways " + mGateways + " lease time " + mLeaseTime + ", domain " + mDomainName; } @@ -81,7 +81,7 @@ class DhcpOfferPacket extends DhcpPacket { } addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask); - addTlv(buffer, DHCP_ROUTER, mGateway); + addTlv(buffer, DHCP_ROUTER, mGateways); addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName); addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress); addTlv(buffer, DHCP_DNS_SERVER, mDnsServers); diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index cbf8fc21b809..8927bfa25654 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -114,6 +114,11 @@ abstract class DhcpPacket { protected static final int MAX_LENGTH = 1500; /** + * The magic cookie that identifies this as a DHCP packet instead of BOOTP. + */ + private static final int DHCP_MAGIC_COOKIE = 0x63825363; + + /** * DHCP Optional Type: DHCP Subnet Mask */ protected static final byte DHCP_SUBNET_MASK = 1; @@ -123,7 +128,7 @@ abstract class DhcpPacket { * DHCP Optional Type: DHCP Router */ protected static final byte DHCP_ROUTER = 3; - protected Inet4Address mGateway; + protected List <Inet4Address> mGateways; /** * DHCP Optional Type: DHCP DNS Server @@ -403,7 +408,7 @@ abstract class DhcpPacket { (HWADDR_LEN - mClientMac.length) // pad addr to 16 bytes + 64 // empty server host name (64 bytes) + 128); // empty boot file name (128 bytes) - buf.putInt(0x63825363); // magic number + buf.putInt(DHCP_MAGIC_COOKIE); // magic number finishPacket(buf); // round up to an even number of octets @@ -668,6 +673,20 @@ abstract class DhcpPacket { return new String(bytes, 0, length, StandardCharsets.US_ASCII); } + private static boolean isPacketToOrFromClient(short udpSrcPort, short udpDstPort) { + return (udpSrcPort == DHCP_CLIENT) || (udpDstPort == DHCP_CLIENT); + } + + private static boolean isPacketServerToServer(short udpSrcPort, short udpDstPort) { + return (udpSrcPort == DHCP_SERVER) && (udpDstPort == DHCP_SERVER); + } + + public static class ParseException extends Exception { + public ParseException(String msg, Object... args) { + super(String.format(msg, args)); + } + } + /** * Creates a concrete DhcpPacket from the supplied ByteBuffer. The * buffer may have an L2 encapsulation (which is the full EthernetII @@ -677,7 +696,7 @@ abstract class DhcpPacket { * A subset of the optional parameters are parsed and are stored * in object fields. */ - public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) + public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType) throws ParseException { // bootp parameters int transactionId; @@ -687,8 +706,8 @@ abstract class DhcpPacket { Inet4Address nextIp; Inet4Address relayIp; byte[] clientMac; - List<Inet4Address> dnsServers = new ArrayList<Inet4Address>(); - Inet4Address gateway = null; // aka router + List<Inet4Address> dnsServers = new ArrayList<>(); + List<Inet4Address> gateways = new ArrayList<>(); // aka router Inet4Address serverIdentifier = null; Inet4Address netMask = null; String message = null; @@ -720,7 +739,8 @@ abstract class DhcpPacket { // check to see if we need to parse L2, IP, and UDP encaps if (pktType == ENCAP_L2) { if (packet.remaining() < MIN_PACKET_LENGTH_L2) { - return null; + throw new ParseException("L2 packet too short, %d < %d", + packet.remaining(), MIN_PACKET_LENGTH_L2); } byte[] l2dst = new byte[6]; @@ -732,18 +752,20 @@ abstract class DhcpPacket { short l2type = packet.getShort(); if (l2type != OsConstants.ETH_P_IP) - return null; + throw new ParseException("Unexpected L2 type 0x%04x, expected 0x%04x", + l2type, OsConstants.ETH_P_IP); } if (pktType <= ENCAP_L3) { if (packet.remaining() < MIN_PACKET_LENGTH_L3) { - return null; + throw new ParseException("L3 packet too short, %d < %d", + packet.remaining(), MIN_PACKET_LENGTH_L3); } byte ipTypeAndLength = packet.get(); int ipVersion = (ipTypeAndLength & 0xf0) >> 4; if (ipVersion != 4) { - return null; + throw new ParseException("Invalid IP version %d", ipVersion); } // System.out.println("ipType is " + ipType); @@ -759,8 +781,9 @@ abstract class DhcpPacket { ipSrc = readIpAddress(packet); ipDst = readIpAddress(packet); - if (ipProto != IP_TYPE_UDP) // UDP - return null; + if (ipProto != IP_TYPE_UDP) { + throw new ParseException("Protocol not UDP: %d", ipProto); + } // Skip options. This cannot cause us to read beyond the end of the buffer because the // IPv4 header cannot be more than (0x0f * 4) = 60 bytes long, and that is less than @@ -776,13 +799,19 @@ abstract class DhcpPacket { short udpLen = packet.getShort(); short udpChkSum = packet.getShort(); - if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT)) + // Only accept packets to or from the well-known client port (expressly permitting + // packets from ports other than the well-known server port; http://b/24687559), and + // server-to-server packets, e.g. for relays. + if (!isPacketToOrFromClient(udpSrcPort, udpDstPort) && + !isPacketServerToServer(udpSrcPort, udpDstPort)) { return null; + } } // We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length. if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) { - return null; + throw new ParseException("Invalid type or BOOTP packet too short, %d < %d", + packet.remaining(), MIN_PACKET_LENGTH_BOOTP); } byte type = packet.get(); @@ -805,7 +834,7 @@ abstract class DhcpPacket { packet.get(ipv4addr); relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr); } catch (UnknownHostException ex) { - return null; + throw new ParseException("Invalid IPv4 address: %s", Arrays.toString(ipv4addr)); } // Some DHCP servers have been known to announce invalid client hardware address values such @@ -828,8 +857,10 @@ abstract class DhcpPacket { int dhcpMagicCookie = packet.getInt(); - if (dhcpMagicCookie != 0x63825363) - return null; + if (dhcpMagicCookie != DHCP_MAGIC_COOKIE) { + throw new ParseException("Bad magic cookie 0x%08x, should be 0x%08x", dhcpMagicCookie, + DHCP_MAGIC_COOKIE); + } // parse options boolean notFinishedOptions = true; @@ -852,8 +883,9 @@ abstract class DhcpPacket { expectedLen = 4; break; case DHCP_ROUTER: - gateway = readIpAddress(packet); - expectedLen = 4; + for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) { + gateways.add(readIpAddress(packet)); + } break; case DHCP_DNS_SERVER: for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) { @@ -937,18 +969,20 @@ abstract class DhcpPacket { } if (expectedLen != optionLen) { - return null; + throw new ParseException("Invalid length %d for option %d, expected %d", + optionLen, optionType, expectedLen); } } } catch (BufferUnderflowException e) { - return null; + throw new ParseException("BufferUnderflowException"); } } DhcpPacket newPacket; switch(dhcpType) { - case -1: return null; + case (byte) 0xFF: + throw new ParseException("No DHCP message type option"); case DHCP_MESSAGE_TYPE_DISCOVER: newPacket = new DhcpDiscoverPacket( transactionId, secs, clientMac, broadcast); @@ -981,14 +1015,13 @@ abstract class DhcpPacket { clientMac); break; default: - System.out.println("Unimplemented type: " + dhcpType); - return null; + throw new ParseException("Unimplemented DHCP type %d", dhcpType); } newPacket.mBroadcastAddress = bcAddr; newPacket.mDnsServers = dnsServers; newPacket.mDomainName = domainName; - newPacket.mGateway = gateway; + newPacket.mGateways = gateways; newPacket.mHostName = hostName; newPacket.mLeaseTime = leaseTime; newPacket.mMessage = message; @@ -1009,7 +1042,7 @@ abstract class DhcpPacket { * Parse a packet from an array of bytes, stopping at the given length. */ public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType) - { + throws ParseException { ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN); return decodeFullPacket(buffer, pktType); } @@ -1044,7 +1077,11 @@ abstract class DhcpPacket { } catch (IllegalArgumentException e) { return null; } - results.gateway = mGateway; + + if (mGateways.size() > 0) { + results.gateway = mGateways.get(0); + } + results.dnsServers.addAll(mDnsServers); results.domains = mDomainName; results.serverAddress = mServerIdentifier; @@ -1086,11 +1123,11 @@ abstract class DhcpPacket { public static ByteBuffer buildOfferPacket(int encap, int transactionId, boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, - Inet4Address gateway, List<Inet4Address> dnsServers, + List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName) { DhcpPacket pkt = new DhcpOfferPacket( transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac); - pkt.mGateway = gateway; + pkt.mGateways = gateways; pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; pkt.mDomainName = domainName; @@ -1106,11 +1143,11 @@ abstract class DhcpPacket { public static ByteBuffer buildAckPacket(int encap, int transactionId, boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr, - Inet4Address gateway, List<Inet4Address> dnsServers, + List<Inet4Address> gateways, List<Inet4Address> dnsServers, Inet4Address dhcpServerIdentifier, String domainName) { DhcpPacket pkt = new DhcpAckPacket( transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac); - pkt.mGateway = gateway; + pkt.mGateways = gateways; pkt.mDnsServers = dnsServers; pkt.mLeaseTime = timeout; pkt.mDomainName = domainName; diff --git a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java index cd3b8bb7e0b9..7e60bf17d96e 100644 --- a/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java +++ b/services/tests/servicestests/src/android/net/dhcp/DhcpPacketTest.java @@ -117,7 +117,7 @@ public class DhcpPacketTest extends TestCase { private void assertDomainAndVendorInfoParses( String expectedDomain, byte[] domainBytes, - String expectedVendorInfo, byte[] vendorInfoBytes) { + String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception { ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER) .setDomainBytes(domainBytes) .setVendorInfoBytes(vendorInfoBytes) @@ -158,17 +158,25 @@ public class DhcpPacketTest extends TestCase { } private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime, - long leaseTimeMillis, byte[] leaseTimeBytes) { + long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception { TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER); if (leaseTimeBytes != null) { testPacket.setLeaseTimeBytes(leaseTimeBytes); } ByteBuffer packet = testPacket.build(); - DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + DhcpPacket offerPacket = null; + if (!expectValid) { - assertNull(offerPacket); + try { + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + fail("Invalid packet parsed successfully: " + offerPacket); + } catch (ParseException expected) { + } return; } + + offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP); + assertNotNull(offerPacket); assertEquals(rawLeaseTime, offerPacket.mLeaseTime); DhcpResults dhcpResults = offerPacket.toDhcpResults(); // Just check this doesn't crash. assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis()); @@ -200,14 +208,14 @@ public class DhcpPacketTest extends TestCase { } private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp, - byte[] netmaskBytes) { + byte[] netmaskBytes) throws Exception { checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes); checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes); } private void checkIpAddress(String expected, byte type, Inet4Address clientIp, Inet4Address yourIp, - byte[] netmaskBytes) { + byte[] netmaskBytes) throws Exception { ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp) .setNetmaskBytes(netmaskBytes) .build(); @@ -506,4 +514,74 @@ public class DhcpPacketTest extends TestCase { assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53", "lancs.ac.uk", "10.32.255.128", null, 7200, false, dhcpResults); } + + @SmallTest + public void testUdpServerAnySourcePort() throws Exception { + final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode(( + // Ethernet header. + "9cd917000000001c2e0000000800" + + // IP header. + "45a00148000040003d115087d18194fb0a0f7af2" + + // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). + // NOTE: The server source port is not the canonical port 67. + "C29F004401341268" + + // BOOTP header. + "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" + + // MAC address. + "9cd91700000000000000000000000000" + + // Server name. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // File. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // Options. + "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" + + "d18180060f0777766d2e6564751c040a0fffffff000000" + ).toCharArray(), false)); + + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + assertTrue(offerPacket instanceof DhcpOfferPacket); + assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac())); + DhcpResults dhcpResults = offerPacket.toDhcpResults(); + assertDhcpResults("10.15.122.242/16", "10.15.200.23", + "209.129.128.3,209.129.148.3,209.129.128.6", + "wvm.edu", "10.1.105.252", null, 86400, false, dhcpResults); + } + + @SmallTest + public void testMultipleRouters() throws Exception { + final ByteBuffer packet = ByteBuffer.wrap(HexEncoding.decode(( + // Ethernet header. + "fc3d93000000" + "081735000000" + "0800" + + // IP header. + "45000148c2370000ff117ac2c0a8bd02ffffffff" + + // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation). + "0043004401343beb" + + // BOOTP header. + "0201060027f518e20000800000000000c0a8bd310000000000000000" + + // MAC address. + "fc3d9300000000000000000000000000" + + // Server name. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // File. + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + // Options. + "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" + + "0308c0a8bd01ffffff0006080808080808080404ff000000000000" + ).toCharArray(), false)); + + DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2); + assertTrue(offerPacket instanceof DhcpOfferPacket); + assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac())); + DhcpResults dhcpResults = offerPacket.toDhcpResults(); + assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4", + null, "192.171.189.2", null, 28800, false, dhcpResults); + } } |