Merge "Smooth transition when switching dreaming." into tm-qpr-dev am: f932a2974e
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20041857 Change-Id: Idbbd394f57016b4609450819729f22ee102db478 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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);
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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();
|
||||||
|
|
||||||
mHandler.post(() -> mListener.onDreamStopped(oldDream.mToken));
|
// Current dream stopped, device no longer dreaming.
|
||||||
|
if (dream == mCurrentDream) {
|
||||||
|
mCurrentDream = null;
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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();
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -5856,12 +5855,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) {
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user