diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/IActivityTaskManager.aidl | 8 | ||||
| -rw-r--r-- | core/java/android/service/dreams/DreamActivity.java | 59 | ||||
| -rw-r--r-- | core/java/android/service/dreams/DreamService.java | 245 |
3 files changed, 205 insertions, 107 deletions
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 5b61402314c4..a55b2c832e41 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -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, diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java new file mode 100644 index 000000000000..8cdd24e0884d --- /dev/null +++ b/core/java/android/service/dreams/DreamActivity.java @@ -0,0 +1,59 @@ +/* + * 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); + } + } +} diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 96a89bcd0120..b31238ca25ca 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -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*/); - } catch (RemoteException ex) { - // system server died - } + try { + // 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,20 +993,12 @@ 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; - mCanDoze = false; - } + mDreamToken = null; + mCanDoze = false; } /** @@ -998,95 +1006,107 @@ public class DreamService extends Service implements Window.Callback { * * 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"); } - if (!mWindowless) { - mWindow = new PhoneWindow(this); - mWindow.setCallback(this); - 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 - | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0) - | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0) - ); - mWindow.setAttributes(lp); - // 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); + mDispatchAfterOnAttachedToWindow = () -> { + if (mWindow != null || mWindowless) { + mStarted = true; + try { + onDreamingStarted(); + } finally { + try { + started.sendResult(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + }; - applySystemUiVisibilityFlags( - (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0), - View.SYSTEM_UI_FLAG_LOW_PROFILE); + // 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) { + Intent i = new Intent(this, DreamActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + i.putExtra(DreamActivity.EXTRA_CALLBACK, mDreamServiceWrapper); 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; + 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(); } - // 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() { - @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(); - } + } + + private void onWindowCreated(Window w) { + mWindow = w; + mWindow.setCallback(this); + mWindow.setType(TYPE_DREAM); + mWindow.requestFeature(Window.FEATURE_NO_TITLE); + + WindowManager.LayoutParams lp = mWindow.getAttributes(); + 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) + ); + mWindow.setAttributes(lp); + // 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); + + applySystemUiVisibilityFlags( + (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0), + View.SYSTEM_UI_FLAG_LOW_PROFILE); + + mWindow.getDecorView().addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mDispatchAfterOnAttachedToWindow.run(); } - } - } - }); + + @Override + public void onViewDetachedFromWindow(View v) { + finish(); + } + }); } private boolean getWindowFlagValue(int flag, boolean defaultValue) { @@ -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()); + } } } |
