IDreamOverlay Introduction.

This changelist introduces IDreamOverlay, an interface
for rendering an overlay on top of a Dream. IDreamOverlay
implementations can register with DreamManagerService to
be bound to by DreamService when dream commences. The
overlay will be passed the Dream Window information so
that it may render within this window space.

Bug: 201676854
Test: atest DreamOverlayTest
Change-Id: I382ba2acb4a6922f19250753f8f0afc4181a6454
This commit is contained in:
Bryce Lee
2021-09-30 15:57:26 -07:00
parent 22f575a036
commit 87a5024ef5
6 changed files with 179 additions and 2 deletions

View File

@@ -26,7 +26,10 @@ import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -58,6 +61,8 @@ import com.android.internal.util.DumpUtils.Dump;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.function.Consumer;
/**
* Extend this class to implement a custom dream (available to the user as a "Daydream").
@@ -169,6 +174,13 @@ public class DreamService extends Service implements Window.Callback {
public static final String SERVICE_INTERFACE =
"android.service.dreams.DreamService";
/**
* The name of the extra where the dream overlay component is stored.
* @hide
*/
public static final String EXTRA_DREAM_OVERLAY_COMPONENT =
"android.service.dream.DreamService.dream_overlay_component";
/**
* Name under which a Dream publishes information about itself.
* This meta-data must reference an XML resource containing
@@ -191,6 +203,7 @@ public class DreamService extends Service implements Window.Callback {
private boolean mCanDoze;
private boolean mDozing;
private boolean mWindowless;
private boolean mOverlayServiceBound;
private int mDozeScreenState = Display.STATE_UNKNOWN;
private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
@@ -199,8 +212,62 @@ public class DreamService extends Service implements Window.Callback {
private DreamServiceWrapper mDreamServiceWrapper;
private Runnable mDispatchAfterOnAttachedToWindow;
private OverlayConnection mOverlayConnection;
private static class OverlayConnection implements ServiceConnection {
// Overlay set during onBind.
private IDreamOverlay mOverlay;
// A Queue of pending requests to execute on the overlay.
private ArrayDeque<Consumer<IDreamOverlay>> mRequests;
OverlayConnection() {
mRequests = new ArrayDeque<>();
}
public void request(Consumer<IDreamOverlay> request) {
mRequests.push(request);
evaluate();
}
private void evaluate() {
if (mOverlay == null) {
return;
}
// Any new requests that arrive during this loop will be processed synchronously after
// the loop exits.
while (!mRequests.isEmpty()) {
final Consumer<IDreamOverlay> request = mRequests.pop();
request.accept(mOverlay);
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Store Overlay and execute pending requests.
mOverlay = IDreamOverlay.Stub.asInterface(service);
evaluate();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// Clear Overlay binder to prevent further request processing.
mOverlay = null;
}
}
private IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() {
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
finish();
}
};
public DreamService() {
mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
mOverlayConnection = new OverlayConnection();
}
/**
@@ -861,6 +928,18 @@ public class DreamService extends Service implements Window.Callback {
public final IBinder onBind(Intent intent) {
if (mDebug) Slog.v(TAG, "onBind() intent = " + intent);
mDreamServiceWrapper = new DreamServiceWrapper();
// Connect to the overlay service if present.
final ComponentName overlayComponent =
intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT);
if (overlayComponent != null && !mWindowless) {
final Intent overlayIntent = new Intent();
overlayIntent.setComponent(overlayComponent);
mOverlayServiceBound = getApplicationContext().bindService(overlayIntent,
mOverlayConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
}
return mDreamServiceWrapper;
}
@@ -894,6 +973,11 @@ public class DreamService extends Service implements Window.Callback {
return;
}
if (!mWindowless && mOverlayServiceBound) {
unbindService(mOverlayConnection);
mOverlayServiceBound = false;
}
try {
// finishSelf will unbind the dream controller from the dream service. This will
// trigger DreamService.this.onDestroy and DreamService.this will die.
@@ -1101,6 +1185,16 @@ public class DreamService extends Service implements Window.Callback {
}
}
});
// Request the DreamOverlay be told to dream with dream's window parameters once the service
// has connected.
mOverlayConnection.request(overlay -> {
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
} catch (RemoteException e) {
Log.e(TAG, "could not send window attributes:" + e);
}
});
}
private boolean getWindowFlagValue(int flag, boolean defaultValue) {

View File

@@ -41,4 +41,5 @@ interface IDreamManager {
void forceAmbientDisplayEnabled(boolean enabled);
ComponentName[] getDreamComponentsForUser(int userId);
void setDreamComponentsForUser(int userId, in ComponentName[] componentNames);
void registerDreamOverlayService(in ComponentName componentName);
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2021, 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.service.dreams.IDreamOverlayCallback;
import android.view.WindowManager.LayoutParams;
/**
* {@link IDreamOverlay} provides a way for a component to annotate a dream with additional view
* elements. Registered through the DreamManager, a IDreamOverlay is bound to by the dream and
* passed the necessary window details to participate in the user interface.
* @hide
*/
interface IDreamOverlay {
/**
* @param params The {@link LayoutParams} for the associated DreamWindow, including the window
token of the Dream Activity.
* @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
* dream.
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback);
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2021, 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;
/**
* {@link IDreamOverlayCallback} defines the interactions a dream overlay can have with its
* associated dream. It is the discretion of the {@link DreamService}) to honor any inbound requests
* from this callback.
*
* @hide
*/
interface IDreamOverlayCallback {
/**
* Invoked to request the dream exit.
*/
void onExitRequested();
}

View File

@@ -117,7 +117,8 @@ final class DreamController {
}
public void startDream(Binder token, ComponentName name,
boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock,
ComponentName overlayComponentName) {
stopDream(true /*immediate*/, "starting new dream");
Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
@@ -138,6 +139,7 @@ final class DreamController {
Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
intent.setComponent(name);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(DreamService.EXTRA_DREAM_OVERLAY_COMPONENT, overlayComponentName);
try {
if (!mContext.bindServiceAsUser(intent, mCurrentDream,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,

View File

@@ -95,6 +95,8 @@ public final class DreamManagerService extends SystemService {
private int mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
private int mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
private ComponentName mDreamOverlayServiceName;
private AmbientDisplayConfiguration mDozeConfig;
@VisibleForTesting
@@ -421,7 +423,8 @@ public final class DreamManagerService extends SystemService {
if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
mUiEventLogger.log(DreamManagerEvent.DREAM_START);
}
mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock);
mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock,
mDreamOverlayServiceName);
}));
}
@@ -591,6 +594,15 @@ public final class DreamManagerService extends SystemService {
}
}
@Override // Binder call
public void registerDreamOverlayService(ComponentName overlayComponent) {
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
// Store the overlay service component so that it can be passed to the dream when it is
// invoked.
mDreamOverlayServiceName = overlayComponent;
}
@Override // Binder call
public ComponentName getDefaultDreamComponentForUser(int userId) {
checkPermission(android.Manifest.permission.READ_DREAM_STATE);