android:layout_height="match_parent"
android:layout_width="match_parent"
>
+
+ <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
+ android:id="@+id/keyguard_indication_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="70dp"
+ android:layout_gravity="bottom|center_horizontal"
+ android:textStyle="italic"
+ android:textColor="#ffffff"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+ <FrameLayout
+ android:id="@+id/preview_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ </FrameLayout>
+
<com.android.systemui.statusbar.KeyguardAffordanceView
android:id="@+id/camera_button"
android:layout_height="64dp"
android:scaleType="center"
android:contentDescription="@string/accessibility_phone_button" />
- <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
- android:id="@+id/keyguard_indication_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="70dp"
- android:layout_gravity="bottom|center_horizontal"
- android:textStyle="italic"
- android:textColor="#ffffff"
- android:textAppearance="?android:attr/textAppearanceSmall"/>
-
<com.android.systemui.statusbar.KeyguardAffordanceView
android:id="@+id/lock_icon"
android:layout_width="64dp"
package com.android.systemui.statusbar;
+import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.view.ViewPropertyAnimator;
* @param endValue the end value of the animator
* @param velocity the current velocity of the motion
*/
- public void apply(ValueAnimator animator, float currValue, float endValue, float velocity) {
+ public void apply(Animator animator, float currValue, float endValue, float velocity) {
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
}
* @param maxDistance the maximum distance for this interaction; the maximum animation length
* gets multiplied by the ratio between the actual distance and this value
*/
- public void apply(ValueAnimator animator, float currValue, float endValue, float velocity,
+ public void apply(Animator animator, float currValue, float endValue, float velocity,
float maxDistance) {
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
maxDistance);
* @param maxDistance the maximum distance for this interaction; the maximum animation length
* gets multiplied by the ratio between the actual distance and this value
*/
- public void applyDismissing(ValueAnimator animator, float currValue, float endValue,
+ public void applyDismissing(Animator animator, float currValue, float endValue,
float velocity, float maxDistance) {
AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
maxDistance);
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewAnimationUtils;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.ImageView;
private int mCircleColor;
private boolean mIsLeft;
private float mArrowAlpha = 0.0f;
+ private View mPreviewView;
+ private float mCircleStartRadius;
+ private float mMaxCircleSize;
+ private Animator mPreviewClipper;
+ private AnimatorListenerAdapter mClipEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPreviewClipper = null;
+ }
+ };
private AnimatorListenerAdapter mCircleEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onLayout(changed, left, top, right, bottom);
mCenterX = getWidth() / 2;
mCenterY = getHeight() / 2;
+ mMaxCircleSize = getMaxCircleSize();
}
@Override
canvas.restore();
}
+ public void setPreviewView(View v) {
+ mPreviewView = v;
+ }
+
private void drawArrow(Canvas canvas) {
if (mArrowAlpha > 0) {
canvas.save();
private void updateCircleColor() {
float fraction = 0.5f + 0.5f * Math.max(0.0f, Math.min(1.0f,
(mCircleRadius - mMinBackgroundRadius) / (0.5f * mMinBackgroundRadius)));
+ if (mPreviewView != null) {
+ float finishingFraction = 1 - Math.max(0, mCircleRadius - mCircleStartRadius)
+ / (mMaxCircleSize - mCircleStartRadius);
+ fraction *= finishingFraction;
+ }
int color = Color.argb((int) (Color.alpha(mCircleColor) * fraction),
Color.red(mCircleColor),
Color.green(mCircleColor), Color.blue(mCircleColor));
public void finishAnimation(float velocity, final Runnable mAnimationEndRunnable) {
cancelAnimator(mCircleAnimator);
+ cancelAnimator(mPreviewClipper);
+ mCircleStartRadius = mCircleRadius;
float maxCircleSize = getMaxCircleSize();
ValueAnimator animatorToRadius = getAnimatorToRadius(maxCircleSize);
mFlingAnimationUtils.applyDismissing(animatorToRadius, mCircleRadius, maxCircleSize,
});
animatorToRadius.start();
setImageAlpha(0, true);
+ if (mPreviewView != null) {
+ mPreviewView.setVisibility(View.VISIBLE);
+ mPreviewClipper = ViewAnimationUtils.createCircularReveal(
+ mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
+ maxCircleSize);
+ mFlingAnimationUtils.applyDismissing(mPreviewClipper, mCircleRadius, maxCircleSize,
+ velocity, maxCircleSize);
+ mPreviewClipper.addListener(mClipEndListener);
+ mPreviewClipper.start();
+ }
}
private float getMaxCircleSize() {
if (mCircleAnimator == null) {
mCircleRadius = circleRadius;
invalidate();
+ if (nowHidden) {
+ if (mPreviewView != null) {
+ mPreviewView.setVisibility(View.INVISIBLE);
+ }
+ }
} else if (!mCircleWillBeHidden) {
// We just update the end value
}
} else {
cancelAnimator(mCircleAnimator);
+ cancelAnimator(mPreviewClipper);
ValueAnimator animator = getAnimatorToRadius(circleRadius);
Interpolator interpolator = circleRadius == 0.0f
? mDisappearInterpolator
duration = Math.min(duration, CIRCLE_DISAPPEAR_MAX_DURATION);
animator.setDuration(duration);
animator.start();
+ if (mPreviewView != null) {
+ mPreviewView.setVisibility(View.VISIBLE);
+ mPreviewClipper = ViewAnimationUtils.createCircularReveal(
+ mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
+ circleRadius);
+ mPreviewClipper.setInterpolator(interpolator);
+ mPreviewClipper.setDuration(duration);
+ mPreviewClipper.addListener(mClipEndListener);
+ mPreviewClipper.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPreviewView.setVisibility(View.INVISIBLE);
+ }
+ });
+ mPreviewClipper.start();
+ }
}
}
private Animator mSwipeAnimator;
private int mMinBackgroundRadius;
private boolean mMotionPerformedByUser;
- private PowerManager mPM;
private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLeftIcon.setIsLeft(true);
mCenterIcon = mCallback.getCenterIcon();
mRightIcon = mCallback.getRightIcon();
+ mLeftIcon.setPreviewView(mCallback.getLeftPreview());
+ mRightIcon.setPreviewView(mCallback.getRightPreview());
updateIcon(mLeftIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
updateIcon(mCenterIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
updateIcon(mRightIcon, 0.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
initDimens();
- mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
private void initDimens() {
float absTranslation = Math.abs(translation);
if (absTranslation > Math.abs(mTranslationOnDown) + mMinTranslationAmount ||
mMotionPerformedByUser) {
- userActivity();
mMotionPerformedByUser = true;
}
if (translation != mTranslation || isReset) {
return translation * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
}
-
- private void userActivity() {
- mPM.userActivity(SystemClock.uptimeMillis(), false);
- }
-
public void animateHideLeftRightIcon() {
updateIcon(mRightIcon, 0f, 0f, true);
updateIcon(mLeftIcon, 0f, 0f, true);
KeyguardAffordanceView getCenterIcon();
KeyguardAffordanceView getRightIcon();
+
+ View getLeftPreview();
+
+ View getRightPreview();
}
}
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.policy.PreviewInflater;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
private KeyguardAffordanceView mPhoneImageView;
private KeyguardAffordanceView mLockIcon;
private View mIndicationText;
+ private ViewGroup mPreviewContainer;
+
+ private View mPhonePreview;
+ private View mCameraPreview;
private ActivityStarter mActivityStarter;
private UnlockMethodCache mUnlockMethodCache;
protected void onFinishInflate() {
super.onFinishInflate();
mLockPatternUtils = new LockPatternUtils(mContext);
+ mPreviewContainer = (ViewGroup) findViewById(R.id.preview_container);
mCameraImageView = (KeyguardAffordanceView) findViewById(R.id.camera_button);
mPhoneImageView = (KeyguardAffordanceView) findViewById(R.id.phone_button);
mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon);
updateTrust();
setClipChildren(false);
setClipToPadding(false);
+ inflatePreviews();
}
public void setActivityStarter(ActivityStarter activityStarter) {
return mCameraImageView;
}
+ public View getPhonePreview() {
+ return mPhonePreview;
+ }
+
+ public View getCameraPreview() {
+ return mCameraPreview;
+ }
+
public KeyguardAffordanceView getLockIcon() {
return mLockIcon;
}
updateCameraVisibility();
}
+ private void inflatePreviews() {
+ PreviewInflater inflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
+ mPhonePreview = inflater.inflatePreview(PHONE_INTENT);
+ mCameraPreview = inflater.inflatePreview(getCameraIntent());
+ if (mPhonePreview != null) {
+ mPreviewContainer.addView(mPhonePreview);
+ mPhonePreview.setVisibility(View.INVISIBLE);
+ }
+ if (mCameraPreview != null) {
+ mPreviewContainer.addView(mCameraPreview);
+ mCameraPreview.setVisibility(View.INVISIBLE);
+ }
+ }
+
private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
post(new Runnable() {
--- /dev/null
+/*
+ * Copyright (C) 2014 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 com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+
+/**
+ * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
+ * the fake navigation bar.
+ */
+public class KeyguardPreviewContainer extends FrameLayout {
+
+ private Drawable mBlackBarDrawable = new Drawable() {
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.save();
+ canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
+ canvas.drawColor(Color.BLACK);
+ canvas.restore();
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // noop
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ // noop
+ }
+
+ @Override
+ public int getOpacity() {
+ return android.graphics.PixelFormat.OPAQUE;
+ }
+ };
+
+ public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setBackground(mBlackBarDrawable);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ setPadding(0, 0, 0, insets.getStableInsetBottom());
+ return super.onApplyWindowInsets(insets);
+ }
+}
}
@Override
+ public View getLeftPreview() {
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getCameraPreview()
+ : mKeyguardBottomArea.getPhonePreview();
+ }
+
+ @Override
+ public View getRightPreview() {
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getPhonePreview()
+ : mKeyguardBottomArea.getCameraPreview();
+ }
+
+ @Override
protected float getPeekHeight() {
if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
return mNotificationStackScroller.getPeekHeight();
--- /dev/null
+/*
+ * Copyright (C) 2014 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 com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardActivityLauncher;
+import com.android.systemui.statusbar.phone.KeyguardPreviewContainer;
+
+import java.util.List;
+
+/**
+ * Utility class to inflate previews for phone and camera affordance.
+ */
+public class PreviewInflater {
+
+ private static final String TAG = "PreviewInflater";
+
+ private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
+
+ private Context mContext;
+ private LockPatternUtils mLockPatternUtils;
+
+ public PreviewInflater(Context context, LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ public View inflatePreview(Intent intent) {
+ WidgetInfo info = getWidgetInfo(intent);
+ if (info == null) {
+ return null;
+ }
+ View v = inflateWidgetView(info);
+ if (v == null) {
+ return null;
+ }
+ KeyguardPreviewContainer container = new KeyguardPreviewContainer(mContext, null);
+ container.addView(v);
+ return container;
+ }
+
+ private View inflateWidgetView(WidgetInfo widgetInfo) {
+ View widgetView = null;
+ try {
+ Context appContext = mContext.createPackageContext(
+ widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
+ LayoutInflater appInflater = (LayoutInflater)
+ appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ appInflater = appInflater.cloneInContext(appContext);
+ widgetView = appInflater.inflate(widgetInfo.layoutId, null, false);
+ } catch (PackageManager.NameNotFoundException|RuntimeException e) {
+ Log.w(TAG, "Error creating widget view", e);
+ }
+ return widgetView;
+ }
+
+ private WidgetInfo getWidgetInfo(Intent intent) {
+ WidgetInfo info = new WidgetInfo();
+ PackageManager packageManager = mContext.getPackageManager();
+ final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY, mLockPatternUtils.getCurrentUser());
+ if (appList.size() == 0) {
+ return null;
+ }
+ ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
+ PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
+ mLockPatternUtils.getCurrentUser());
+ if (wouldLaunchResolverActivity(resolved, appList)) {
+ return null;
+ }
+ if (resolved == null || resolved.activityInfo == null) {
+ return null;
+ }
+ if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
+ return null;
+ }
+ int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
+ if (layoutId == 0) {
+ return null;
+ }
+ info.contextPackage = resolved.activityInfo.packageName;
+ info.layoutId = layoutId;
+ return info;
+ }
+
+ private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
+ // If the list contains the above resolved activity, then it can't be
+ // ResolverActivity itself.
+ for (int i = 0; i < appList.size(); i++) {
+ ResolveInfo tmp = appList.get(i);
+ if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
+ && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static class WidgetInfo {
+ String contextPackage;
+ int layoutId;
+ }
+}