• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.statusbar.phone;
16 
17 import android.content.Context;
18 import android.content.res.Configuration;
19 import android.graphics.Rect;
20 import android.support.annotation.VisibleForTesting;
21 import android.util.AttributeSet;
22 import android.util.Log;
23 import android.util.Pair;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.FrameLayout;
28 
29 import com.android.systemui.R;
30 
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 
34 /**
35  * Redirects touches that aren't handled by any child view to the nearest
36  * clickable child. Only takes effect on <sw600dp.
37  */
38 public class NearestTouchFrame extends FrameLayout {
39 
40     private final ArrayList<View> mClickableChildren = new ArrayList<>();
41     private final boolean mIsActive;
42     private final int[] mTmpInt = new int[2];
43     private final int[] mOffset = new int[2];
44     private View mTouchingChild;
45 
NearestTouchFrame(Context context, AttributeSet attrs)46     public NearestTouchFrame(Context context, AttributeSet attrs) {
47         this(context, attrs, context.getResources().getConfiguration());
48     }
49 
50     @VisibleForTesting
NearestTouchFrame(Context context, AttributeSet attrs, Configuration c)51     NearestTouchFrame(Context context, AttributeSet attrs, Configuration c) {
52         super(context, attrs);
53         mIsActive = c.smallestScreenWidthDp < 600;
54     }
55 
56     @Override
57     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
58         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
59         mClickableChildren.clear();
60         addClickableChildren(this);
61     }
62 
63     @Override
64     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
65         super.onLayout(changed, left, top, right, bottom);
66         getLocationInWindow(mOffset);
67     }
68 
69     private void addClickableChildren(ViewGroup group) {
70         final int N = group.getChildCount();
71         for (int i = 0; i < N; i++) {
72             View child = group.getChildAt(i);
73             if (child.isClickable()) {
74                 mClickableChildren.add(child);
75             } else if (child instanceof ViewGroup) {
76                 addClickableChildren((ViewGroup) child);
77             }
78         }
79     }
80 
81     @Override
82     public boolean onTouchEvent(MotionEvent event) {
83         if (mIsActive) {
84             if (event.getAction() == MotionEvent.ACTION_DOWN) {
85                 mTouchingChild = findNearestChild(event);
86             }
87             if (mTouchingChild != null) {
88                 event.offsetLocation(mTouchingChild.getWidth() / 2 - event.getX(),
89                         mTouchingChild.getHeight() / 2 - event.getY());
90                 return mTouchingChild.getVisibility() == VISIBLE
91                         && mTouchingChild.dispatchTouchEvent(event);
92             }
93         }
94         return super.onTouchEvent(event);
95     }
96 
97     private View findNearestChild(MotionEvent event) {
98         return mClickableChildren
99                 .stream()
100                 .filter(v -> v.isAttachedToWindow())
101                 .map(v -> new Pair<>(distance(v, event), v))
102                 .min(Comparator.comparingInt(f -> f.first))
103                 .get().second;
104     }
105 
distance(View v, MotionEvent event)106     private int distance(View v, MotionEvent event) {
107         v.getLocationInWindow(mTmpInt);
108         int left = mTmpInt[0] - mOffset[0];
109         int top = mTmpInt[1] - mOffset[1];
110         int right = left + v.getWidth();
111         int bottom = top + v.getHeight();
112 
113         int x = Math.min(Math.abs(left - (int) event.getX()),
114                 Math.abs((int) event.getX() - right));
115         int y = Math.min(Math.abs(top - (int) event.getY()),
116                 Math.abs((int) event.getY() - bottom));
117 
118         return Math.max(x, y);
119     }
120 }
121