Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6861e91b authored by Galia Peycheva's avatar Galia Peycheva
Browse files

Make DreamService use an Activity

Currently, the DreamService uses a floating Window to display the
content of the dream on the screen. This introduces difficulties in the
interactions with the Assistant application, which is an activity.
By design, if the Assistant is invoked while the device is dreaming, the
Assistant should be shown on top. However, since floating windows are
always drawn on top of all activities, the Assistant can't be shown on
top of the Dream.

Here, we migrate the implementation of the DreamService to use an
Activity (DreamActivity). Since the screensaver application is not part
of the framework, we can't declare the activity in their AndroidManifest.xml.
Therefore, we start the dream activity with a dedicated method in
ActivityTaskManagerService.

Bug: 133216167
Test: 1. m && ./vendor/google/tools/flashall
      2. Go to Settings > Device Preferences > Screensaver > Start now
      3. Verify dream appears
      4. Click any key to wake up the dream
      5. Verify that dream disappears

Merged-In: I8dff0a124cd1b41fb925a73528305431b49ee06d
Change-Id: I8dff0a124cd1b41fb925a73528305431b49ee06d
(cherry picked from commit 148478a8)
parent a8e46412
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -98,6 +98,14 @@ interface IActivityTaskManager {
            in ProfilerInfo profilerInfo, in Bundle options, int userId);
    boolean startNextMatchingActivity(in IBinder callingActivity,
            in Intent intent, in Bundle options);

    /**
    *  The DreamActivity has to be started in a special way that does not involve the PackageParser.
    *  The DreamActivity is a framework component inserted in the dream application process. Hence,
    *  it is not declared in the application's manifest and cannot be parsed. startDreamActivity
    *  creates the activity and starts it without reaching out to the PackageParser.
    */
    boolean startDreamActivity(in Intent intent);
    int startActivityIntentSender(in IApplicationThread caller,
            in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent,
            in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.service.dreams;

import android.annotation.Nullable;
import android.app.Activity;
import android.os.Bundle;

/**
 * The Activity used by the {@link DreamService} to draw screensaver content
 * on the screen. This activity runs in dream application's process, but is started by a
 * specialized method: {@link com.android.server.wm.ActivityTaskManagerService#startDreamActivity}.
 * Hence, it does not have to be declared in the dream application's manifest.
 *
 * We use an activity as the dream canvas, because it interacts easier with other activities on
 * the screen (compared to a hover window). However, the DreamService is in charge of the dream and
 * it receives all Window.Callbacks from its main window. Since a window can have only one callback
 * receiver, the activity will not receive any window callbacks.
 *
 * Prior to the DreamActivity, the DreamService used to work with a hovering window and give the
 * screensaver application control over that window. The DreamActivity is a replacement to that
 * hover window. Using an activity allows for better-defined interactions with the rest of the
 * activities on screen. The switch to DreamActivity should be transparent to the screensaver
 * application, i.e. the application will still use DreamService APIs and not notice that the
 * system is using an activity behind the scenes.
 *
 * @hide
 */
public class DreamActivity extends Activity {
    static final String EXTRA_CALLBACK = "binder";

    public DreamActivity() {}

    @Override
    public void onCreate(@Nullable Bundle bundle) {
        super.onCreate(bundle);

        DreamService.DreamServiceWrapper callback =
                (DreamService.DreamServiceWrapper) getIntent().getIBinderExtra(EXTRA_CALLBACK);

        if (callback != null) {
            callback.onActivityCreated(this);
        }
    }
}
+138 −107
Original line number Diff line number Diff line
@@ -15,25 +15,29 @@
 */
package android.service.dreams;

import static android.view.WindowManager.LayoutParams.TYPE_DREAM;

import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.view.ActionMode;
@@ -48,10 +52,8 @@ import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;

import com.android.internal.policy.PhoneWindow;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils.Dump;

@@ -176,10 +178,11 @@ public class DreamService extends Service implements Window.Callback {
     */
    public static final String DREAM_META_DATA = "android.service.dream";

    private final IDreamManager mSandman;
    private final Handler mHandler = new Handler();
    private IBinder mWindowToken;
    private final IDreamManager mDreamManager;
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    private IBinder mDreamToken;
    private Window mWindow;
    private Activity mActivity;
    private boolean mInteractive;
    private boolean mLowProfile = true;
    private boolean mFullscreen;
@@ -195,8 +198,11 @@ public class DreamService extends Service implements Window.Callback {

    private boolean mDebug = false;

    private DreamServiceWrapper mDreamServiceWrapper;
    private Runnable mDispatchAfterOnAttachedToWindow;

    public DreamService() {
        mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
        mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
    }

    /**
@@ -670,14 +676,14 @@ public class DreamService extends Service implements Window.Callback {
    }

    private void updateDoze() {
        if (mWindowToken == null) {
            Slog.w(TAG, "Updating doze without a window token.");
        if (mDreamToken == null) {
            Slog.w(TAG, "Updating doze without a dream token.");
            return;
        }

        if (mDozing) {
            try {
                mSandman.startDozing(mWindowToken, mDozeScreenState, mDozeScreenBrightness);
                mDreamManager.startDozing(mDreamToken, mDozeScreenState, mDozeScreenBrightness);
            } catch (RemoteException ex) {
                // system server died
            }
@@ -700,7 +706,7 @@ public class DreamService extends Service implements Window.Callback {
        if (mDozing) {
            mDozing = false;
            try {
                mSandman.stopDozing(mWindowToken);
                mDreamManager.stopDozing(mDreamToken);
            } catch (RemoteException ex) {
                // system server died
            }
@@ -875,14 +881,15 @@ public class DreamService extends Service implements Window.Callback {
     * </p>
     */
    public void onWakeUp() {
        finish();
        mActivity.finishAndRemoveTask();
    }

    /** {@inheritDoc} */
    @Override
    public final IBinder onBind(Intent intent) {
        if (mDebug) Slog.v(TAG, "onBind() intent = " + intent);
        return new DreamServiceWrapper();
        mDreamServiceWrapper = new DreamServiceWrapper();
        return mDreamServiceWrapper;
    }

    /**
@@ -895,20 +902,29 @@ public class DreamService extends Service implements Window.Callback {
    public final void finish() {
        if (mDebug) Slog.v(TAG, "finish(): mFinished=" + mFinished);

        if (mActivity == null) {
            Slog.w(TAG, "Finish was called before the dream was attached.");
            return;
        }

        // In case the activity is not finished yet, do it now. This can happen if someone calls
        // finish() directly, without going through wakeUp().
        if (!mActivity.isFinishing()) {
            mActivity.finishAndRemoveTask();
            return;
        }

        if (!mFinished) {
            mFinished = true;

            if (mWindowToken == null) {
                Slog.w(TAG, "Finish was called before the dream was attached.");
            } else {
            try {
                    mSandman.finishSelf(mWindowToken, true /*immediate*/);
                // finishSelf will unbind the dream controller from the dream service. This will
                // trigger DreamService.this.onDestroy and DreamService.this will die.
                mDreamManager.finishSelf(mDreamToken, true /*immediate*/);
            } catch (RemoteException ex) {
                // system server died
            }
            }

            stopSelf(); // if launched via any other means
        }
    }

@@ -938,11 +954,11 @@ public class DreamService extends Service implements Window.Callback {
            // Now tell the system we are waking gently, unless we already told
            // it we were finishing immediately.
            if (!fromSystem && !mFinished) {
                if (mWindowToken == null) {
                if (mActivity == null) {
                    Slog.w(TAG, "WakeUp was called before the dream was attached.");
                } else {
                    try {
                        mSandman.finishSelf(mWindowToken, false /*immediate*/);
                        mDreamManager.finishSelf(mDreamToken, false /*immediate*/);
                    } catch (RemoteException ex) {
                        // system server died
                    }
@@ -977,69 +993,96 @@ public class DreamService extends Service implements Window.Callback {
            onDreamingStopped();
        }

        if (mWindow != null) {
            // force our window to be removed synchronously
            if (mDebug) Slog.v(TAG, "detach(): Removing window from window manager");
            mWindow.getWindowManager().removeViewImmediate(mWindow.getDecorView());
            mWindow = null;
        if (mActivity != null && !mActivity.isFinishing()) {
            mActivity.finishAndRemoveTask();
        }

        if (mWindowToken != null) {
            // the following will print a log message if it finds any other leaked windows
            WindowManagerGlobal.getInstance().closeAll(mWindowToken,
                    this.getClass().getName(), "Dream");
            mWindowToken = null;
        mDreamToken = null;
        mCanDoze = false;
    }
    }

    /**
     * Called when the Dream is ready to be shown.
     *
     * Must run on mHandler.
     *
     * @param windowToken A window token that will allow a window to be created in the correct layer.
     * @param dreamToken Token for this dream service.
     * @param started A callback that will be invoked once onDreamingStarted has completed.
     */
    private final void attach(IBinder windowToken, boolean canDoze, IRemoteCallback started) {
        if (mWindowToken != null) {
            Slog.e(TAG, "attach() called when already attached with token=" + mWindowToken);
    private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
        if (mActivity != null) {
            Slog.e(TAG, "attach() called when dream with token=" + mDreamToken
                    + " already attached");
            return;
        }
        if (mFinished || mWaking) {
            Slog.w(TAG, "attach() called after dream already finished");
            try {
                mSandman.finishSelf(windowToken, true /*immediate*/);
                mDreamManager.finishSelf(dreamToken, true /*immediate*/);
            } catch (RemoteException ex) {
                // system server died
            }
            return;
        }

        mWindowToken = windowToken;
        mDreamToken = dreamToken;
        mCanDoze = canDoze;
        if (mWindowless && !mCanDoze) {
            throw new IllegalStateException("Only doze dreams can be windowless");
        }

        mDispatchAfterOnAttachedToWindow = () -> {
            if (mWindow != null || mWindowless) {
                mStarted = true;
                try {
                    onDreamingStarted();
                } finally {
                    try {
                        started.sendResult(null);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        };

        // We need to defer calling onDreamingStarted until after the activity is created.
        // If the dream is windowless, we can call it immediately. Otherwise, we wait
        // for the DreamActivity to report onActivityCreated via
        // DreamServiceWrapper.onActivityCreated.
        if (!mWindowless) {
            mWindow = new PhoneWindow(this);
            Intent i = new Intent(this, DreamActivity.class);
            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            i.putExtra(DreamActivity.EXTRA_CALLBACK, mDreamServiceWrapper);

            try {
                if (!ActivityTaskManager.getService().startDreamActivity(i)) {
                    detach();
                    return;
                }
            } catch (RemoteException e) {
                Log.w(TAG, "Could not connect to activity task manager to start dream activity");
                e.rethrowFromSystemServer();
            }
        } else {
            mDispatchAfterOnAttachedToWindow.run();
        }
    }

    private void onWindowCreated(Window w) {
        mWindow = w;
        mWindow.setCallback(this);
        mWindow.setType(TYPE_DREAM);
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
            mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
            mWindow.setFormat(PixelFormat.OPAQUE);

            if (mDebug) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
                    windowToken, WindowManager.LayoutParams.TYPE_DREAM));

        WindowManager.LayoutParams lp = mWindow.getAttributes();
            lp.type = WindowManager.LayoutParams.TYPE_DREAM;
            lp.token = windowToken;
        lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
        lp.flags |= (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                    | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
                    | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0)
                    | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
                    );
@@ -1047,44 +1090,21 @@ public class DreamService extends Service implements Window.Callback {
        // Workaround: Currently low-profile and in-window system bar backgrounds don't go
        // along well. Dreams usually don't need such bars anyways, so disable them by default.
        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            mWindow.setWindowManager(null, windowToken, "dream", true);

        applySystemUiVisibilityFlags(
                (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0),
                View.SYSTEM_UI_FLAG_LOW_PROFILE);

            try {
                getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
            } catch (WindowManager.BadTokenException ex) {
                // This can happen because the dream manager service will remove the token
                // immediately without necessarily waiting for the dream to start.
                // We should receive a finish message soon.
                Slog.i(TAG, "attach() called after window token already removed, dream will "
                        + "finish soon");
                mWindow = null;
                return;
            }
        }
        // We need to defer calling onDreamingStarted until after onWindowAttached,
        // which is posted to the handler by addView, so we post onDreamingStarted
        // to the handler also.  Need to watch out here in case detach occurs before
        // this callback is invoked.
        mHandler.post(new Runnable() {
        mWindow.getDecorView().addOnAttachStateChangeListener(
                new View.OnAttachStateChangeListener() {
                    @Override
            public void run() {
                if (mWindow != null || mWindowless) {
                    if (mDebug) Slog.v(TAG, "Calling onDreamingStarted()");
                    mStarted = true;
                    try {
                        onDreamingStarted();
                    } finally {
                        try {
                            started.sendResult(null);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
                    }
                    public void onViewAttachedToWindow(View v) {
                        mDispatchAfterOnAttachedToWindow.run();
                    }

                    @Override
                    public void onViewDetachedFromWindow(View v) {
                        finish();
                    }
                });
    }
@@ -1131,10 +1151,10 @@ public class DreamService extends Service implements Window.Callback {
    /** @hide */
    protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.print(TAG + ": ");
        if (mWindowToken == null) {
        if (mFinished) {
            pw.println("stopped");
        } else {
            pw.println("running (token=" + mWindowToken + ")");
            pw.println("running (dreamToken=" + mDreamToken + ")");
        }
        pw.println("  window: " + mWindow);
        pw.print("  flags:");
@@ -1156,11 +1176,16 @@ public class DreamService extends Service implements Window.Callback {
        return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
    }

    private final class DreamServiceWrapper extends IDreamService.Stub {
    /**
     * The DreamServiceWrapper is used as a gateway to the system_server, where DreamController
     * uses it to control the DreamService. It is also used to receive callbacks from the
     * DreamActivity.
     */
    final class DreamServiceWrapper extends IDreamService.Stub {
        @Override
        public void attach(final IBinder windowToken, final boolean canDoze,
        public void attach(final IBinder dreamToken, final boolean canDoze,
                IRemoteCallback started) {
            mHandler.post(() -> DreamService.this.attach(windowToken, canDoze, started));
            mHandler.post(() -> DreamService.this.attach(dreamToken, canDoze, started));
        }

        @Override
@@ -1172,5 +1197,11 @@ public class DreamService extends Service implements Window.Callback {
        public void wakeUp() {
            mHandler.post(() -> DreamService.this.wakeUp(true /*fromSystem*/));
        }

        /** @hide */
        void onActivityCreated(DreamActivity a) {
            mActivity = a;
            onWindowCreated(a.getWindow());
        }
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -1612,6 +1612,7 @@
  <java-symbol type="style" name="TextAppearance.SlidingTabNormal" />
  <java-symbol type="style" name="Theme.DeviceDefault.Dialog.NoFrame" />
  <java-symbol type="style" name="Theme.IconMenu" />
  <java-symbol type="style" name="Theme.Dream" />
  <java-symbol type="style" name="Theme.DeviceDefault.VoiceInteractionSession" />
  <java-symbol type="style" name="Pointer" />
  <java-symbol type="style" name="LargePointer" />
+5 −0
Original line number Diff line number Diff line
@@ -701,6 +701,11 @@ please see themes_device_defaults.xml.
        <item name="windowNoDisplay">true</item>
    </style>

    <style name="Theme.Dream">
        <item name="windowBackground">@null</item>
        <item name="windowDisablePreview">true</item>
    </style>

    <!-- Default theme for dialog windows and activities (on API level 10 and lower),
         which is used by the
         {@link android.app.Dialog} class.  This changes the window to be
Loading