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