1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.rotary.ui;
18 
19 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.inputmethod.InputMethodManager;
25 import androidx.annotation.Nullable;
26 
27 /**
28  * A light version of FocusParkingView in CarUI Lib. It's used by
29  * {@link com.android.car.rotary.RotaryService} to clear the focus highlight in the previous
30  * window when moving focus to another window.
31  * <p>
32  * Unlike FocusParkingView in CarUI Lib, this view shouldn't be placed as the first focusable view
33  * in the window. The general recommendation is to keep this as the last view in the layout.
34  */
35 public class FocusParkingView extends View {
36     /**
37      * This value should not change, even if the actual package containing this class is different.
38      */
39     private static final String FOCUS_PARKING_VIEW_LITE_CLASS_NAME =
40             "com.android.car.rotary.FocusParkingView";
41     /** Action performed on this view to hide the IME. */
42     private static final int ACTION_HIDE_IME = 0x08000000;
FocusParkingView(Context context)43     public FocusParkingView(Context context) {
44         super(context);
45         init();
46     }
FocusParkingView(Context context, @Nullable AttributeSet attrs)47     public FocusParkingView(Context context, @Nullable AttributeSet attrs) {
48         super(context, attrs);
49         init();
50     }
FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)51     public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
52         super(context, attrs, defStyleAttr);
53         init();
54     }
FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)55     public FocusParkingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
56             int defStyleRes) {
57         super(context, attrs, defStyleAttr, defStyleRes);
58         init();
59     }
init()60     private void init() {
61         // This view is focusable, visible and enabled so it can take focus.
62         setFocusable(View.FOCUSABLE);
63         setVisibility(VISIBLE);
64         setEnabled(true);
65         // This view is not clickable so it won't affect the app's behavior when the user clicks on
66         // it by accident.
67         setClickable(false);
68         // This view is always transparent.
69         setAlpha(0f);
70         // Prevent Android from drawing the default focus highlight for this view when it's focused.
71         setDefaultFocusHighlightEnabled(false);
72     }
73     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)74     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
75         // This size of the view is always 1 x 1 pixel, no matter what value is set in the layout
76         // file (match_parent, wrap_content, 100dp, 0dp, etc). Small size is to ensure it has little
77         // impact on the layout, non-zero size is to ensure it can take focus.
78         setMeasuredDimension(1, 1);
79     }
80     @Override
onWindowFocusChanged(boolean hasWindowFocus)81     public void onWindowFocusChanged(boolean hasWindowFocus) {
82         if (!hasWindowFocus) {
83             // We need to clear the focus highlight(by parking the focus on this view)
84             // once the current window goes to background. This can't be done by RotaryService
85             // because RotaryService sees the window as removed, thus can't perform any action
86             // (such as focus, clear focus) on the nodes in the window. So this view has to
87             // grab the focus proactively.
88             super.requestFocus(FOCUS_DOWN, null);
89         }
90         super.onWindowFocusChanged(hasWindowFocus);
91     }
92     @Override
getAccessibilityClassName()93     public CharSequence getAccessibilityClassName() {
94         return FOCUS_PARKING_VIEW_LITE_CLASS_NAME;
95     }
96     @Override
performAccessibilityAction(int action, Bundle arguments)97     public boolean performAccessibilityAction(int action, Bundle arguments) {
98         switch (action) {
99             case ACTION_HIDE_IME:
100                 InputMethodManager inputMethodManager =
101                         getContext().getSystemService(InputMethodManager.class);
102                 return inputMethodManager.hideSoftInputFromWindow(getWindowToken(),
103                         /* flags= */ 0);
104             case ACTION_FOCUS:
105                 // Don't leave this to View to handle as it will exit touch mode.
106                 if (!hasFocus()) {
107                     return super.requestFocus(FOCUS_DOWN, null);
108                 }
109                 return false;
110         }
111         return super.performAccessibilityAction(action, arguments);
112     }
113 }
114