replicant-vendor_cmsdk/sdk/src/java/cyanogenmod/externalviews/KeyguardExternalViewProvide...

637 lines
24 KiB
Java

/*
* 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;
}
}
}