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

Commit ada31b5a authored by brycelee's avatar brycelee
Browse files

Protect against null mDreamToken.

A wakelock wrapped handler was added previously to help protect access
to mDreamToken. This led to an increase in binder calls due to the
wakelock acquisition. Even though this was adjusted to account for
operations already on the main thread, this is a deviation from the
original behavior execution proceeded on any thread. This change
restores the original behavior and instead now checks the nullability
of mDreamToken before using it.

Test: atest DreamServiceTest
Fixes: 408928049
Fixes: 408393208
Flag: EXEMPT bugfix
Change-Id: If7ea441b808a467c3f777f7ba4b36e319e96a411
parent f97b7136
Loading
Loading
Loading
Loading
+68 −154
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import static android.service.dreams.Flags.dreamHandlesConfirmKeys;
import static android.service.dreams.Flags.startAndStopDozingInBackground;

import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IdRes;
import android.annotation.IntDef;
@@ -255,6 +254,7 @@ public class DreamService extends Service implements Window.Callback {
            "android.service.dream.DreamService.dream_overlay_component";

    private final IDreamManager mDreamManager;
    private final Handler mHandler;
    private IBinder mDreamToken;
    private Window mWindow;
    private Activity mActivity;
@@ -323,89 +323,19 @@ public class DreamService extends Service implements Window.Callback {
        /** Returns the associated service info */
        ServiceInfo getServiceInfo();

        /** Returns the handler to be used for any posted operation */
        Handler getHandler();

        /** Returns the package manager */
        PackageManager getPackageManager();

        /** Returns the resources */
        Resources getResources();

        /** Returns a specialized handler to ensure Runnables are not suspended */
        WakefulHandler getWakefulHandler();
    }

    /**
     * {@link WakefulHandler} is an interface for defining an object that helps post work without
     * being interrupted by doze state.
     *
     * @hide
     */
    public interface WakefulHandler {
        /** Posts a {@link Runnable} to be ran on the underlying {@link Handler}. */
        void postIfNeeded(Runnable r);

        /**
         * Returns the underlying {@link Handler}. Should only be used for passing the handler into
         * a function and not for directly calling methods on it.
         */
        Handler getHandler();
    }

    /**
     * {@link WakefulHandlerImpl} ensures work on a handler is not suspended by wrapping the call
     * with a partial wakelock. Note that this is only needed for Doze DreamService implementations.
     * In this case, the component should have wake lock permissions. When such permission is not
     * available, this class behaves like an ordinary handler.
     */
    private static final class WakefulHandlerImpl implements WakefulHandler {
        private static final String SERVICE_HANDLER_WAKE_LOCK_TAG = "dream:service:handler";
        private Context mContext;
        private Handler mHandler;

        private PowerManager.WakeLock mWakeLock;

        private PowerManager.WakeLock getWakeLock() {
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.WAKE_LOCK)
                    != PERMISSION_GRANTED) {
                return null;
            }

            final PowerManager powerManager = mContext.getSystemService(PowerManager.class);

            if (powerManager == null) {
                return null;
            }

            return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    SERVICE_HANDLER_WAKE_LOCK_TAG);
        }

        WakefulHandlerImpl(Context context) {
            mContext = context;
            mHandler = new Handler(Looper.getMainLooper());
            mWakeLock = getWakeLock();
        }

        @Override
        public void postIfNeeded(Runnable r) {
            if (mHandler.getLooper().isCurrentThread()) {
                r.run();
            } else if (mWakeLock != null) {
                mHandler.post(mWakeLock.wrap(r));
            } else {
                mHandler.post(r);
            }
        }

        @Override
        public Handler getHandler() {
            return mHandler;
        }
    }

    private static final class DefaultInjector implements Injector {
        private Context mContext;
        private Class<?> mClassName;
        private WakefulHandler mWakefulHandler;

        public void init(Context context) {
            mContext = context;
@@ -449,14 +379,8 @@ public class DreamService extends Service implements Window.Callback {
        }

        @Override
        public WakefulHandler getWakefulHandler() {
            synchronized (this) {
                if (mWakefulHandler == null) {
                    mWakefulHandler = new WakefulHandlerImpl(mContext);
                }
            }

            return mWakefulHandler;
        public Handler getHandler() {
            return new Handler(Looper.getMainLooper());
        }

        @Override
@@ -485,6 +409,7 @@ public class DreamService extends Service implements Window.Callback {
        mInjector = injector;
        mInjector.init(this);
        mDreamManager = mInjector.getDreamManager();
        mHandler = mInjector.getHandler();
    }

    /**
@@ -527,7 +452,7 @@ public class DreamService extends Service implements Window.Callback {

                        // Simply wake up in the case the device is not locked.
                        if (!keyguardManager.isKeyguardLocked()) {
                            wakeUp(false);
                            wakeUp();
                            return true;
                        }

@@ -549,11 +474,11 @@ public class DreamService extends Service implements Window.Callback {

        if (!mInteractive) {
            if (mDebug) Slog.v(mTag, "Waking up on keyEvent");
            wakeUp(false);
            wakeUp();
            return true;
        } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            if (mDebug) Slog.v(mTag, "Waking up on back key");
            wakeUp(false);
            wakeUp();
            return true;
        }
        return mWindow.superDispatchKeyEvent(event);
@@ -564,7 +489,7 @@ public class DreamService extends Service implements Window.Callback {
    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
        if (!mInteractive) {
            if (mDebug) Slog.v(mTag, "Waking up on keyShortcutEvent");
            wakeUp(false);
            wakeUp();
            return true;
        }
        return mWindow.superDispatchKeyShortcutEvent(event);
@@ -577,7 +502,7 @@ public class DreamService extends Service implements Window.Callback {
        // but finish()es on any other kind of activity
        if (!mInteractive && event.getActionMasked() == MotionEvent.ACTION_UP) {
            if (mDebug) Slog.v(mTag, "Waking up on touchEvent");
            wakeUp(false);
            wakeUp();
            return true;
        }
        return mWindow.superDispatchTouchEvent(event);
@@ -588,7 +513,7 @@ public class DreamService extends Service implements Window.Callback {
    public boolean dispatchTrackballEvent(MotionEvent event) {
        if (!mInteractive) {
            if (mDebug) Slog.v(mTag, "Waking up on trackballEvent");
            wakeUp(false);
            wakeUp();
            return true;
        }
        return mWindow.superDispatchTrackballEvent(event);
@@ -599,7 +524,7 @@ public class DreamService extends Service implements Window.Callback {
    public boolean dispatchGenericMotionEvent(MotionEvent event) {
        if (!mInteractive) {
            if (mDebug) Slog.v(mTag, "Waking up on genericMotionEvent");
            wakeUp(false);
            wakeUp();
            return true;
        }
        return mWindow.superDispatchGenericMotionEvent(event);
@@ -997,18 +922,10 @@ public class DreamService extends Service implements Window.Callback {
        }
    }

    private void postIfNeeded(Runnable runnable) {
        // The handler is based on the populated context is not ready at construction time.
        // therefore we fetch on demand.
        mInjector.getWakefulHandler().postIfNeeded(runnable);
    }
    private synchronized void updateDoze() {
        final IBinder dreamToken = mDreamToken;

    /**
     * Updates doze state. Note that this must be called on the mHandler.
     */
    private void updateDoze() {
        postIfNeeded(() -> {
            if (mDreamToken == null) {
        if (dreamToken == null) {
            Slog.w(mTag, "Updating doze without a dream token.");
            return;
        }
@@ -1020,12 +937,12 @@ public class DreamService extends Service implements Window.Callback {
                        + " mDozeScreenBrightnessFloat=" + mDozeScreenBrightnessFloat);
                if (startAndStopDozingInBackground()) {
                    mDreamManager.startDozingOneway(
                                mDreamToken, mDozeScreenState, mDozeScreenStateReason,
                            dreamToken, mDozeScreenState, mDozeScreenStateReason,
                            mDozeScreenBrightnessFloat, mDozeScreenBrightness,
                            mUseNormalBrightnessForDoze);
                } else {
                    mDreamManager.startDozing(
                                mDreamToken, mDozeScreenState, mDozeScreenStateReason,
                            dreamToken, mDozeScreenState, mDozeScreenStateReason,
                            mDozeScreenBrightnessFloat, mDozeScreenBrightness,
                            mUseNormalBrightnessForDoze);
                }
@@ -1033,7 +950,6 @@ public class DreamService extends Service implements Window.Callback {
                // system server died
            }
        }
        });
    }

    /**
@@ -1049,20 +965,18 @@ public class DreamService extends Service implements Window.Callback {
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    public void stopDozing() {
        postIfNeeded(() -> {
            if (mDreamToken == null) {
                return;
            }

        if (mDozing) {
            mDozing = false;
            try {
                    mDreamManager.stopDozing(mDreamToken);
                final IBinder dreamToken = mDreamToken;

                if (dreamToken != null) {
                    mDreamManager.stopDozing(dreamToken);
                }
            } catch (RemoteException ex) {
                // system server died
            }
        }
        });
    }

    /**
@@ -1292,7 +1206,7 @@ public class DreamService extends Service implements Window.Callback {
                final long token = Binder.clearCallingIdentity();
                try {
                    // Simply finish dream when exit is requested.
                    postIfNeeded(() -> finishInternal());
                    mHandler.post(() -> finish());
                } finally {
                    Binder.restoreCallingIdentity(token);
                }
@@ -1398,13 +1312,9 @@ public class DreamService extends Service implements Window.Callback {
     * </p>
     */
    public final void finish() {
        postIfNeeded(this::finishInternal);
    }

    private void finishInternal() {
        // If there is an active overlay connection, signal that the dream is ending before
        // continuing. Note that the overlay cannot rely on the unbound state, since another
        // dream might have bound to it in the meantime.
        // continuing. Note that the overlay cannot rely on the unbound state, since another dream
        // might have bound to it in the meantime.
        if (mOverlayConnection != null) {
            mOverlayConnection.addConsumer(overlay -> {
                try {
@@ -1433,7 +1343,8 @@ public class DreamService extends Service implements Window.Callback {
        }
        mFinished = true;

        if (mDreamToken == null) {
        final IBinder dreamToken = mDreamToken;
        if (dreamToken == null) {
            if (mDebug) Slog.v(mTag, "finish() called when not attached.");
            stopSelf();
            return;
@@ -1443,9 +1354,9 @@ public class DreamService extends Service implements Window.Callback {
            // finishSelf will unbind the dream controller from the dream service. This will
            // trigger DreamService.this.onDestroy and DreamService.this will die.
            if (startAndStopDozingInBackground()) {
                mDreamManager.finishSelfOneway(mDreamToken, true /*immediate*/);
                mDreamManager.finishSelfOneway(dreamToken, true /*immediate*/);
            } else {
                mDreamManager.finishSelf(mDreamToken, true /*immediate*/);
                mDreamManager.finishSelf(dreamToken, true /*immediate*/);
            }
        } catch (RemoteException ex) {
            // system server died
@@ -1460,7 +1371,7 @@ public class DreamService extends Service implements Window.Callback {
     * </p>
     */
    public final void wakeUp() {
        postIfNeeded(()-> wakeUp(false));
        wakeUp(false);
    }

    /**
@@ -1532,10 +1443,14 @@ public class DreamService extends Service implements Window.Callback {
                    Slog.w(mTag, "WakeUp was called before the dream was attached.");
                } else {
                    try {
                        final IBinder dreamToken = mDreamToken;

                        if (dreamToken != null) {
                            if (startAndStopDozingInBackground()) {
                            mDreamManager.finishSelfOneway(mDreamToken, false /*immediate*/);
                                mDreamManager.finishSelfOneway(dreamToken, false /*immediate*/);
                            } else {
                            mDreamManager.finishSelf(mDreamToken, false /*immediate*/);
                                mDreamManager.finishSelf(dreamToken, false /*immediate*/);
                            }
                        }
                    } catch (RemoteException ex) {
                        // system server died
@@ -1662,7 +1577,7 @@ public class DreamService extends Service implements Window.Callback {
        if (mActivity != null && !mActivity.isFinishing()) {
            mActivity.finishAndRemoveTask();
        } else {
            finishInternal();
            finish();
        }

        mDreamToken = null;
@@ -1736,7 +1651,7 @@ public class DreamService extends Service implements Window.Callback {
            i.setPackage(mInjector.getDreamPackageName());
            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
            DreamActivity.setCallback(i,
                    new DreamActivityCallbacks(mDreamToken, new WeakReference<>(this)));
                    new DreamActivityCallbacks(dreamToken, new WeakReference<>(this)));
            final ServiceInfo serviceInfo = mInjector.getServiceInfo();
            final CharSequence title = fetchDreamLabel(mInjector.getPackageManager(),
                    mInjector.getResources(), serviceInfo, isPreviewMode);
@@ -1822,7 +1737,7 @@ public class DreamService extends Service implements Window.Callback {
                            // the window reference in order to fully release the DreamActivity.
                            mWindow = null;
                            mActivity = null;
                            finishInternal();
                            finish();
                        }

                        if (mOverlayConnection != null && mDreamStartOverlayConsumer != null) {
@@ -1909,8 +1824,7 @@ public class DreamService extends Service implements Window.Callback {

    @Override
    protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
        DumpUtils.dumpAsync(mInjector.getWakefulHandler().getHandler(),
                (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
        DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> dumpOnHandler(fd, pw1, args), pw, "", 1000);
    }

    /** @hide */
@@ -1966,7 +1880,7 @@ public class DreamService extends Service implements Window.Callback {
                return;
            }

            service.postIfNeeded(() -> consumer.accept(service));
            service.mHandler.post(() -> consumer.accept(service));
        }

        @Override
+8 −12
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
@@ -175,9 +176,7 @@ public class TestDreamEnvironment {
        @Mock
        private ServiceInfo mServiceInfo;

        @Mock
        private DreamService.WakefulHandler mWakefulHandler;

        private final Handler mHandler;
        private final IDreamManager mDreamManager;
        private final DreamOverlayConnectionHandler mDreamOverlayConnectionHandler;

@@ -186,6 +185,7 @@ public class TestDreamEnvironment {
                DreamOverlayConnectionHandler dreamOverlayConnectionHandler,
                boolean shouldShowComplications) {
            MockitoAnnotations.initMocks(this);
            mHandler = new Handler(looper);
            mDreamManager = dreamManager;
            mDreamOverlayConnectionHandler = dreamOverlayConnectionHandler;
            mServiceInfo.packageName = FAKE_DREAM_PACKAGE_NAME;
@@ -198,10 +198,6 @@ public class TestDreamEnvironment {
                    .thenReturn(FAKE_DREAM_SETTINGS_ACTIVITY);
            when(mPackageManager.extractPackageItemInfoAttributes(any(), any(), any(), any()))
                    .thenReturn(mAttributes);
            doAnswer(invocation -> {
                ((Runnable) invocation.getArgument(0)).run();
                return null;
            }).when(mWakefulHandler).postIfNeeded(any());
        }
        @Override
        public void init(Context context) {
@@ -238,6 +234,11 @@ public class TestDreamEnvironment {
            return mServiceInfo;
        }

        @Override
        public Handler getHandler() {
            return mHandler;
        }

        @Override
        public PackageManager getPackageManager() {
            return mPackageManager;
@@ -247,11 +248,6 @@ public class TestDreamEnvironment {
        public Resources getResources() {
            return mResources;
        }

        @Override
        public DreamService.WakefulHandler getWakefulHandler() {
            return mWakefulHandler;
        }
    }

    @Mock