diff options
23 files changed, 583 insertions, 146 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a3360bc6b8d9..b6016e93be53 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2338,6 +2338,7 @@ public final class ViewRootImpl implements ViewParent, mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds); } bounds.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop); + bounds.intersect(0, 0, mAttachInfo.mViewRootImpl.mWidth, mAttachInfo.mViewRootImpl.mHeight); drawable.setBounds(bounds); drawable.draw(canvas); } diff --git a/core/res/res/drawable-hdpi/kg_widget_delete_drop_target.png b/core/res/res/drawable-hdpi/kg_widget_delete_drop_target.png Binary files differnew file mode 100644 index 000000000000..84549ff946a7 --- /dev/null +++ b/core/res/res/drawable-hdpi/kg_widget_delete_drop_target.png diff --git a/core/res/res/drawable-mdpi/kg_widget_delete_drop_target.png b/core/res/res/drawable-mdpi/kg_widget_delete_drop_target.png Binary files differnew file mode 100644 index 000000000000..219f3e5cfd6d --- /dev/null +++ b/core/res/res/drawable-mdpi/kg_widget_delete_drop_target.png diff --git a/core/res/res/drawable-xhdpi/kg_widget_delete_drop_target.png b/core/res/res/drawable-xhdpi/kg_widget_delete_drop_target.png Binary files differnew file mode 100644 index 000000000000..d4965d9f804e --- /dev/null +++ b/core/res/res/drawable-xhdpi/kg_widget_delete_drop_target.png diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml index 67ac1d5298c0..be1d5b6c5ce6 100644 --- a/core/res/res/layout-land/keyguard_host_view.xml +++ b/core/res/res/layout-land/keyguard_host_view.xml @@ -33,6 +33,12 @@ android:layout_height="match_parent" android:clipChildren="false"> + <include layout="@layout/keyguard_widget_remove_drop_target" + android:id="@+id/keyguard_widget_pager_delete_target" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" /> + <include layout="@layout/keyguard_widget_pager" android:id="@+id/app_widget_container" android:layout_width="match_parent" diff --git a/core/res/res/layout-land/keyguard_widget_pager.xml b/core/res/res/layout-land/keyguard_widget_pager.xml index 975288fa4cc2..02c6d0e8d276 100644 --- a/core/res/res/layout-land/keyguard_widget_pager.xml +++ b/core/res/res/layout-land/keyguard_widget_pager.xml @@ -25,7 +25,6 @@ android:paddingRight="25dp" android:paddingTop="25dp" android:paddingBottom="25dp" - android:clipChildren="false" android:clipToPadding="false" androidprv:pageSpacing="10dp"> </com.android.internal.policy.impl.keyguard.KeyguardWidgetCarousel>
\ No newline at end of file diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml index b3270e0d9d3a..9921313bb723 100644 --- a/core/res/res/layout-port/keyguard_host_view.xml +++ b/core/res/res/layout-port/keyguard_host_view.xml @@ -35,6 +35,16 @@ <FrameLayout android:layout_width="match_parent" + android:layout_height="wrap_content"> + <include layout="@layout/keyguard_widget_remove_drop_target" + android:id="@+id/keyguard_widget_pager_delete_target" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" /> + </FrameLayout> + + <FrameLayout + android:layout_width="match_parent" android:layout_height="match_parent" androidprv:layout_childType="widgets"> <include layout="@layout/keyguard_widget_pager" diff --git a/core/res/res/layout-port/keyguard_widget_pager.xml b/core/res/res/layout-port/keyguard_widget_pager.xml index 7fd370b6bdb5..7f22709a0d72 100644 --- a/core/res/res/layout-port/keyguard_widget_pager.xml +++ b/core/res/res/layout-port/keyguard_widget_pager.xml @@ -26,7 +26,6 @@ android:paddingRight="25dp" android:paddingTop="25dp" android:paddingBottom="@dimen/kg_widget_pager_bottom_padding" - android:clipChildren="false" android:clipToPadding="false" androidprv:pageSpacing="10dp"> </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager> diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml index 5d858ae2ef1e..809104d6e0f2 100644 --- a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml +++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml @@ -34,6 +34,12 @@ android:clipChildren="false" android:orientation="vertical"> + <include layout="@layout/keyguard_widget_remove_drop_target" + android:id="@+id/keyguard_widget_pager_delete_target" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top|center_horizontal" /> + <include layout="@layout/keyguard_widget_pager" android:id="@+id/app_widget_container" android:layout_width="match_parent" diff --git a/core/res/res/layout/keyguard_widget_remove_drop_target.xml b/core/res/res/layout/keyguard_widget_remove_drop_target.xml new file mode 100644 index 000000000000..c4fe9e0fe501 --- /dev/null +++ b/core/res/res/layout/keyguard_widget_remove_drop_target.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, 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. +*/ +--> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:gravity="center" + android:padding="20dp" + android:paddingLeft="40dp" + android:paddingRight="40dp" + android:drawableLeft="@drawable/kg_widget_delete_drop_target" + android:drawablePadding="4dp" + android:textColor="#FFF" + android:textSize="13sp" + android:shadowColor="#000" + android:shadowDy="1.0" + android:shadowRadius="1.0" + android:visibility="gone" />
\ No newline at end of file diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml index 36f26287a4fb..8f1bd9aa75f6 100644 --- a/core/res/res/values-land/dimens.xml +++ b/core/res/res/values-land/dimens.xml @@ -56,4 +56,9 @@ <!-- Bottom padding for the widget pager --> <dimen name="kg_widget_pager_bottom_padding">0dp</dimen> + <!-- If the height if keyguard drops below this threshold (most likely + due to the appearance of the IME), then drop the multiuser selector. + Landscape's layout allows this to be smaller than for portrait. --> + <dimen name="kg_squashed_layout_threshold">400dp</dimen> + </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 4966b9733126..3a24cc1fa1cb 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -330,4 +330,9 @@ <!-- Size of the region along the edge of the screen that will accept swipes to scroll the widget area. --> <dimen name="kg_edge_swipe_region_size">24dp</dimen> + + <!-- If the height if keyguard drops below this threshold (most likely + due to the appearance of the IME), then drop the multiuser selector. --> + <dimen name="kg_squashed_layout_threshold">600dp</dimen> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 729999924f1d..a12c14cf6fcb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1234,6 +1234,7 @@ <java-symbol type="dimen" name="keyguard_avatar_frame_stroke_width" /> <java-symbol type="dimen" name="keyguard_avatar_frame_shadow_radius" /> <java-symbol type="dimen" name="kg_edge_swipe_region_size" /> + <java-symbol type="dimen" name="kg_squashed_layout_threshold" /> <java-symbol type="drawable" name="ic_jog_dial_sound_off" /> <java-symbol type="drawable" name="ic_jog_dial_sound_on" /> <java-symbol type="drawable" name="ic_jog_dial_unlock" /> @@ -1318,6 +1319,7 @@ <java-symbol type="id" name="keyguard_sim_puk_view" /> <java-symbol type="id" name="keyguard_account_view" /> <java-symbol type="id" name="keyguard_selector_fade_container" /> + <java-symbol type="id" name="keyguard_widget_pager_delete_target" /> <java-symbol type="id" name="app_widget_container" /> <java-symbol type="id" name="view_flipper" /> <java-symbol type="id" name="emergency_call_button" /> diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java index 08d388b8363d..d28656410d99 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java @@ -150,9 +150,11 @@ public class KeyguardHostView extends KeyguardViewBase { protected void onFinishInflate() { // Grab instances of and make any necessary changes to the main layouts. Create // view state manager and wire up necessary listeners / callbacks. + View deleteDropTarget = findViewById(R.id.keyguard_widget_pager_delete_target); mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container); mAppWidgetContainer.setVisibility(VISIBLE); mAppWidgetContainer.setCallbacks(mWidgetCallbacks); + mAppWidgetContainer.setDeleteDropTarget(deleteDropTarget); mAppWidgetContainer.setMinScale(0.5f); SlidingChallengeLayout slider = diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java index cea3fb09cc82..fa803525a1e9 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardPINView.java @@ -70,7 +70,7 @@ public class KeyguardPINView extends KeyguardAbsKeyInputView } } }); - ok.setOnHoverListener(new NumPadKey.LiftToActivateListener(getContext())); + ok.setOnHoverListener(new LiftToActivateListener(getContext())); } // The delete button is of the PIN keyboard itself in some (e.g. tablet) layouts, diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java index b6ea9baa4094..9ffabf8f58cd 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetFrame.java @@ -43,6 +43,10 @@ public class KeyguardWidgetFrame extends FrameLayout { new PorterDuffXfermode(PorterDuff.Mode.ADD); static final float OUTLINE_ALPHA_MULTIPLIER = 0.6f; + static final int HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR = 0x99FF0000; + + // Temporarily disable this for the time being until we know why the gfx is messing up + static final boolean ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY = true; private int mGradientColor; private LinearGradient mForegroundGradient; @@ -76,6 +80,8 @@ public class KeyguardWidgetFrame extends FrameLayout { // This will hold the width value before we've actually been measured private int mFrameHeight; + private boolean mIsHoveringOverDeleteDropTarget; + // Multiple callers may try and adjust the alpha of the frame. When a caller shows // the outlines, we give that caller control, and nobody else can fade them out. // This prevents animation conflicts. @@ -110,6 +116,15 @@ public class KeyguardWidgetFrame extends FrameLayout { cancelLongPress(); } + void setIsHoveringOverDeleteDropTarget(boolean isHovering) { + if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) { + if (mIsHoveringOverDeleteDropTarget != isHovering) { + mIsHoveringOverDeleteDropTarget = isHovering; + invalidate(); + } + } + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // Watch for longpress events at this level to make sure @@ -171,6 +186,12 @@ public class KeyguardWidgetFrame extends FrameLayout { c.drawRect(mForegroundRect, mGradientPaint); } + private void drawHoveringOverDeleteOverlay(Canvas c) { + if (mIsHoveringOverDeleteDropTarget) { + c.drawColor(HOVER_OVER_DELETE_DROP_TARGET_OVERLAY_COLOR); + } + } + protected void drawBg(Canvas canvas) { if (mBackgroundAlpha > 0.0f) { Drawable bg = mBackgroundDrawable; @@ -183,9 +204,16 @@ public class KeyguardWidgetFrame extends FrameLayout { @Override protected void dispatchDraw(Canvas canvas) { + if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) { + canvas.save(); + } drawBg(canvas); super.dispatchDraw(canvas); drawGradientOverlay(canvas); + if (ENABLE_HOVER_OVER_DELETE_DROP_TARGET_OVERLAY) { + drawHoveringOverDeleteOverlay(canvas); + canvas.restore(); + } } /** diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java index a6d1a029cc06..acb2913a6782 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardWidgetPager.java @@ -717,4 +717,10 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit return indexOfChild((KeyguardWidgetFrame)view.getParent()); } } + + @Override + protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) { + KeyguardWidgetFrame child = getWidgetPageAt(viewIndex); + child.setIsHoveringOverDeleteDropTarget(isHovering); + } } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/LiftToActivateListener.java b/policy/src/com/android/internal/policy/impl/keyguard/LiftToActivateListener.java new file mode 100644 index 000000000000..818108c67f22 --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/LiftToActivateListener.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 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.internal.policy.impl.keyguard; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +/** + * Hover listener that implements lift-to-activate interaction for + * accessibility. May be added to multiple views. + */ +class LiftToActivateListener implements View.OnHoverListener { + /** Manager used to query accessibility enabled state. */ + private final AccessibilityManager mAccessibilityManager; + + private boolean mCachedClickableState; + + public LiftToActivateListener(Context context) { + mAccessibilityManager = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); + } + + @Override + public boolean onHover(View v, MotionEvent event) { + // When touch exploration is turned on, lifting a finger while + // inside the view bounds should perform a click action. + if (mAccessibilityManager.isEnabled() + && mAccessibilityManager.isTouchExplorationEnabled()) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_ENTER: + // Lift-to-type temporarily disables double-tap + // activation by setting the view as not clickable. + mCachedClickableState = v.isClickable(); + v.setClickable(false); + break; + case MotionEvent.ACTION_HOVER_EXIT: + final int x = (int) event.getX(); + final int y = (int) event.getY(); + if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop()) + && (x < v.getWidth() - v.getPaddingRight()) + && (y < v.getHeight() - v.getPaddingBottom())) { + v.performClick(); + } + v.setClickable(mCachedClickableState); + break; + } + } + + // Pass the event to View.onHoverEvent() to handle accessibility. + v.onHoverEvent(event); + + // Consume the event so it doesn't fall through to other views. + return true; + } +}
\ No newline at end of file diff --git a/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java index b38eb284d8b5..3bc39eb38af1 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/MultiPaneChallengeLayout.java @@ -47,6 +47,7 @@ public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayo private OnBouncerStateChangedListener mBouncerListener; private final Rect mTempRect = new Rect(); + private final Context mContext; private final OnClickListener mScrimClickListener = new OnClickListener() { @Override @@ -66,6 +67,8 @@ public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayo public MultiPaneChallengeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + mContext = context; + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiPaneChallengeLayout, defStyleAttr, 0); mOrientation = a.getInt(R.styleable.MultiPaneChallengeLayout_orientation, @@ -173,6 +176,8 @@ public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayo throw new IllegalArgumentException( "MultiPaneChallengeLayout must be measured with an exact size"); } + float squashedLayoutThreshold = + mContext.getResources().getDimension(R.dimen.kg_squashed_layout_threshold); final int width = MeasureSpec.getSize(widthSpec); final int height = MeasureSpec.getSize(heightSpec); @@ -208,28 +213,32 @@ public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayo mUserSwitcherView = child; if (child.getVisibility() == GONE) continue; - - int adjustedWidthSpec = widthSpec; - int adjustedHeightSpec = heightSpec; - if (lp.maxWidth >= 0) { - adjustedWidthSpec = MeasureSpec.makeMeasureSpec( - Math.min(lp.maxWidth, MeasureSpec.getSize(widthSpec)), - MeasureSpec.EXACTLY); - } - if (lp.maxHeight >= 0) { - adjustedHeightSpec = MeasureSpec.makeMeasureSpec( - Math.min(lp.maxHeight, MeasureSpec.getSize(heightSpec)), - MeasureSpec.EXACTLY); - } - // measureChildWithMargins will resolve layout direction for the LayoutParams - measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0); - - // Only subtract out space from one dimension. Favor vertical. - // Offset by 1.5x to add some balance along the other edge. - if (Gravity.isVertical(lp.gravity)) { - heightUsed += child.getMeasuredHeight() * 1.5f; - } else if (Gravity.isHorizontal(lp.gravity)) { - widthUsed += child.getMeasuredWidth() * 1.5f; + if (height < squashedLayoutThreshold) { + int zero = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY); + measureChild(child, zero, zero); + } else { + int adjustedWidthSpec = widthSpec; + int adjustedHeightSpec = heightSpec; + if (lp.maxWidth >= 0) { + adjustedWidthSpec = MeasureSpec.makeMeasureSpec( + Math.min(lp.maxWidth, MeasureSpec.getSize(widthSpec)), + MeasureSpec.EXACTLY); + } + if (lp.maxHeight >= 0) { + adjustedHeightSpec = MeasureSpec.makeMeasureSpec( + Math.min(lp.maxHeight, MeasureSpec.getSize(heightSpec)), + MeasureSpec.EXACTLY); + } + // measureChildWithMargins will resolve layout direction for the LayoutParams + measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0); + + // Only subtract out space from one dimension. Favor vertical. + // Offset by 1.5x to add some balance along the other edge. + if (Gravity.isVertical(lp.gravity)) { + heightUsed += child.getMeasuredHeight() * 1.5f; + } else if (Gravity.isHorizontal(lp.gravity)) { + widthUsed += child.getMeasuredWidth() * 1.5f; + } } } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) { setScrimView(child); diff --git a/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java index bf37ed19b260..a0038bc92fda 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/NumPadKey.java @@ -22,9 +22,7 @@ import android.text.SpannableStringBuilder; import android.text.style.TextAppearanceSpan; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; import android.view.View; -import android.view.accessibility.AccessibilityManager; import android.widget.Button; import android.widget.TextView; @@ -76,6 +74,7 @@ public class NumPadKey extends Button { setOnClickListener(mListener); setOnHoverListener(new LiftToActivateListener(context)); + setAccessibilityDelegate(new ObscureSpeechDelegate(context)); mEnableHaptics = new LockPatternUtils(context).isTactileFeedbackEnabled(); @@ -101,6 +100,14 @@ public class NumPadKey extends Button { setText(builder); } + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + // Reset the "announced headset" flag when detached. + ObscureSpeechDelegate.sAnnouncedHeadset = false; + } + public void setTextView(TextView tv) { mTextView = tv; } @@ -118,45 +125,4 @@ public class NumPadKey extends Button { | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } } - - /** - * Hover listener that implements lift-to-activate interaction for - * accessibility. May be added to multiple views. - */ - static class LiftToActivateListener implements View.OnHoverListener { - /** Manager used to query accessibility enabled state. */ - private final AccessibilityManager mAccessibilityManager; - - public LiftToActivateListener(Context context) { - mAccessibilityManager = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); - } - - @Override - public boolean onHover(View v, MotionEvent event) { - // When touch exploration is turned on, lifting a finger while - // inside the view bounds should perform a click action. - if (mAccessibilityManager.isEnabled() - && mAccessibilityManager.isTouchExplorationEnabled()) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_HOVER_ENTER: - // Lift-to-type temporarily disables double-tap - // activation. - v.setClickable(false); - break; - case MotionEvent.ACTION_HOVER_EXIT: - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop()) - && (x < v.getWidth() - v.getPaddingRight()) - && (y < v.getHeight() - v.getPaddingBottom())) { - v.performClick(); - } - v.setClickable(true); - break; - } - } - return false; - } - } } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/ObscureSpeechDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/ObscureSpeechDelegate.java new file mode 100644 index 000000000000..af043abbf11c --- /dev/null +++ b/policy/src/com/android/internal/policy/impl/keyguard/ObscureSpeechDelegate.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013 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.internal.policy.impl.keyguard; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioManager; +import android.provider.Settings; +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import com.android.internal.R; + +/** + * Accessibility delegate that obscures speech for a view when the user has + * not turned on the "speak passwords" preference and is not listening + * through headphones. + */ +class ObscureSpeechDelegate extends AccessibilityDelegate { + /** Whether any client has announced the "headset" notification. */ + static boolean sAnnouncedHeadset = false; + + private final ContentResolver mContentResolver; + private final AudioManager mAudioManager; + + public ObscureSpeechDelegate(Context context) { + mContentResolver = context.getContentResolver(); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + } + + @Override + public void sendAccessibilityEvent(View host, int eventType) { + super.sendAccessibilityEvent(host, eventType); + + // Play the "headset required" announcement the first time the user + // places accessibility focus on a key. + if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) + && !sAnnouncedHeadset && shouldObscureSpeech()) { + sAnnouncedHeadset = true; + host.announceForAccessibility(host.getContext().getString( + R.string.keyboard_headset_required_to_hear_password)); + } + } + + @Override + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + super.onPopulateAccessibilityEvent(host, event); + + if ((event.getEventType() != AccessibilityEvent.TYPE_ANNOUNCEMENT) + && shouldObscureSpeech()) { + event.getText().clear(); + event.setContentDescription(host.getContext().getString( + R.string.keyboard_password_character_no_headset)); + } + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + + if (shouldObscureSpeech()) { + final Context ctx = host.getContext(); + info.setText(null); + info.setContentDescription( + ctx.getString(R.string.keyboard_password_character_no_headset)); + } + } + + @SuppressWarnings("deprecation") + private boolean shouldObscureSpeech() { + // The user can optionally force speaking passwords. + if (Settings.Secure.getInt(mContentResolver, + Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0) { + return false; + } + + // Always speak if the user is listening through headphones. + if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn()) { + return false; + } + + // Don't speak since this key is used to type a password. + return true; + } +}
\ No newline at end of file diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java index bad50e8616fe..00a0aede5809 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java @@ -48,6 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import android.widget.Scroller; import com.android.internal.R; @@ -205,6 +206,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc protected int REORDERING_ZOOM_IN_OUT_DURATION = 250; private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 300; private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f; + private long REORDERING_DELETE_DROP_TARGET_FADE_DURATION = 150; private float mMinScale = 1f; protected View mDragView; private AnimatorSet mZoomInOutAnim; @@ -228,15 +230,21 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Convenience/caching private Matrix mTmpInvMatrix = new Matrix(); private float[] mTmpPoint = new float[2]; + private Rect mTmpRect = new Rect(); // Fling to delete private int FLING_TO_DELETE_FADE_OUT_DURATION = 350; private float FLING_TO_DELETE_FRICTION = 0.035f; // The degrees specifies how much deviation from the up vector to still consider a fling "up" - private float FLING_TO_DELETE_MAX_FLING_DEGREES = 35f; - private int FLING_TO_DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250; + private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f; protected int mFlingToDeleteThresholdVelocity = -1400; - private boolean mIsFlingingToDelete = false; + // Drag to delete + private boolean mDeferringForDelete = false; + private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250; + private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350; + + // Drop to delete + private View mDeleteDropTarget; public interface PageSwitchListener { void onPageSwitching(View newPage, int newPageIndex); @@ -294,19 +302,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc setOnHierarchyChangeListener(this); } + void setDeleteDropTarget(View v) { + mDeleteDropTarget = v; + } + // Convenience methods to map points from self to parent and vice versa - float[] mapPointFromSelfToParent(float x, float y) { + float[] mapPointFromViewToParent(View v, float x, float y) { mTmpPoint[0] = x; mTmpPoint[1] = y; - getMatrix().mapPoints(mTmpPoint); - mTmpPoint[0] += getLeft(); - mTmpPoint[1] += getTop(); + v.getMatrix().mapPoints(mTmpPoint); + mTmpPoint[0] += v.getLeft(); + mTmpPoint[1] += v.getTop(); return mTmpPoint; } - float[] mapPointFromParentToSelf(float x, float y) { - mTmpPoint[0] = x - getLeft(); - mTmpPoint[1] = y - getTop(); - getMatrix().invert(mTmpInvMatrix); + float[] mapPointFromParentToView(View v, float x, float y) { + mTmpPoint[0] = x - v.getLeft(); + mTmpPoint[1] = y - v.getTop(); + v.getMatrix().invert(mTmpInvMatrix); mTmpInvMatrix.mapPoints(mTmpPoint); return mTmpPoint; } @@ -329,7 +341,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc public void setScaleX(float scaleX) { super.setScaleX(scaleX); if (isReordering(true)) { - float[] p = mapPointFromParentToSelf(mParentDownMotionX, mParentDownMotionY); + float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); mLastMotionX = p[0]; mLastMotionY = p[1]; updateDragViewTranslationDuringDrag(); @@ -519,7 +531,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Update the last motion events when scrolling if (isReordering(true)) { - float[] p = mapPointFromParentToSelf(mParentDownMotionX, mParentDownMotionY); + float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); mLastMotionX = p[0]; mLastMotionY = p[1]; updateDragViewTranslationDuringDrag(); @@ -547,6 +559,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mTouchState == TOUCH_STATE_REST) { pageEndMoving(); } + onPostReorderingAnimationCompleted(); return true; } @@ -640,7 +653,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // ensure that the cache is filled with good values. invalidateCachedOffsets(); - if (mChildCountOnLastMeasure != getChildCount() && !mIsFlingingToDelete) { + if (mChildCountOnLastMeasure != getChildCount() && !mDeferringForDelete) { setCurrentPage(mCurrentPage); } mChildCountOnLastMeasure = getChildCount(); @@ -864,7 +877,6 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc final int pageCount = getChildCount(); if (pageCount > 0) { getVisiblePages(mTempVisiblePagesRange); - boundByReorderablePages(isReordering(false), mTempVisiblePagesRange); final int leftScreen = mTempVisiblePagesRange[0]; final int rightScreen = mTempVisiblePagesRange[1]; if (leftScreen != -1 && rightScreen != -1) { @@ -1046,7 +1058,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mDownScrollX = getScrollX(); mLastMotionX = x; mLastMotionY = y; - float[] p = mapPointFromSelfToParent(x, y); + float[] p = mapPointFromViewToParent(this, x, y); mParentDownMotionX = p[0]; mParentDownMotionY = p[1]; mLastMotionXRemainder = 0; @@ -1278,7 +1290,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mDownMotionX = mLastMotionX = ev.getX(); mDownMotionY = mLastMotionY = ev.getY(); mDownScrollX = getScrollX(); - float[] p = mapPointFromSelfToParent(mLastMotionX, mLastMotionY); + float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); mParentDownMotionX = p[0]; mParentDownMotionY = p[1]; mLastMotionXRemainder = 0; @@ -1330,7 +1342,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Update the parent down so that our zoom animations take this new movement into // account - float[] pt = mapPointFromSelfToParent(mLastMotionX, mLastMotionY); + float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); mParentDownMotionX = pt[0]; mParentDownMotionY = pt[1]; updateDragViewTranslationDuringDrag(); @@ -1339,11 +1351,16 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc final int dragViewIndex = indexOfChild(mDragView); int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE * getViewportWidth()); - int leftBufferEdge = (int) (mapPointFromSelfToParent(mViewport.left, 0)[0] + int leftBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.left, 0)[0] + bufferSize); - int rightBufferEdge = (int) (mapPointFromSelfToParent(mViewport.right, 0)[0] + int rightBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.right, 0)[0] - bufferSize); + // Change the drag view if we are hovering over the drop target + boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget( + (int) mParentDownMotionX, (int) mParentDownMotionY); + setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete); + if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge); if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge); if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX); @@ -1360,7 +1377,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } final int pageUnderPointIndex = pageIndexToSnapTo; - if (pageUnderPointIndex > -1) { + if (pageUnderPointIndex > -1 && !isHoveringOverDelete) { mTempVisiblePagesRange[0] = 0; mTempVisiblePagesRange[1] = getPageCount() - 1; boundByReorderablePages(true, mTempVisiblePagesRange); @@ -1493,13 +1510,29 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc snapToDestination(); } } else if (mTouchState == TOUCH_STATE_REORDERING) { + // Update the last motion position + mLastMotionX = ev.getX(); + mLastMotionY = ev.getY(); + + // Update the parent down so that our zoom animations take this new movement into + // account + float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); + mParentDownMotionX = pt[0]; + mParentDownMotionY = pt[1]; + updateDragViewTranslationDuringDrag(); + boolean handledFling = false; if (!DISABLE_FLING_TO_DELETE) { // Check the velocity and see if we are flinging-to-delete PointF flingToDeleteVector = isFlingingToDelete(); if (flingToDeleteVector != null) { onFlingToDelete(flingToDeleteVector); + handledFling = true; } } + if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX, + (int) mParentDownMotionY)) { + onDropToDelete(); + } } else { onUnhandledTap(ev); } @@ -1776,6 +1809,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc computeScroll(); } + mForceScreenScrolled = true; invalidate(); } @@ -1999,6 +2033,23 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mZoomInOutAnim.playTogether( ObjectAnimator.ofFloat(this, "scaleX", mMinScale), ObjectAnimator.ofFloat(this, "scaleY", mMinScale)); + mZoomInOutAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Show the delete drop target + if (mDeleteDropTarget != null) { + mDeleteDropTarget.setVisibility(View.VISIBLE); + mDeleteDropTarget.animate().alpha(1f) + .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mDeleteDropTarget.setAlpha(0f); + } + }); + } + } + }); mZoomInOutAnim.start(); return true; } @@ -2015,6 +2066,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTouchState = TOUCH_STATE_REORDERING; mIsReordering = true; + // Mark all the non-widget pages as invisible + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + for (int i = 0; i < getPageCount(); ++i) { + if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) { + getPageAt(i).setAlpha(0f); + } + } + // We must invalidate to trigger a redraw to update the layers such that the drag view // is always drawn on top invalidate(); @@ -2036,6 +2096,15 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc R.string.keyguard_accessibility_widget_reorder_end)); } mIsReordering = false; + + // Mark all the non-widget pages as visible again + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + for (int i = 0; i < getPageCount(); ++i) { + if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) { + getPageAt(i).setAlpha(1f); + } + } } public boolean startReordering() { @@ -2080,7 +2149,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc onEndReordering(); } }; - if (!mIsFlingingToDelete) { + if (!mDeferringForDelete) { mPostReorderingPreZoomInRunnable = new Runnable() { public void run() { zoomIn(onCompleteRunnable); @@ -2094,7 +2163,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc // Animate the drag view back to the front position animateDragViewToOriginalPosition(); } else { - zoomIn(onCompleteRunnable); + // Handled in post-delete-animation-callbacks } } @@ -2111,6 +2180,20 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc ObjectAnimator.ofFloat(this, "scaleY", 1f)); mZoomInOutAnim.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationStart(Animator animation) { + // Hide the delete drop target + if (mDeleteDropTarget != null) { + mDeleteDropTarget.animate().alpha(0f) + .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mDeleteDropTarget.setVisibility(View.GONE); + } + }); + } + } + @Override public void onAnimationCancel(Animator animation) { mDragView = null; } @@ -2194,6 +2277,97 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } }; + private Runnable createPostDeleteAnimationRunnable(final View dragView) { + return new Runnable() { + @Override + public void run() { + int dragViewIndex = indexOfChild(dragView); + + // For each of the pages around the drag view, animate them from the previous + // position to the new position in the layout (as a result of the drag view moving + // in the layout) + // NOTE: We can make an assumption here because we have side-bound pages that we + // will always have pages to animate in from the left + getVisiblePages(mTempVisiblePagesRange); + boundByReorderablePages(true, mTempVisiblePagesRange); + boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]); + boolean slideFromLeft = (isLastWidgetPage || + dragViewIndex > mTempVisiblePagesRange[0]); + + // Setup the scroll to the correct page before we swap the views + if (slideFromLeft) { + snapToPageImmediately(dragViewIndex - 1); + } + + int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]); + int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1); + int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 ); + int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex); + ArrayList<Animator> animations = new ArrayList<Animator>(); + for (int i = lowerIndex; i <= upperIndex; ++i) { + View v = getChildAt(i); + // dragViewIndex < pageUnderPointIndex, so after we remove the + // drag view all subsequent views to pageUnderPointIndex will + // shift down. + int oldX = 0; + int newX = 0; + if (slideFromLeft) { + if (i == 0) { + // Simulate the page being offscreen with the page spacing + oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i) + - mPageSpacing; + } else { + oldX = getViewportOffsetX() + getChildOffset(i - 1); + } + newX = getViewportOffsetX() + getChildOffset(i); + } else { + oldX = getChildOffset(i) - getChildOffset(i - 1); + newX = 0; + } + + // Animate the view translation from its old position to its new + // position + AnimatorSet anim = (AnimatorSet) v.getTag(); + if (anim != null) { + anim.cancel(); + } + + // Note: Hacky, but we want to skip any optimizations to not draw completely + // hidden views + v.setAlpha(Math.max(v.getAlpha(), 0.01f)); + v.setTranslationX(oldX - newX); + anim = new AnimatorSet(); + anim.playTogether( + ObjectAnimator.ofFloat(v, "translationX", 0f), + ObjectAnimator.ofFloat(v, "alpha", 1f)); + animations.add(anim); + v.setTag(anim); + } + + AnimatorSet slideAnimations = new AnimatorSet(); + slideAnimations.playTogether(animations); + slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION); + slideAnimations.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final Runnable onCompleteRunnable = new Runnable() { + @Override + public void run() { + mDeferringForDelete = false; + onEndReordering(); + } + }; + zoomIn(onCompleteRunnable); + } + }); + slideAnimations.start(); + + removeView(dragView); + onRemoveView(dragView); + } + }; + } + public void onFlingToDelete(PointF vel) { final long startTime = AnimationUtils.currentAnimationTimeMillis(); @@ -2230,59 +2404,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel, from, startTime, FLING_TO_DELETE_FRICTION); - final Runnable onAnimationEndRunnable = new Runnable() { - @Override - public void run() { - int dragViewIndex = indexOfChild(dragView); - // Setup the scroll to the correct page before we swap the views - snapToPageImmediately(dragViewIndex - 1); - - // For each of the pages around the drag view, animate them from the previous - // position to the new position in the layout (as a result of the drag view moving - // in the layout) - // NOTE: We can make an assumption here because we have side-bound pages that we - // will always have pages to animate in from the left - int lowerIndex = 0; - int upperIndex = dragViewIndex - 1; - for (int i = lowerIndex; i <= upperIndex; ++i) { - View v = getChildAt(i); - // dragViewIndex < pageUnderPointIndex, so after we remove the - // drag view all subsequent views to pageUnderPointIndex will - // shift down. - int oldX = 0; - if (i == 0) { - oldX = -(getViewportOffsetX() + getChildOffset(i)); - } else { - oldX = getViewportOffsetX() + getChildOffset(i - 1); - } - int newX = getViewportOffsetX() + getChildOffset(i); - - // Animate the view translation from its old position to its new - // position - AnimatorSet anim = (AnimatorSet) v.getTag(); - if (anim != null) { - anim.cancel(); - } - - v.setTranslationX(oldX - newX); - anim = new AnimatorSet(); - anim.setDuration(FLING_TO_DELETE_SLIDE_IN_SIDE_PAGE_DURATION); - anim.playTogether( - ObjectAnimator.ofFloat(v, "translationX", 0f)); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mIsFlingingToDelete = false; - } - }); - anim.start(); - v.setTag(anim); - } - - removeView(dragView); - onRemoveView(dragView); - } - }; + final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView); // Create and start the animation ValueAnimator mDropAnim = new ValueAnimator(); @@ -2296,11 +2418,57 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } }); mDropAnim.start(); - mIsFlingingToDelete = true; + mDeferringForDelete = true; } - /* Accessibility */ + /* Drag to delete */ + private boolean isHoveringOverDeleteDropTarget(int x, int y) { + if (mDeleteDropTarget != null) { + mDeleteDropTarget.getGlobalVisibleRect(mTmpRect); + return mTmpRect.contains(x, y); + } + return false; + } + + protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {} + + private void onDropToDelete() { + final View dragView = mDragView; + final float toScale = 0f; + final float toAlpha = 0f; + + // Create and start the complex animation + ArrayList<Animator> animations = new ArrayList<Animator>(); + AnimatorSet motionAnim = new AnimatorSet(); + motionAnim.setInterpolator(new DecelerateInterpolator(2)); + motionAnim.playTogether( + ObjectAnimator.ofFloat(dragView, "scaleX", toScale), + ObjectAnimator.ofFloat(dragView, "scaleY", toScale)); + animations.add(motionAnim); + + AnimatorSet alphaAnim = new AnimatorSet(); + alphaAnim.setInterpolator(new LinearInterpolator()); + alphaAnim.playTogether( + ObjectAnimator.ofFloat(dragView, "alpha", toAlpha)); + animations.add(alphaAnim); + + final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView); + + AnimatorSet anim = new AnimatorSet(); + anim.playTogether(animations); + anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION); + anim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + onAnimationEndRunnable.run(); + } + }); + anim.start(); + + mDeferringForDelete = true; + } + + /* Accessibility */ @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 6b277c7887e2..4953f48bb76d 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -40,7 +40,9 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.database.ContentObserver; +import android.graphics.Point; import android.graphics.Rect; +import android.hardware.display.DisplayManager; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Binder; @@ -62,6 +64,7 @@ import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; +import android.view.Display; import android.view.IWindow; import android.view.IWindowManager; import android.view.InputDevice; @@ -137,6 +140,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>(); + private final Rect mTempRect = new Rect(); + + private final Point mTempPoint = new Point(); + + private final Display mDefaultDisplay; + private final PackageManager mPackageManager; private final IWindowManager mWindowManagerService; @@ -194,6 +203,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mWindowManagerService = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE); mSecurityPolicy = new SecurityPolicy(); mMainHandler = new MainHandler(mContext.getMainLooper()); + //TODO: (multi-display) We need to support multiple displays. + DisplayManager displayManager = (DisplayManager) + mContext.getSystemService(Context.DISPLAY_SERVICE); + mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( context.getContentResolver()); @@ -582,6 +595,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * @param outBounds The output to which to write the focus bounds. * @return Whether accessibility focus was found and the bounds are populated. */ + // TODO: (multi-display) Make sure this works for multiple displays. boolean getAccessibilityFocusBoundsInActiveWindow(Rect outBounds) { // Instead of keeping track of accessibility focus events per // window to be able to find the focus in the active window, @@ -603,6 +617,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } focus.getBoundsInScreen(outBounds); + // Clip to the window rectangle. + Rect windowBounds = mTempRect; + getActiveWindowBounds(windowBounds); + outBounds.intersect(windowBounds); + // Clip to the screen rectangle. + mDefaultDisplay.getRealSize(mTempPoint); + outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y); return true; } finally { client.removeConnection(connectionId); |