Loading core/java/android/service/dreams/DreamService.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -1047,7 +1047,7 @@ public class DreamService extends Service implements Window.Callback { } } if (mDreamToken == null) { if (mDreamToken == null) { Slog.w(mTag, "Finish was called before the dream was attached."); if (mDebug) Slog.v(mTag, "finish() called when not attached."); stopSelf(); stopSelf(); return; return; } } Loading services/core/java/com/android/server/dreams/DreamController.java +124 −72 Original line number Original line Diff line number Diff line Loading @@ -34,13 +34,13 @@ import android.os.UserHandle; import android.service.dreams.DreamService; import android.service.dreams.DreamService; import android.service.dreams.IDreamService; import android.service.dreams.IDreamService; import android.util.Slog; import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.PrintWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.NoSuchElementException; /** /** Loading @@ -60,9 +60,6 @@ final class DreamController { private final Context mContext; private final Context mContext; private final Handler mHandler; private final Handler mHandler; private final Listener mListener; private final Listener mListener; private final IWindowManager mIWindowManager; private long mDreamStartTime; private String mSavedStopReason; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); Loading @@ -73,27 +70,20 @@ final class DreamController { private DreamRecord mCurrentDream; private DreamRecord mCurrentDream; private final Runnable mStopUnconnectedDreamRunnable = new Runnable() { // Whether a dreaming started intent has been broadcast. @Override private boolean mSentStartBroadcast = false; public void run() { if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) { Slog.w(TAG, "Bound dream did not connect in the time allotted"); stopDream(true /*immediate*/, "slow to connect"); } } }; private final Runnable mStopStubbornDreamRunnable = () -> { // When a new dream is started and there is an existing dream, the existing dream is allowed to Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); // live a little longer until the new dream is started, for a smoother transition. This dream is stopDream(true /*immediate*/, "slow to finish"); // stopped as soon as the new dream is started, and this list is cleared. Usually there should mSavedStopReason = null; // only be one previous dream while waiting for a new dream to start, but we store a list to }; // proof the edge case of multiple previous dreams. private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>(); public DreamController(Context context, Handler handler, Listener listener) { public DreamController(Context context, Handler handler, Listener listener) { mContext = context; mContext = context; mHandler = handler; mHandler = handler; mListener = listener; mListener = listener; mIWindowManager = WindowManagerGlobal.getWindowManagerService(); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent.putExtra("reason", "dream"); mCloseNotificationShadeIntent.putExtra("reason", "dream"); } } Loading @@ -109,18 +99,17 @@ final class DreamController { pw.println(" mUserId=" + mCurrentDream.mUserId); pw.println(" mUserId=" + mCurrentDream.mUserId); pw.println(" mBound=" + mCurrentDream.mBound); pw.println(" mBound=" + mCurrentDream.mBound); pw.println(" mService=" + mCurrentDream.mService); pw.println(" mService=" + mCurrentDream.mService); pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast); pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); } else { } else { pw.println(" mCurrentDream: null"); pw.println(" mCurrentDream: null"); } } pw.println(" mSentStartBroadcast=" + mSentStartBroadcast); } } public void startDream(Binder token, ComponentName name, public void startDream(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, ComponentName overlayComponentName, String reason) { ComponentName overlayComponentName, String reason) { stopDream(true /*immediate*/, "starting new dream"); Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); try { try { // Close the notification shade. No need to send to all, but better to be explicit. // Close the notification shade. No need to send to all, but better to be explicit. Loading @@ -130,9 +119,12 @@ final class DreamController { + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze + ", userId=" + userId + ", reason='" + reason + "'"); + ", userId=" + userId + ", reason='" + reason + "'"); if (mCurrentDream != null) { mPreviousDreams.add(mCurrentDream); } mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); mDreamStartTime = SystemClock.elapsedRealtime(); mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime(); MetricsLogger.visible(mContext, MetricsLogger.visible(mContext, mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); Loading @@ -155,31 +147,49 @@ final class DreamController { } } mCurrentDream.mBound = true; mCurrentDream.mBound = true; mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); } finally { } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); Trace.traceEnd(Trace.TRACE_TAG_POWER); } } } } /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops * dreaming. */ public void stopDream(boolean immediate, String reason) { public void stopDream(boolean immediate, String reason) { if (mCurrentDream == null) { stopPreviousDreams(); stopDreamInstance(immediate, reason, mCurrentDream); } /** * Stops the given dream instance. * * The device may still be dreaming afterwards if there are other dreams running. */ private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) { if (dream == null) { return; return; } } Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); try { try { if (!immediate) { if (!immediate) { if (mCurrentDream.mWakingGently) { if (dream.mWakingGently) { return; // already waking gently return; // already waking gently } } if (mCurrentDream.mService != null) { if (dream.mService != null) { // Give the dream a moment to wake up and finish itself gently. // Give the dream a moment to wake up and finish itself gently. mCurrentDream.mWakingGently = true; dream.mWakingGently = true; try { try { mSavedStopReason = reason; dream.mStopReason = reason; mCurrentDream.mService.wakeUp(); dream.mService.wakeUp(); mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); mHandler.postDelayed(dream.mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); return; return; } catch (RemoteException ex) { } catch (RemoteException ex) { // oh well, we tried, finish immediately instead // oh well, we tried, finish immediately instead Loading @@ -187,54 +197,73 @@ final class DreamController { } } } } final DreamRecord oldDream = mCurrentDream; Slog.i(TAG, "Stopping dream: name=" + dream.mName mCurrentDream = null; + ", isPreviewMode=" + dream.mIsPreviewMode Slog.i(TAG, "Stopping dream: name=" + oldDream.mName + ", canDoze=" + dream.mCanDoze + ", isPreviewMode=" + oldDream.mIsPreviewMode + ", userId=" + dream.mUserId + ", canDoze=" + oldDream.mCanDoze + ", userId=" + oldDream.mUserId + ", reason='" + reason + "'" + ", reason='" + reason + "'" + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')")); + (dream.mStopReason == null ? "" : "(from '" + dream.mStopReason + "')")); MetricsLogger.hidden(mContext, MetricsLogger.hidden(mContext, oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); MetricsLogger.histogram(mContext, MetricsLogger.histogram(mContext, oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" , dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes", (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L))); (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L * 60L))); mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable); mHandler.removeCallbacks(mStopStubbornDreamRunnable); mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable); mSavedStopReason = null; if (oldDream.mSentStartBroadcast) { if (dream.mService != null) { mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); } if (oldDream.mService != null) { try { try { oldDream.mService.detach(); dream.mService.detach(); } catch (RemoteException ex) { } catch (RemoteException ex) { // we don't care; this thing is on the way out // we don't care; this thing is on the way out } } try { try { oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); dream.mService.asBinder().unlinkToDeath(dream, 0); } catch (NoSuchElementException ex) { } catch (NoSuchElementException ex) { // don't care // don't care } } oldDream.mService = null; dream.mService = null; } } if (oldDream.mBound) { if (dream.mBound) { mContext.unbindService(oldDream); mContext.unbindService(dream); } } oldDream.releaseWakeLockIfNeeded(); dream.releaseWakeLockIfNeeded(); // Current dream stopped, device no longer dreaming. if (dream == mCurrentDream) { mCurrentDream = null; mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken)); if (mSentStartBroadcast) { mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); } mListener.onDreamStopped(dream.mToken); } } finally { } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); Trace.traceEnd(Trace.TRACE_TAG_POWER); } } } } /** * Stops all previous dreams, if any. */ private void stopPreviousDreams() { if (mPreviousDreams.isEmpty()) { return; } // Using an iterator because mPreviousDreams is modified while the iteration is in process. for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) { stopDreamInstance(true /*immediate*/, "stop previous dream", it.next()); it.remove(); } } private void attach(IDreamService service) { private void attach(IDreamService service) { try { try { service.asBinder().linkToDeath(mCurrentDream, 0); service.asBinder().linkToDeath(mCurrentDream, 0); Loading @@ -248,9 +277,9 @@ final class DreamController { mCurrentDream.mService = service; mCurrentDream.mService = service; if (!mCurrentDream.mIsPreviewMode) { if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) { mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); mCurrentDream.mSentStartBroadcast = true; mSentStartBroadcast = true; } } } } Loading @@ -272,10 +301,35 @@ final class DreamController { public boolean mBound; public boolean mBound; public boolean mConnected; public boolean mConnected; public IDreamService mService; public IDreamService mService; public boolean mSentStartBroadcast; private String mStopReason; private long mDreamStartTime; public boolean mWakingGently; public boolean mWakingGently; private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; private final Runnable mStopUnconnectedDreamRunnable = () -> { if (mBound && !mConnected) { Slog.w(TAG, "Bound dream did not connect in the time allotted"); stopDream(true /*immediate*/, "slow to connect" /*reason*/); } }; private final Runnable mStopStubbornDreamRunnable = () -> { Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); stopDream(true /*immediate*/, "slow to finish" /*reason*/); mStopReason = null; }; private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { // May be called on any thread. @Override public void sendResult(Bundle data) { mHandler.post(mStopPreviousDreamsIfNeeded); mHandler.post(mReleaseWakeLockIfNeeded); } }; DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { mToken = token; mToken = token; Loading @@ -286,7 +340,9 @@ final class DreamController { mWakeLock = wakeLock; mWakeLock = wakeLock; // Hold the lock while we're waiting for the service to connect and start dreaming. // Hold the lock while we're waiting for the service to connect and start dreaming. // Released after the service has started dreaming, we stop dreaming, or it timed out. // Released after the service has started dreaming, we stop dreaming, or it timed out. if (mWakeLock != null) { mWakeLock.acquire(); mWakeLock.acquire(); } mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); } } Loading Loading @@ -326,6 +382,12 @@ final class DreamController { }); }); } } void stopPreviousDreamsIfNeeded() { if (mCurrentDream == DreamRecord.this) { stopPreviousDreams(); } } void releaseWakeLockIfNeeded() { void releaseWakeLockIfNeeded() { if (mWakeLock != null) { if (mWakeLock != null) { mWakeLock.release(); mWakeLock.release(); Loading @@ -333,15 +395,5 @@ final class DreamController { mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); } } } } final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { // May be called on any thread. @Override public void sendResult(Bundle data) throws RemoteException { mHandler.post(mReleaseWakeLockIfNeeded); } }; } } } } services/core/java/com/android/server/dreams/DreamManagerService.java +0 −2 Original line number Original line Diff line number Diff line Loading @@ -493,8 +493,6 @@ public final class DreamManagerService extends SystemService { return; return; } } stopDreamLocked(true /*immediate*/, "starting new dream"); Slog.i(TAG, "Entering dreamland."); Slog.i(TAG, "Entering dreamland."); mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze); mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze); Loading services/core/java/com/android/server/wm/Task.java +2 −5 Original line number Original line Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; Loading Loading @@ -5855,12 +5854,10 @@ class Task extends TaskFragment { return false; return false; } } // Existing Tasks can be reused if a new root task will be created anyway, or for the // Existing Tasks can be reused if a new root task will be created anyway. // Dream - because there can only ever be one DreamActivity. final int windowingMode = getWindowingMode(); final int windowingMode = getWindowingMode(); final int activityType = getActivityType(); final int activityType = getActivityType(); return DisplayContent.alwaysCreateRootTask(windowingMode, activityType) return DisplayContent.alwaysCreateRootTask(windowingMode, activityType); || activityType == ACTIVITY_TYPE_DREAM; } } void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { Loading services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java 0 → 100644 +160 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2022 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.dreams; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class DreamControllerTest { @Mock private DreamController.Listener mListener; @Mock private Context mContext; @Mock private IBinder mIBinder; @Mock private IDreamService mIDreamService; @Captor private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; @Captor private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private final TestLooper mLooper = new TestLooper(); private final Handler mHandler = new Handler(mLooper.getLooper()); private DreamController mDreamController; private Binder mToken; private ComponentName mDreamName; private ComponentName mOverlayName; @Before public void setup() { MockitoAnnotations.initMocks(this); when(mIDreamService.asBinder()).thenReturn(mIBinder); when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService); when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); mToken = new Binder(); mDreamName = ComponentName.unflattenFromString("dream"); mOverlayName = ComponentName.unflattenFromString("dream_overlay"); mDreamController = new DreamController(mContext, mHandler, mListener); } @Test public void startDream_attachOnServiceConnected() throws RemoteException { // Call dream controller to start dreaming. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); // Mock service connected. final ServiceConnection serviceConnection = captureServiceConnection(); serviceConnection.onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Verify that dream service is called to attach. verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any()); } @Test public void startDream_startASecondDream_detachOldDreamOnceNewDreamIsStarted() throws RemoteException { // Start first dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); clearInvocations(mContext); // Set up second dream. final Binder newToken = new Binder(); final ComponentName newDreamName = ComponentName.unflattenFromString("new_dream"); final ComponentName newOverlayName = ComponentName.unflattenFromString("new_dream_overlay"); final IDreamService newDreamService = mock(IDreamService.class); final IBinder newBinder = mock(IBinder.class); when(newDreamService.asBinder()).thenReturn(newBinder); when(newBinder.queryLocalInterface(anyString())).thenReturn(newDreamService); // Start second dream. mDreamController.startDream(newToken, newDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, newOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(newDreamName, newBinder); mLooper.dispatchAll(); // Mock second dream started. verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/, mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(null /*data*/); mLooper.dispatchAll(); // Verify that the first dream is called to detach. verify(mIDreamService).detach(); } @Test public void stopDream_detachFromService() throws RemoteException { // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Stop dream. mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/); // Verify that dream service is called to detach. verify(mIDreamService).detach(); } private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); return mServiceConnectionACaptor.getValue(); } } Loading
core/java/android/service/dreams/DreamService.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -1047,7 +1047,7 @@ public class DreamService extends Service implements Window.Callback { } } if (mDreamToken == null) { if (mDreamToken == null) { Slog.w(mTag, "Finish was called before the dream was attached."); if (mDebug) Slog.v(mTag, "finish() called when not attached."); stopSelf(); stopSelf(); return; return; } } Loading
services/core/java/com/android/server/dreams/DreamController.java +124 −72 Original line number Original line Diff line number Diff line Loading @@ -34,13 +34,13 @@ import android.os.UserHandle; import android.service.dreams.DreamService; import android.service.dreams.DreamService; import android.service.dreams.IDreamService; import android.service.dreams.IDreamService; import android.util.Slog; import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.io.PrintWriter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.NoSuchElementException; /** /** Loading @@ -60,9 +60,6 @@ final class DreamController { private final Context mContext; private final Context mContext; private final Handler mHandler; private final Handler mHandler; private final Listener mListener; private final Listener mListener; private final IWindowManager mIWindowManager; private long mDreamStartTime; private String mSavedStopReason; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); Loading @@ -73,27 +70,20 @@ final class DreamController { private DreamRecord mCurrentDream; private DreamRecord mCurrentDream; private final Runnable mStopUnconnectedDreamRunnable = new Runnable() { // Whether a dreaming started intent has been broadcast. @Override private boolean mSentStartBroadcast = false; public void run() { if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) { Slog.w(TAG, "Bound dream did not connect in the time allotted"); stopDream(true /*immediate*/, "slow to connect"); } } }; private final Runnable mStopStubbornDreamRunnable = () -> { // When a new dream is started and there is an existing dream, the existing dream is allowed to Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); // live a little longer until the new dream is started, for a smoother transition. This dream is stopDream(true /*immediate*/, "slow to finish"); // stopped as soon as the new dream is started, and this list is cleared. Usually there should mSavedStopReason = null; // only be one previous dream while waiting for a new dream to start, but we store a list to }; // proof the edge case of multiple previous dreams. private final ArrayList<DreamRecord> mPreviousDreams = new ArrayList<>(); public DreamController(Context context, Handler handler, Listener listener) { public DreamController(Context context, Handler handler, Listener listener) { mContext = context; mContext = context; mHandler = handler; mHandler = handler; mListener = listener; mListener = listener; mIWindowManager = WindowManagerGlobal.getWindowManagerService(); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent.putExtra("reason", "dream"); mCloseNotificationShadeIntent.putExtra("reason", "dream"); } } Loading @@ -109,18 +99,17 @@ final class DreamController { pw.println(" mUserId=" + mCurrentDream.mUserId); pw.println(" mUserId=" + mCurrentDream.mUserId); pw.println(" mBound=" + mCurrentDream.mBound); pw.println(" mBound=" + mCurrentDream.mBound); pw.println(" mService=" + mCurrentDream.mService); pw.println(" mService=" + mCurrentDream.mService); pw.println(" mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast); pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); pw.println(" mWakingGently=" + mCurrentDream.mWakingGently); } else { } else { pw.println(" mCurrentDream: null"); pw.println(" mCurrentDream: null"); } } pw.println(" mSentStartBroadcast=" + mSentStartBroadcast); } } public void startDream(Binder token, ComponentName name, public void startDream(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock, ComponentName overlayComponentName, String reason) { ComponentName overlayComponentName, String reason) { stopDream(true /*immediate*/, "starting new dream"); Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream"); try { try { // Close the notification shade. No need to send to all, but better to be explicit. // Close the notification shade. No need to send to all, but better to be explicit. Loading @@ -130,9 +119,12 @@ final class DreamController { + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze + ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze + ", userId=" + userId + ", reason='" + reason + "'"); + ", userId=" + userId + ", reason='" + reason + "'"); if (mCurrentDream != null) { mPreviousDreams.add(mCurrentDream); } mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock); mDreamStartTime = SystemClock.elapsedRealtime(); mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime(); MetricsLogger.visible(mContext, MetricsLogger.visible(mContext, mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); Loading @@ -155,31 +147,49 @@ final class DreamController { } } mCurrentDream.mBound = true; mCurrentDream.mBound = true; mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); mHandler.postDelayed(mCurrentDream.mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); } finally { } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); Trace.traceEnd(Trace.TRACE_TAG_POWER); } } } } /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops * dreaming. */ public void stopDream(boolean immediate, String reason) { public void stopDream(boolean immediate, String reason) { if (mCurrentDream == null) { stopPreviousDreams(); stopDreamInstance(immediate, reason, mCurrentDream); } /** * Stops the given dream instance. * * The device may still be dreaming afterwards if there are other dreams running. */ private void stopDreamInstance(boolean immediate, String reason, DreamRecord dream) { if (dream == null) { return; return; } } Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); Trace.traceBegin(Trace.TRACE_TAG_POWER, "stopDream"); try { try { if (!immediate) { if (!immediate) { if (mCurrentDream.mWakingGently) { if (dream.mWakingGently) { return; // already waking gently return; // already waking gently } } if (mCurrentDream.mService != null) { if (dream.mService != null) { // Give the dream a moment to wake up and finish itself gently. // Give the dream a moment to wake up and finish itself gently. mCurrentDream.mWakingGently = true; dream.mWakingGently = true; try { try { mSavedStopReason = reason; dream.mStopReason = reason; mCurrentDream.mService.wakeUp(); dream.mService.wakeUp(); mHandler.postDelayed(mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); mHandler.postDelayed(dream.mStopStubbornDreamRunnable, DREAM_FINISH_TIMEOUT); return; return; } catch (RemoteException ex) { } catch (RemoteException ex) { // oh well, we tried, finish immediately instead // oh well, we tried, finish immediately instead Loading @@ -187,54 +197,73 @@ final class DreamController { } } } } final DreamRecord oldDream = mCurrentDream; Slog.i(TAG, "Stopping dream: name=" + dream.mName mCurrentDream = null; + ", isPreviewMode=" + dream.mIsPreviewMode Slog.i(TAG, "Stopping dream: name=" + oldDream.mName + ", canDoze=" + dream.mCanDoze + ", isPreviewMode=" + oldDream.mIsPreviewMode + ", userId=" + dream.mUserId + ", canDoze=" + oldDream.mCanDoze + ", userId=" + oldDream.mUserId + ", reason='" + reason + "'" + ", reason='" + reason + "'" + (mSavedStopReason == null ? "" : "(from '" + mSavedStopReason + "')")); + (dream.mStopReason == null ? "" : "(from '" + dream.mStopReason + "')")); MetricsLogger.hidden(mContext, MetricsLogger.hidden(mContext, oldDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); dream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); MetricsLogger.histogram(mContext, MetricsLogger.histogram(mContext, oldDream.mCanDoze ? "dozing_minutes" : "dreaming_minutes" , dream.mCanDoze ? "dozing_minutes" : "dreaming_minutes", (int) ((SystemClock.elapsedRealtime() - mDreamStartTime) / (1000L * 60L))); (int) ((SystemClock.elapsedRealtime() - dream.mDreamStartTime) / (1000L * 60L))); mHandler.removeCallbacks(mStopUnconnectedDreamRunnable); mHandler.removeCallbacks(dream.mStopUnconnectedDreamRunnable); mHandler.removeCallbacks(mStopStubbornDreamRunnable); mHandler.removeCallbacks(dream.mStopStubbornDreamRunnable); mSavedStopReason = null; if (oldDream.mSentStartBroadcast) { if (dream.mService != null) { mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); } if (oldDream.mService != null) { try { try { oldDream.mService.detach(); dream.mService.detach(); } catch (RemoteException ex) { } catch (RemoteException ex) { // we don't care; this thing is on the way out // we don't care; this thing is on the way out } } try { try { oldDream.mService.asBinder().unlinkToDeath(oldDream, 0); dream.mService.asBinder().unlinkToDeath(dream, 0); } catch (NoSuchElementException ex) { } catch (NoSuchElementException ex) { // don't care // don't care } } oldDream.mService = null; dream.mService = null; } } if (oldDream.mBound) { if (dream.mBound) { mContext.unbindService(oldDream); mContext.unbindService(dream); } } oldDream.releaseWakeLockIfNeeded(); dream.releaseWakeLockIfNeeded(); // Current dream stopped, device no longer dreaming. if (dream == mCurrentDream) { mCurrentDream = null; mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken)); if (mSentStartBroadcast) { mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL); } mListener.onDreamStopped(dream.mToken); } } finally { } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); Trace.traceEnd(Trace.TRACE_TAG_POWER); } } } } /** * Stops all previous dreams, if any. */ private void stopPreviousDreams() { if (mPreviousDreams.isEmpty()) { return; } // Using an iterator because mPreviousDreams is modified while the iteration is in process. for (final Iterator<DreamRecord> it = mPreviousDreams.iterator(); it.hasNext(); ) { stopDreamInstance(true /*immediate*/, "stop previous dream", it.next()); it.remove(); } } private void attach(IDreamService service) { private void attach(IDreamService service) { try { try { service.asBinder().linkToDeath(mCurrentDream, 0); service.asBinder().linkToDeath(mCurrentDream, 0); Loading @@ -248,9 +277,9 @@ final class DreamController { mCurrentDream.mService = service; mCurrentDream.mService = service; if (!mCurrentDream.mIsPreviewMode) { if (!mCurrentDream.mIsPreviewMode && !mSentStartBroadcast) { mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); mCurrentDream.mSentStartBroadcast = true; mSentStartBroadcast = true; } } } } Loading @@ -272,10 +301,35 @@ final class DreamController { public boolean mBound; public boolean mBound; public boolean mConnected; public boolean mConnected; public IDreamService mService; public IDreamService mService; public boolean mSentStartBroadcast; private String mStopReason; private long mDreamStartTime; public boolean mWakingGently; public boolean mWakingGently; private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; private final Runnable mStopUnconnectedDreamRunnable = () -> { if (mBound && !mConnected) { Slog.w(TAG, "Bound dream did not connect in the time allotted"); stopDream(true /*immediate*/, "slow to connect" /*reason*/); } }; private final Runnable mStopStubbornDreamRunnable = () -> { Slog.w(TAG, "Stubborn dream did not finish itself in the time allotted"); stopDream(true /*immediate*/, "slow to finish" /*reason*/); mStopReason = null; }; private final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { // May be called on any thread. @Override public void sendResult(Bundle data) { mHandler.post(mStopPreviousDreamsIfNeeded); mHandler.post(mReleaseWakeLockIfNeeded); } }; DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, DreamRecord(Binder token, ComponentName name, boolean isPreviewMode, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { mToken = token; mToken = token; Loading @@ -286,7 +340,9 @@ final class DreamController { mWakeLock = wakeLock; mWakeLock = wakeLock; // Hold the lock while we're waiting for the service to connect and start dreaming. // Hold the lock while we're waiting for the service to connect and start dreaming. // Released after the service has started dreaming, we stop dreaming, or it timed out. // Released after the service has started dreaming, we stop dreaming, or it timed out. if (mWakeLock != null) { mWakeLock.acquire(); mWakeLock.acquire(); } mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); mHandler.postDelayed(mReleaseWakeLockIfNeeded, 10000); } } Loading Loading @@ -326,6 +382,12 @@ final class DreamController { }); }); } } void stopPreviousDreamsIfNeeded() { if (mCurrentDream == DreamRecord.this) { stopPreviousDreams(); } } void releaseWakeLockIfNeeded() { void releaseWakeLockIfNeeded() { if (mWakeLock != null) { if (mWakeLock != null) { mWakeLock.release(); mWakeLock.release(); Loading @@ -333,15 +395,5 @@ final class DreamController { mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); } } } } final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; final IRemoteCallback mDreamingStartedCallback = new IRemoteCallback.Stub() { // May be called on any thread. @Override public void sendResult(Bundle data) throws RemoteException { mHandler.post(mReleaseWakeLockIfNeeded); } }; } } } }
services/core/java/com/android/server/dreams/DreamManagerService.java +0 −2 Original line number Original line Diff line number Diff line Loading @@ -493,8 +493,6 @@ public final class DreamManagerService extends SystemService { return; return; } } stopDreamLocked(true /*immediate*/, "starting new dream"); Slog.i(TAG, "Entering dreamland."); Slog.i(TAG, "Entering dreamland."); mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze); mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze); Loading
services/core/java/com/android/server/wm/Task.java +2 −5 Original line number Original line Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; Loading Loading @@ -5855,12 +5854,10 @@ class Task extends TaskFragment { return false; return false; } } // Existing Tasks can be reused if a new root task will be created anyway, or for the // Existing Tasks can be reused if a new root task will be created anyway. // Dream - because there can only ever be one DreamActivity. final int windowingMode = getWindowingMode(); final int windowingMode = getWindowingMode(); final int activityType = getActivityType(); final int activityType = getActivityType(); return DisplayContent.alwaysCreateRootTask(windowingMode, activityType) return DisplayContent.alwaysCreateRootTask(windowingMode, activityType); || activityType == ACTIVITY_TYPE_DREAM; } } void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { void addChild(WindowContainer child, final boolean toTop, boolean showForAllUsers) { Loading
services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java 0 → 100644 +160 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2022 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.dreams; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class DreamControllerTest { @Mock private DreamController.Listener mListener; @Mock private Context mContext; @Mock private IBinder mIBinder; @Mock private IDreamService mIDreamService; @Captor private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; @Captor private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private final TestLooper mLooper = new TestLooper(); private final Handler mHandler = new Handler(mLooper.getLooper()); private DreamController mDreamController; private Binder mToken; private ComponentName mDreamName; private ComponentName mOverlayName; @Before public void setup() { MockitoAnnotations.initMocks(this); when(mIDreamService.asBinder()).thenReturn(mIBinder); when(mIBinder.queryLocalInterface(anyString())).thenReturn(mIDreamService); when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); mToken = new Binder(); mDreamName = ComponentName.unflattenFromString("dream"); mOverlayName = ComponentName.unflattenFromString("dream_overlay"); mDreamController = new DreamController(mContext, mHandler, mListener); } @Test public void startDream_attachOnServiceConnected() throws RemoteException { // Call dream controller to start dreaming. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); // Mock service connected. final ServiceConnection serviceConnection = captureServiceConnection(); serviceConnection.onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Verify that dream service is called to attach. verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any()); } @Test public void startDream_startASecondDream_detachOldDreamOnceNewDreamIsStarted() throws RemoteException { // Start first dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); clearInvocations(mContext); // Set up second dream. final Binder newToken = new Binder(); final ComponentName newDreamName = ComponentName.unflattenFromString("new_dream"); final ComponentName newOverlayName = ComponentName.unflattenFromString("new_dream_overlay"); final IDreamService newDreamService = mock(IDreamService.class); final IBinder newBinder = mock(IBinder.class); when(newDreamService.asBinder()).thenReturn(newBinder); when(newBinder.queryLocalInterface(anyString())).thenReturn(newDreamService); // Start second dream. mDreamController.startDream(newToken, newDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, newOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(newDreamName, newBinder); mLooper.dispatchAll(); // Mock second dream started. verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/, mRemoteCallbackCaptor.capture()); mRemoteCallbackCaptor.getValue().sendResult(null /*data*/); mLooper.dispatchAll(); // Verify that the first dream is called to detach. verify(mIDreamService).detach(); } @Test public void stopDream_detachFromService() throws RemoteException { // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Stop dream. mDreamController.stopDream(true /*immediate*/, "test stop dream" /*reason*/); // Verify that dream service is called to detach. verify(mIDreamService).detach(); } private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); return mServiceConnectionACaptor.getValue(); } }