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

Commit 7d276c37 authored by Daniel Sandler's avatar Daniel Sandler
Browse files

New Android Dreams architecture, disabled for now.

Rather than normal Activities (which have a host of problems
when used for this purpose), screen savers are now a
special kind of Service that can add views to its own
special window (TYPE_DREAM, in the SCREENSAVER layer).

Dreams are now launched by the power manager; whenever it is
about to turn the screen off, it asks the window manager if
it wants to run a screen saver instead. (http://b/5677408)

Also, the new config_enableDreams bool allows the entire
feature to be switched on or off in one place. It is
currently switched off (and the APIs are all @hidden).

Change-Id: Idfe9d430568471d15f4b463cb70586a899a331f7
parent 63c115c4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -132,6 +132,8 @@ LOCAL_SRC_FILES += \
	core/java/android/os/IRemoteCallback.aidl \
	core/java/android/os/IUpdateLock.aidl \
	core/java/android/os/IVibratorService.aidl \
	core/java/android/service/dreams/IDreamManager.aidl \
	core/java/android/service/dreams/IDreamService.aidl \
	core/java/android/service/wallpaper/IWallpaperConnection.aidl \
	core/java/android/service/wallpaper/IWallpaperEngine.aidl \
	core/java/android/service/wallpaper/IWallpaperService.aidl \
+392 −0
Original line number Diff line number Diff line
/**
 * 
 */
package android.service.dreams;

import com.android.internal.policy.PolicyManager;

import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import android.view.ActionMode;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;

/**
 * @hide
 *
 */
public class Dream extends Service implements Window.Callback {
    private final static boolean DEBUG = true;
    private final static String TAG = "Dream";
    
    /**
     * The {@link Intent} that must be declared as handled by the service.
     * To be supported, the service must also require the
     * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
     * that other applications can not abuse it.
     */
    @SdkConstant(SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_INTERFACE =
            "android.service.dreams.Dream";

    private Window mWindow;

    private WindowManager mWindowManager;
    private IDreamManager mSandman;
    
    private boolean mInteractive;
    
    final Handler mHandler = new Handler();
    
    boolean mFinished = false;
    
    // begin Window.Callback methods
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (!mInteractive) { 
            finish();
            return true;
        }
        return mWindow.superDispatchKeyEvent(event);
    }

    @Override
    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
        if (!mInteractive) { 
            finish();
            return true;
        }
        return mWindow.superDispatchKeyShortcutEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (!mInteractive) { 
            finish();
            return true;
        }
        return mWindow.superDispatchTouchEvent(event);
    }

    @Override
    public boolean dispatchTrackballEvent(MotionEvent event) {
        if (!mInteractive) { 
            finish();
            return true;
        }
        return mWindow.superDispatchTrackballEvent(event);
    }

    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent event) {
        if (!mInteractive) { 
            finish();
            return true;
        }
        return mWindow.superDispatchGenericMotionEvent(event);
    }

    @Override
    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
        return false;
    }

    @Override
    public View onCreatePanelView(int featureId) {
        return null;
    }

    @Override
    public boolean onCreatePanelMenu(int featureId, Menu menu) {
        return false;
    }

    @Override
    public boolean onPreparePanel(int featureId, View view, Menu menu) {
        return false;
    }

    @Override
    public boolean onMenuOpened(int featureId, Menu menu) {
        return false;
    }

    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        return false;
    }

    @Override
    public void onWindowAttributesChanged(LayoutParams attrs) {

    }

    @Override
    public void onContentChanged() {

    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {

    }

    @Override
    public void onAttachedToWindow() {
        mWindow.addFlags(
                WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
        );
        lightsOut();
    }

    @Override
    public void onDetachedFromWindow() {
    }

    @Override
    public void onPanelClosed(int featureId, Menu menu) {
    }

    @Override
    public boolean onSearchRequested() {
        return false;
    }

    @Override
    public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) {
        return null;
    }

    @Override
    public void onActionModeStarted(ActionMode mode) {
    }

    @Override
    public void onActionModeFinished(ActionMode mode) {
    }
    // end Window.Callback methods

    public WindowManager getWindowManager() {
        return mWindowManager;
    }

    public Window getWindow() {
        return mWindow;
    }
    
    /**
     * Called when this Dream is constructed. Place your initialization here.
     * 
     * Subclasses must call through to the superclass implementation.
     */
    @Override
    public void onCreate() {
        super.onCreate();

        if (DEBUG) Slog.v(TAG, "Dream created on thread " + Thread.currentThread().getId());

        mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams"));
    }
    
    /**
     * Called when this Dream is started. Place your initialization here.
     * 
     * Subclasses must call through to the superclass implementation.
     * 
     * XXX(dsandler) Might want to make this final and have a different method for clients to override 
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    
   /**
     * Inflate a layout resource and set it to be the content view for this Dream.
     * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
     *
     * @param layoutResID Resource ID to be inflated.
     * 
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
    }

    /**
     * Set a view to be the content view for this Dream.
     * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)},
     * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view.
     * 
     * @param view The desired content to display.
     *
     * @see #setContentView(int)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(View view) {
        getWindow().setContentView(view);
    }

    /**
     * Set a view to be the content view for this Dream.
     * Behaves similarly to 
     * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}.
     *
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(int)
     */
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
    }

    /**
     * Add a view to the Dream's window, leaving other content views in place.
     * 
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     */
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().addContentView(view, params);
    }
    
    /**
     * @param mInteractive the mInteractive to set
     */
    public void setInteractive(boolean mInteractive) {
        this.mInteractive = mInteractive;
    }

    /**
     * @return the mInteractive
     */
    public boolean isInteractive() {
        return mInteractive;
    }
    
    /** Convenience method for setting View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. */
    protected void lightsOut() {
        // turn the lights down low
        final View v = mWindow.getDecorView();
        if (v != null) {
            v.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
        }
    }

    /**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onCreate}.
     *
     * @return The view if found or null otherwise.
     */
    public View findViewById(int id) {
        return getWindow().findViewById(id);
    }
    
    /**
     * Called when this Dream is being removed from the screen and stopped.
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        mWindowManager.removeView(mWindow.getDecorView());
    }

    /**
     * Creates a new dream window, attaches the current content view, and shows it.
     * 
     * @param windowToken Binder to attach to the window to allow access to the correct window type.
     * @hide
     */
    final /*package*/ void attach(IBinder windowToken) {
        if (DEBUG) Slog.v(TAG, "Dream attached on thread " + Thread.currentThread().getId());
        
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));

        if (DEBUG) Slog.v(TAG, "attaching window token: " + windowToken 
                + " to window of type " + 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;
        
        //WindowManagerImpl.getDefault().addView(mWindow.getDecorView(), lp);
        
        if (DEBUG) Slog.v(TAG, "created and attached window: " + mWindow);

        mWindow.setWindowManager(null, windowToken, "dream", true);
        mWindowManager = mWindow.getWindowManager();
        
        // now make it visible
        mHandler.post(new Runnable(){
            @Override
            public void run() {
                if (DEBUG) Slog.v(TAG, "Dream window added on thread " + Thread.currentThread().getId());
                
                getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
            }});        
    }
    
    /**
     * Stop the dream and wake up.
     * 
     * After this method is called, the service will be stopped.
     */
    public void finish() {
        if (mFinished) return;
        try {
            mSandman.awaken(); // assuming we were started by the DreamManager
            stopSelf(); // if launched via any other means
            mFinished = true;
        } catch (RemoteException ex) {
            // sigh
        }
    }

    class IDreamServiceWrapper extends IDreamService.Stub {
        public IDreamServiceWrapper() {
        }

        public void attach(IBinder windowToken) {
            Dream.this.attach(windowToken);
        }
    }

    /**
     * Implement to return the implementation of the internal accessibility
     * service interface.  Subclasses should not override.
     */
    @Override
    public final IBinder onBind(Intent intent) {
        return new IDreamServiceWrapper();
    }
}
+182 −0
Original line number Diff line number Diff line
package android.service.dreams;

import static android.provider.Settings.Secure.SCREENSAVER_COMPONENT;

import java.io.FileDescriptor;
import java.io.PrintWriter;

import com.android.internal.view.IInputMethod;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import android.view.IWindowManager;
import android.view.WindowManager;

/**
 * 
 * @hide
 *
 */

public class DreamManagerService 
        extends IDreamManager.Stub 
        implements ServiceConnection
{
    private static final boolean DEBUG = true;
    private static final String TAG = "DreamManagerService";
    
    final Object mLock = new Object[0];

    private Context mContext;
    private IWindowManager mIWindowManager;
    
    private ComponentName mCurrentDreamComponent;
    private IDreamService mCurrentDream;
    private Binder mCurrentDreamToken; 

    public DreamManagerService(Context context) {
        if (DEBUG) Slog.v(TAG, "DreamManagerService startup");
        mContext = context;
        mIWindowManager = IWindowManager.Stub.asInterface(
                ServiceManager.getService(Context.WINDOW_SERVICE));
    }

    private void checkPermission(String permission) {
        if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) {
            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
                    + ", must have permission " + permission);
        }
    }

    // IDreamManager method
    public void dream() {
        ComponentName name = getDreamComponent();
        if (name != null) {
            synchronized (mLock) {
                final long ident = Binder.clearCallingIdentity();
                try {
                    bindDreamComponentL(name, false);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }
    }

    // IDreamManager method
    public void setDreamComponent(ComponentName name) {
        Settings.Secure.putString(mContext.getContentResolver(), SCREENSAVER_COMPONENT, name.flattenToString());
    }
    
    // IDreamManager method
    public ComponentName getDreamComponent() {
        // TODO(dsandler) don't load this every time, watch the value  
        String component = Settings.Secure.getString(mContext.getContentResolver(), SCREENSAVER_COMPONENT);
        if (component == null) {
            component = mContext.getResources().getString(
                com.android.internal.R.string.config_defaultDreamComponent);
        }
        if (component != null) {
            return ComponentName.unflattenFromString(component);
        } else {
            return null;
        }
    }
    
    // IDreamManager method
    public void testDream(ComponentName name) {
        if (DEBUG) Slog.v(TAG, "startDream name=" + name
                + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
//        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
        synchronized (mLock) {
            final long ident = Binder.clearCallingIdentity();
            try {
                bindDreamComponentL(name, true);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

    // IDreamManager method
    public void awaken() {
        if (DEBUG) Slog.v(TAG, "awaken()");
        synchronized (mLock) {
            if (mCurrentDream != null) {
                mContext.unbindService(this);
            }
        }
    }

    public void bindDreamComponentL(ComponentName componentName, boolean test) {
        if (DEBUG) Slog.v(TAG, "bindDreamComponent: componentName=" + componentName
                + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());

        Intent intent = new Intent(Intent.ACTION_MAIN)
            .setComponent(componentName)
            .addFlags(
                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                )
            .putExtra("android.dreams.TEST", test);
        
        if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
            Slog.w(TAG, "unable to bind service: " + componentName);
            return;
        }
        mCurrentDreamComponent = componentName;
        mCurrentDreamToken = new Binder();
        try {
            if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken 
                    + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM);
            mIWindowManager.addWindowToken(mCurrentDreamToken,
                    WindowManager.LayoutParams.TYPE_DREAM);
        } catch (RemoteException e) {
            Slog.w(TAG, "Unable to add window token. Proceed at your own risk.");
        }
        
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (DEBUG) Slog.v(TAG, "connected to dream: " + name + " binder=" + service + " thread=" + Thread.currentThread().getId());

        mCurrentDream = IDreamService.Stub.asInterface(service);
        try {
            if (DEBUG) Slog.v(TAG, "attaching with token:" + mCurrentDreamToken);
            mCurrentDream.attach(mCurrentDreamToken);
        } catch (RemoteException ex) {
            Slog.w(TAG, "Unable to send window token to dream:" + ex);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        if (DEBUG) Slog.v(TAG, "disconnected: " + name + " service: " + mCurrentDream);
        mCurrentDream = null;
        mCurrentDreamToken = null;
    }
    
    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Dreamland:");
        pw.print("  component="); pw.println(mCurrentDreamComponent);
        pw.print("  token="); pw.println(mCurrentDreamToken);
        pw.print("  dream="); pw.println(mCurrentDream);
    }

    public void systemReady() {
        if (DEBUG) Slog.v(TAG, "ready to dream!");
    }

}
+30 −0
Original line number Diff line number Diff line
/**
 * Copyright (c) 2012, 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.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.ComponentName;

/** @hide */
interface IDreamManager {
    void dream();
    void awaken();
    void setDreamComponent(in ComponentName componentName);
    ComponentName getDreamComponent();
    void testDream(in ComponentName componentName);
}
 No newline at end of file
+24 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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;

/**
 * @hide
 */
oneway interface IDreamService {
    void attach(IBinder windowToken);
}
Loading