aboutsummaryrefslogtreecommitdiffstats
path: root/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java
diff options
context:
space:
mode:
Diffstat (limited to 'sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java')
-rw-r--r--sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java636
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>&lt;lockscreen&gt;</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;
+ }
+ }
+}