diff options
Diffstat (limited to 'sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java')
-rw-r--r-- | sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java new file mode 100644 index 0000000..4c69a0a --- /dev/null +++ b/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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 cyanogenmod.externalviews; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; +import android.view.ActionMode; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import com.android.internal.policy.PhoneWindow; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * A class for providing a view that can be displayed within the lock screen. Applications that + * wish to provide a view to be displayed within the lock screen should extend this service. + * + * <p>Applications extending this class should include the + * {@link cyanogenmod.platform.Manifest.permission#THIRD_PARTY_KEYGUARD} permission in their + * manifest</p> + + * <p>Applications extending this class should also extend + * {@link KeyguardExternalViewProviderService.Provider} and return a new instance of + * {@link KeyguardExternalViewProviderService.Provider} in + * {@link KeyguardExternalViewProviderService#createExternalView(Bundle)}.</p> + */ +public abstract class KeyguardExternalViewProviderService extends Service { + + private static final String TAG = KeyguardExternalViewProviderService.class.getSimpleName(); + private static final boolean DEBUG = false; + + /** + * The action that must be declared as handled by this service. + * + * <p>{@code + * <intent-filter> + * <action android:name="cyanogenmod.externalviews.KeyguardExternalViewProviderService"/> + * </intent-filter> + *}</p> + */ + public static final String SERVICE_INTERFACE = + "cyanogenmod.externalviews.KeyguardExternalViewProviderService"; + + /** + * Name under which an external keyguard view publishes information about itself. + * This meta-data must reference an XML resource containing + * a <code><lockscreen></code> + * tag. + */ + public static final String META_DATA = "cyanogenmod.externalviews.keyguard"; + + private WindowManager mWindowManager; + private final Handler mHandler = new Handler(); + + @Override + public void onCreate() { + super.onCreate(); + + mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); + return START_NOT_STICKY; + } + + @Override + public final IBinder onBind(Intent intent) { + return new IExternalViewProviderFactory.Stub() { + @Override public IBinder createExternalView(final Bundle options) { + FutureTask<IBinder> c = new FutureTask<IBinder>(new Callable<IBinder>() { + @Override + public IBinder call() throws Exception { + return KeyguardExternalViewProviderService.this + .createExternalView(options).mImpl; + } + }); + mHandler.post(c); + try { + return c.get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "error: ", e); + return null; + } + } + }; + } + + /** + * Called when the host has bound to this service. + * @param options Optional bundle. This param is currently not used. + * @return The newly created provider. + */ + protected abstract Provider createExternalView(Bundle options); + + /** + * This class provides an interface for the host and service to communicate to each other. + */ + protected abstract class Provider { + private final class ProviderImpl extends IKeyguardExternalViewProvider.Stub + implements Window.Callback { + private final Window mWindow; + private final WindowManager.LayoutParams mParams; + + private boolean mShouldShow = true; + private boolean mAskedShow = false; + + private final RemoteCallbackList<IKeyguardExternalViewCallbacks> mCallbacks = + new RemoteCallbackList<IKeyguardExternalViewCallbacks>(); + + public ProviderImpl(Provider provider) { + mWindow = new PhoneWindow(KeyguardExternalViewProviderService.this); + mWindow.setCallback(this); + ((ViewGroup) mWindow.getDecorView()).addView(onCreateView()); + + mParams = new WindowManager.LayoutParams(); + mParams.type = provider.getWindowType(); + mParams.flags = provider.getWindowFlags(); + mParams.gravity = Gravity.LEFT | Gravity.TOP; + mParams.format = PixelFormat.TRANSPARENT; + } + + @Override + public void onAttach(IBinder windowToken) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mWindowManager.addView(mWindow.getDecorView(), mParams); + Provider.this.onAttach(); + } + }); + } + + @Override + public void onDetach() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mWindowManager.removeView(mWindow.getDecorView()); + Provider.this.onDetach(); + } + }); + } + + @Override + public void onKeyguardShowing(final boolean screenOn) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onKeyguardShowing(screenOn); + } + }); + } + + @Override + public void onKeyguardDismissed() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onKeyguardDismissed(); + } + }); + } + + @Override + public void onBouncerShowing(final boolean showing) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onBouncerShowing(showing); + } + }); + } + + @Override + public void onScreenTurnedOn() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onScreenTurnedOn(); + } + }); + } + + @Override + public void onScreenTurnedOff() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onScreenTurnedOff(); + } + }); + } + + @Override + public void onLockscreenSlideOffsetChanged(final float swipeProgress) + throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onLockscreenSlideOffsetChanged(swipeProgress); + } + }); + } + + @Override + public void alterWindow(final int x, final int y, final int width, final int height, + final boolean visible, final Rect clipRect) { + mHandler.post(new Runnable() { + @Override + public void run() { + mParams.x = x; + mParams.y = y; + mParams.width = width; + mParams.height = height; + + if (DEBUG) Log.d(TAG, mParams.toString()); + + mAskedShow = visible; + + updateVisibility(); + + View decorView = mWindow.getDecorView(); + if (decorView.getVisibility() == View.VISIBLE) { + decorView.setClipBounds(clipRect); + } + + if (mWindow.getDecorView().getVisibility() != View.GONE) + mWindowManager.updateViewLayout(mWindow.getDecorView(), mParams); + } + }); + } + + @Override + public void registerCallback(IKeyguardExternalViewCallbacks callback) { + mCallbacks.register(callback); + } + + @Override + public void unregisterCallback(IKeyguardExternalViewCallbacks callback) { + mCallbacks.unregister(callback); + } + + private void updateVisibility() { + if (DEBUG) Log.d(TAG, "shouldShow = " + mShouldShow + " askedShow = " + mAskedShow); + mWindow.getDecorView().setVisibility(mShouldShow && mAskedShow ? + View.VISIBLE : View.GONE); + } + + // callbacks from provider to host + protected final boolean requestDismiss() { + boolean ret = true; + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + ret &= callback.requestDismiss(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + return ret; + } + + protected final boolean requestDismissAndStartActivity(final Intent intent) { + boolean ret = true; + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + ret &= callback.requestDismissAndStartActivity(intent); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + return ret; + } + + protected final void collapseNotificationPanel() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.collapseNotificationPanel(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + protected final void setInteractivity(final boolean isInteractive) { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.setInteractivity(isInteractive); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + public void slideLockscreenIn() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.slideLockscreenIn(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + // region Window callbacks + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return false; + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + return false; + } + + @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(WindowManager.LayoutParams attrs) {} + + @Override + public void onContentChanged() {} + + @Override + public void onWindowFocusChanged(boolean hasFocus) {} + + @Override + public void onAttachedToWindow() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.onAttachedToWindow(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + @Override + public void onDetachedFromWindow() { + int N = mCallbacks.beginBroadcast(); + for(int i=0; i < N; i++) { + IKeyguardExternalViewCallbacks callback = mCallbacks.getBroadcastItem(0); + try { + callback.onDetachedFromWindow(); + } catch(RemoteException e) { + } + } + mCallbacks.finishBroadcast(); + } + + @Override + public void onPanelClosed(int featureId, Menu menu) {} + + @Override + public boolean onSearchRequested() { + return false; + } + + @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return false; + } + + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return null; + } + + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) { + return null; + } + + @Override + public void onActionModeStarted(ActionMode mode) {} + + @Override + public void onActionModeFinished(ActionMode mode) {} + } + + private final ProviderImpl mImpl = new ProviderImpl(this); + private final Bundle mOptions; + + protected Provider(Bundle options) { + mOptions = options; + } + + protected Bundle getOptions() { + return mOptions; + } + + /** + * Called when the host view is attached to a window. + */ + protected void onAttach() {} + + /** + * Called when the host view is detached from a window. + */ + protected void onDetach() {} + + /** + * Callback used for getting the view to be displayed within the host's content. + * @return The view to be displayed within the host's content. If null is returned no + * content will be displayed. + */ + protected abstract View onCreateView(); + + // keyguard events + + /** + * Called from the host when the keyguard is being shown to the user. + * @param screenOn True if the screen is currently on. + */ + protected abstract void onKeyguardShowing(boolean screenOn); + + /** + * Called from the host when the user has unlocked the device. Once this is called the lock + * lock screen is no longer being displayed. + * + * <p>The view component should enter a paused state when this is called, and save any state + * information that may be needed once the lock screen is displayed again. For example, a + * non-interactive component that provides animated visuals should pause playback of those + * animations and save the state, if necessary, of that animation.</p> + */ + protected abstract void onKeyguardDismissed(); + + /** + * Called from the host when the keyguard is displaying the security screen for the user to + * enter their pin, password, or pattern. + * + * <p>Interactive components will no longer have focus when the bouncer is displayed and + * should enter a paused or idle state while the bouncer is being shown.</p> + * @param showing True if the bouncer is being show or false when it is dismissed without the + * device being unlocked. + */ + protected abstract void onBouncerShowing(boolean showing); + + /** + * Called from the host when the screen is turned on. + * + * <p>The provided view should return to a running state when this is called. For example, + * a non-interactive component that provides animated visuals should resume playback of + * those animations.</p> + */ + protected abstract void onScreenTurnedOn(); + + /** + * Called from the host when the screen is turned off. + * + * <p>The provided view should provided view should pause its activity, if not currently + * in a paused state, and do any work necessary to be ready when the screen is turned + * back on. This will allow for a seamless user experience once the screen is turned on. + * </p> + */ + protected abstract void onScreenTurnedOff(); + + /** + * Called from the host when the user is swiping the lockscreen + * to transition into the live lock screen + * + * @param swipeProgress [0-1] represents the progress of the swipe + */ + protected void onLockscreenSlideOffsetChanged(float swipeProgress) {} + + // callbacks from provider to host + + /** + * Request that the keyguard be dismissed. Calling this method will dismiss the lock + * screen, if it is a not secure, or present the user with the security screen for the user + * to enter their security code to finish dismissing the lock screen. + * + * <p>If the user has a secure lock screen and dismisses the bouncer without entering their + * secure code, the lock screen will not be dismissed and + * {@link KeyguardExternalViewProviderService.Provider#onBouncerShowing(boolean)} will be + * called with {@code onShowing} being set to false, indicating that the lock screen was not + * dismissed as requested.</p> + * @return True if the call succeeded. + */ + protected final boolean requestDismiss() { + return mImpl.requestDismiss(); + } + + /** + * Request that the keyguard be dismissed and the activity provided by the given intent be + * started once the keyguard is dismissed. If a secure lock screen is being used the user + * will need to enter their correct security code to finish dismissing the lock screen. + * + * <p>If the user has a secure lock screen and dismisses the bouncer without entering their + * secure code, the lock screen will not be dismissed and + * {@link KeyguardExternalViewProviderService.Provider#onBouncerShowing(boolean)} will be + * called with onShowing being set to false, indicating that the lock screen was not + * dismissed as requested.</p> + * @param intent An intent specifying an activity to launch. + * @return True if the call succeeded. + */ + protected final boolean requestDismissAndStartActivity(final Intent intent) { + return mImpl.requestDismissAndStartActivity(intent); + } + + /** + * Call this method when you would like to take focus and hide the notification panel. + * + * <p>You should call this method if your component requires focus and the users's + * attention. The user will still be able to bring the notifications back into view by + * sliding down from the status bar. + * Calling this method has no effect for non-interactive components.</p> + */ + protected final void collapseNotificationPanel() { + mImpl.collapseNotificationPanel(); + } + + /** + * This method should be called when the provided view needs to change from interactive to + * non-interactive and vice versa. + * + * <p>Interactive components can receive input focus and receive user interaction while + * non-interactive components never receive focus and are purely visual.</p> + * @param isInteractive + */ + protected final void setInteractivity(final boolean isInteractive) { + mImpl.setInteractivity(isInteractive); + } + + /** + * Call this method when you like to slide in the lockscreen on top of + * your live lockscreen. Only relevant if you use + * {@link KeyguardExternalViewProviderService.Provider#setInteractivity(boolean)} + */ + protected final void slideLockscreenIn() { + mImpl.slideLockscreenIn(); + } + + /*package*/ final int getWindowType() { + return WindowManager.LayoutParams.TYPE_KEYGUARD_PANEL; + } + + /*package*/ final int getWindowFlags() { + return WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_FULLSCREEN; + } + } +} |