• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.systemui.car.displaycompat;
17 
18 import static android.car.Car.PERMISSION_MANAGE_DISPLAY_COMPATIBILITY;
19 import static android.view.Display.DEFAULT_DISPLAY;
20 import static android.view.View.GONE;
21 import static android.view.View.VISIBLE;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.ActivityManager.RunningTaskInfo;
26 import android.car.content.pm.CarPackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.hardware.input.InputManager;
29 import android.hardware.input.InputManagerGlobal;
30 import android.os.Handler;
31 import android.os.SystemClock;
32 import android.util.Log;
33 import android.view.KeyCharacterMap;
34 import android.view.KeyEvent;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.ImageButton;
38 
39 import androidx.annotation.MainThread;
40 import androidx.annotation.RequiresPermission;
41 
42 import com.android.systemui.R;
43 import com.android.systemui.car.CarServiceProvider;
44 import com.android.systemui.dagger.qualifiers.Main;
45 import com.android.systemui.dagger.qualifiers.UiBackground;
46 
47 import javax.inject.Inject;
48 
49 /**
50  * Implementation of {@link ToolbarController} for showing/hiding the display compatibility toolbar.
51  */
52 public class ToolbarControllerImpl implements ToolbarController {
53 
54     private static final String TAG = ToolbarControllerImpl.class.getSimpleName();
55 
56     private ViewGroup mToolbarParent;
57 
58     @NonNull
59     private final Handler mMainHandler;
60 
61     @NonNull
62     private CarServiceProvider mCarServiceProvider;
63     @Nullable
64     private CarPackageManager mCarPackageManager;
65     @NonNull
66     @UiBackground
67     private final CarServiceProvider.CarServiceOnConnectedListener mCarServiceLifecycleListener =
68             car -> {
69                 mCarPackageManager = car.getCarManager(CarPackageManager.class);
70             };
71     @Nullable
72     private ImageButton mBackButton;
73 
74     @Inject
ToolbarControllerImpl(@onNull @ain Handler mainHandler, CarServiceProvider carServiceProvider)75     public ToolbarControllerImpl(@NonNull @Main Handler mainHandler,
76             CarServiceProvider carServiceProvider) {
77         mMainHandler = mainHandler;
78         mCarServiceProvider = carServiceProvider;
79     }
80 
81     /**
82      * Needs to be called before calling any other method.
83      */
84     @Override
init(@onNull ViewGroup parent)85     public void init(@NonNull ViewGroup parent) {
86         mToolbarParent = parent;
87         mCarServiceProvider.addListener(mCarServiceLifecycleListener);
88     }
89 
90     @MainThread
91     @Override
show()92     public void show() {
93         if (mToolbarParent == null) {
94             Log.w(TAG, "init was not called");
95             return;
96         }
97         if (mBackButton == null) {
98             // Can't do this in init method because that's called when the window is available way
99             // before the views are inflated.
100             mBackButton = mToolbarParent.findViewById(R.id.back_btn);
101             if (mBackButton != null) {
102                 mBackButton.setOnClickListener(v -> {
103                     sendVirtualBackPress();
104                 });
105             }
106         }
107         mToolbarParent.setVisibility(VISIBLE);
108         View actionBar = mToolbarParent.findViewById(R.id.action_bar);
109         if (actionBar != null) {
110             actionBar.setVisibility(VISIBLE);
111         }
112     }
113 
114     @MainThread
115     @Override
hide()116     public void hide() {
117         if (mToolbarParent == null) {
118             Log.w(TAG, "init was not called");
119             return;
120         }
121         mToolbarParent.setVisibility(GONE);
122         View actionBar = mToolbarParent.findViewById(R.id.action_bar);
123         if (actionBar != null) {
124             actionBar.setVisibility(GONE);
125         }
126     }
127 
128     @RequiresPermission(allOf = {PERMISSION_MANAGE_DISPLAY_COMPATIBILITY,
129             android.Manifest.permission.QUERY_ALL_PACKAGES})
130     @Override
update(@onNull RunningTaskInfo taskInfo)131     public void update(@NonNull RunningTaskInfo taskInfo) {
132         if (mToolbarParent == null) {
133             Log.w(TAG, "init was not called");
134             return;
135         }
136         if (requiresDisplayCompat(getPackageName(taskInfo))
137                 && taskInfo.displayId == DEFAULT_DISPLAY) {
138             mMainHandler.post(() -> show());
139             return;
140         }
141         mMainHandler.post(() -> hide());
142     }
143 
getPackageName(RunningTaskInfo taskInfo)144     private String getPackageName(RunningTaskInfo taskInfo) {
145         if (taskInfo.topActivity != null) {
146             return taskInfo.topActivity.getPackageName();
147         }
148         return taskInfo.baseIntent.getComponent().getPackageName();
149     }
150 
151     @RequiresPermission(allOf = {PERMISSION_MANAGE_DISPLAY_COMPATIBILITY,
152             android.Manifest.permission.QUERY_ALL_PACKAGES})
requiresDisplayCompat(String packageName)153     private boolean requiresDisplayCompat(String packageName) {
154         boolean result = false;
155         if (mCarPackageManager != null) {
156             try {
157                 result = mCarPackageManager.requiresDisplayCompat(packageName);
158             } catch (NameNotFoundException e) {
159             }
160         } else {
161             Log.w(TAG, "CarPackageManager is not set.");
162         }
163         return result;
164     }
165 
166     /**
167      * Send both action down and up to be qualified as a back press. Set time for key events, so
168      * they are not staled.
169      */
sendVirtualBackPress()170     public static void sendVirtualBackPress() {
171         long downEventTime = SystemClock.uptimeMillis();
172         long upEventTime = downEventTime + 1;
173 
174         final KeyEvent keydown = new KeyEvent(downEventTime, downEventTime, KeyEvent.ACTION_DOWN,
175                 KeyEvent.KEYCODE_BACK, /* repeat= */ 0, /* metaState= */ 0,
176                 KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, KeyEvent.FLAG_FROM_SYSTEM);
177         final KeyEvent keyup = new KeyEvent(upEventTime, upEventTime, KeyEvent.ACTION_UP,
178                 KeyEvent.KEYCODE_BACK, /* repeat= */ 0, /* metaState= */ 0,
179                 KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, KeyEvent.FLAG_FROM_SYSTEM);
180 
181         InputManagerGlobal inputManagerGlobal = InputManagerGlobal.getInstance();
182         inputManagerGlobal.injectInputEvent(keydown, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
183         inputManagerGlobal.injectInputEvent(keyup, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
184     }
185 }
186