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