diff --git a/CleanSpec.mk b/CleanSpec.mk index 0501ef0..3f23969 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -42,4 +42,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/org.cy $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/org.cyanogenmod.platform.sdk_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/org.cyanogenmod.platform.internal_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/docs/cm-api-stubs-timestamp) -$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/cmsdk_stubs_current_intermediates) \ No newline at end of file +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/cmsdk_stubs_current_intermediates) +# KeyguardExternalView uses a new interface which requires cleaning to avoid a runtime exception +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/org.cyanogenmod.platform_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/org.cyanogenmod.platform.sdk_intermediates) diff --git a/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl b/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl new file mode 100644 index 0000000..10f069e --- /dev/null +++ b/src/java/cyanogenmod/externalviews/IKeyguardExternalViewProvider.aidl @@ -0,0 +1,40 @@ +/** + * 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.graphics.Rect; + +/** @hide */ +interface IKeyguardExternalViewProvider +{ + oneway void onAttach(in IBinder windowToken); + oneway void onStart(); + oneway void onResume(); + oneway void onPause(); + oneway void onStop(); + oneway void onDetach(); + + // Keyguard specific interface + oneway void onKeyguardShowing(boolean screenOn); + oneway void onKeyguardDismissed(); + oneway void onBouncerShowing(boolean showing); + oneway void onScreenTurnedOn(); + oneway void onScreenTurnedOff(); + + void alterWindow(in int x, in int y, in int width, in int height, in boolean visible, + in Rect clipRect); +} diff --git a/src/java/cyanogenmod/externalviews/KeyguardExternalView.java b/src/java/cyanogenmod/externalviews/KeyguardExternalView.java index 516422f..b340a77 100644 --- a/src/java/cyanogenmod/externalviews/KeyguardExternalView.java +++ b/src/java/cyanogenmod/externalviews/KeyguardExternalView.java @@ -16,24 +16,40 @@ package cyanogenmod.externalviews; -import android.content.Context; +import android.app.Activity; +import android.app.Application; import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.graphics.Point; import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; import android.os.RemoteException; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; import android.view.WindowManager; +import java.util.LinkedList; + /** * TODO: unhide once documented and finalized * @hide */ -public final class KeyguardExternalView extends ExternalView { +public class KeyguardExternalView extends View implements Application.ActivityLifecycleCallbacks, + ViewTreeObserver.OnPreDrawListener { public static final String EXTRA_PERMISSION_LIST = "permissions_list"; public static final String CATEGORY_KEYGUARD_GRANT_PERMISSION = "org.cyanogenmod.intent.category.KEYGUARD_GRANT_PERMISSION"; + private LinkedList mQueue = new LinkedList(); + + private Context mContext; + private final ExternalViewProperties mExternalViewProperties; + private volatile IKeyguardExternalViewProvider mExternalViewProvider; private final Point mDisplaySize; public KeyguardExternalView(Context context, AttributeSet attrs) { @@ -41,22 +57,65 @@ public final class KeyguardExternalView extends ExternalView { } public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context,attrs); + this(context, attrs); } - public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - this(context,attrs); + public KeyguardExternalView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs); } - public KeyguardExternalView(Context context, AttributeSet attributeSet, - ComponentName componentName) { - super(context,attributeSet,componentName); + public KeyguardExternalView(Context context, AttributeSet attributeSet, ComponentName componentName) { + super(context, attributeSet); + mContext = getContext(); + mExternalViewProperties = new ExternalViewProperties(this, mContext); + Application app = (mContext instanceof Activity) ? ((Activity) mContext).getApplication() + : (Application) mContext; + app.registerActivityLifecycleCallbacks(this); + if (componentName != null) { + mContext.bindService(new Intent().setComponent(componentName), + mServiceConnection, Context.BIND_AUTO_CREATE); + } mDisplaySize = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getRealSize(mDisplaySize); } + private ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + try { + mExternalViewProvider = IKeyguardExternalViewProvider.Stub.asInterface( + IExternalViewProviderFactory.Stub.asInterface(service). + createExternalView(null)); + executeQueue(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mExternalViewProvider = null; + } + }; + + private void executeQueue() { + while (!mQueue.isEmpty()) { + Runnable r = mQueue.pop(); + r.run(); + } + } + + protected void performAction(Runnable r) { + if (mExternalViewProvider != null) { + r.run(); + } else { + mQueue.add(r); + } + } + + // view overrides, for positioning + @Override public boolean onPreDraw() { if (!mExternalViewProperties.hasChanged()) { @@ -81,4 +140,179 @@ public final class KeyguardExternalView extends ExternalView { }); return true; } + + // Activity lifecycle callbacks + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + } + + @Override + public void onActivityStarted(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onStart(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onActivityResumed(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onResume(); + } catch (RemoteException e) { + } + getViewTreeObserver().addOnPreDrawListener(KeyguardExternalView.this); + } + }); + } + + @Override + public void onActivityPaused(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onPause(); + } catch (RemoteException e) { + } + getViewTreeObserver().removeOnPreDrawListener(KeyguardExternalView.this); + } + }); + } + + @Override + public void onActivityStopped(Activity activity) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onStop(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + mExternalViewProvider = null; + mContext.unbindService(mServiceConnection); + } + + // Placeholder callbacks + + @Override + public void onDetachedFromWindow() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onDetach(); + } catch (RemoteException e) { + } + } + }); + } + + @Override + public void onAttachedToWindow() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onAttach(null); + } catch (RemoteException e) { + } + } + }); + } + + /** + * Sets the component of the ExternalViewProviderService to be used for this ExternalView. + * If a provider is already connected to this view, it is first unbound before binding to the + * new provider. + * @param componentName + */ + public void setProviderComponent(ComponentName componentName) { + // unbind any existing external view provider + if (mExternalViewProvider != null) { + mContext.unbindService(mServiceConnection); + } + if (componentName != null) { + mContext.bindService(new Intent().setComponent(componentName), + mServiceConnection, Context.BIND_AUTO_CREATE); + } + } + + public void onKeyguardShowing(final boolean screenOn) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onKeyguardShowing(screenOn); + } catch (RemoteException e) { + } + } + }); + } + + public void onKeyguardDismissed() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onKeyguardDismissed(); + } catch (RemoteException e) { + } + } + }); + } + + public void onBouncerShowing(final boolean showing) { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onBouncerShowing(showing); + } catch (RemoteException e) { + } + } + }); + } + + public void onScreenTurnedOn() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onScreenTurnedOn(); + } catch (RemoteException e) { + } + } + }); + } + + public void onScreenTurnedOff() { + performAction(new Runnable() { + @Override + public void run() { + try { + mExternalViewProvider.onScreenTurnedOff(); + } catch (RemoteException e) { + } + } + }); + } } diff --git a/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java b/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java index 208b667..6efcb66 100644 --- a/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java +++ b/src/java/cyanogenmod/externalviews/KeyguardExternalViewProviderService.java @@ -16,23 +16,265 @@ 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.RemoteException; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; import android.view.WindowManager; +import com.android.internal.policy.PhoneWindow; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; /** * TODO: unhide once documented and finalized * @hide */ -public abstract class KeyguardExternalViewProviderService extends ExternalViewProviderService { +public abstract class KeyguardExternalViewProviderService extends Service { private static final String TAG = KeyguardExternalViewProviderService.class.getSimpleName(); private static final boolean DEBUG = false; - protected abstract class Provider extends ExternalViewProviderService.Provider { - protected Provider(Bundle options) { - super(options); + private WindowManager mWindowManager; + private final Handler mHandler = new Handler(); + + @Override + public void onCreate() { + super.onCreate(); + + mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + + @Override + public final IBinder onBind(Intent intent) { + return new IExternalViewProviderFactory.Stub() { + @Override public IBinder createExternalView(final Bundle options) { + FutureTask c = new FutureTask(new Callable() { + @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; + } + } + }; + } + + protected abstract Provider createExternalView(Bundle options); + + protected abstract class Provider { + private final class ProviderImpl extends IKeyguardExternalViewProvider.Stub { + private final Window mWindow; + private final WindowManager.LayoutParams mParams; + + private boolean mShouldShow = true; + private boolean mAskedShow = false; + + public ProviderImpl(Provider provider) { + mWindow = new PhoneWindow(KeyguardExternalViewProviderService.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 onStart() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onStart(); + } + }); + } + + @Override + public void onResume() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mShouldShow = true; + updateVisibility(); + Provider.this.onResume(); + } + }); + } + + @Override + public void onPause() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mShouldShow = false; + updateVisibility(); + Provider.this.onPause(); + } + }); + } + + @Override + public void onStop() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + Provider.this.onStop(); + } + }); + } + + @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 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); + } + }); + } + + private void updateVisibility() { + if (DEBUG) Log.d(TAG, "shouldShow = " + mShouldShow + " askedShow = " + mAskedShow); + mWindow.getDecorView().setVisibility(mShouldShow && mAskedShow ? + View.VISIBLE : View.GONE); + } } + private final ProviderImpl mImpl = new ProviderImpl(this); + private final Bundle mOptions; + + protected Provider(Bundle options) { + mOptions = options; + } + + protected Bundle getOptions() { + return mOptions; + } + + protected void onAttach() {} + protected abstract View onCreateView(); + protected void onStart() {} + protected void onResume() {} + protected void onPause() {} + protected void onStop() {} + protected void onDetach() {} + + protected abstract void onKeyguardShowing(boolean screenOn); + protected abstract void onKeyguardDismissed(); + protected abstract void onBouncerShowing(boolean showing); + protected abstract void onScreenTurnedOn(); + protected abstract void onScreenTurnedOff(); + /*package*/ final int getWindowType() { return WindowManager.LayoutParams.TYPE_KEYGUARD_PANEL; }