1 /* 2 * Copyright (C) 2022 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.google.android.car.kitchensink.input; 18 19 import static android.car.Car.CAR_OCCUPANT_ZONE_SERVICE; 20 import static android.car.Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER; 21 import static android.car.CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.car.Car; 26 import android.car.CarOccupantZoneManager; 27 import android.car.settings.CarSettings; 28 import android.content.ContentResolver; 29 import android.database.ContentObserver; 30 import android.hardware.display.DisplayManager; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 import android.util.ArraySet; 38 import android.util.Log; 39 import android.view.Display; 40 import android.view.LayoutInflater; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.widget.ListView; 44 45 import androidx.fragment.app.Fragment; 46 47 import com.google.android.car.kitchensink.R; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 52 public final class DisplayInputLockTestFragment extends Fragment { 53 private static final String TAG = "DisplayInputLock"; 54 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 55 56 private Car mCar; 57 private CarOccupantZoneManager mOccupantZoneManager; 58 private DisplayManager mDisplayManager; 59 60 // Array of display unique ids from the display input lock setting. 61 private final ArraySet<String> mDisplayInputLockSetting = new ArraySet<>(); 62 63 private final ArrayList<DisplayInputLockItem> mDisplayInputLockItems = new ArrayList<>(); 64 private DisplayInputLockListAdapter mDisplayInputLockListAdapter; 65 66 private final DisplayManager.DisplayListener mDisplayListener = 67 new DisplayManager.DisplayListener() { 68 @Override 69 public void onDisplayAdded(int displayId) { 70 if (DEBUG) { 71 Log.d(TAG, "onDisplayAdded: display " + displayId); 72 } 73 Display display = mDisplayManager.getDisplay(displayId); 74 mDisplayInputLockItems.add(new DisplayInputLockItem(displayId, 75 mDisplayInputLockSetting.contains(display.getUniqueId()))); 76 mDisplayInputLockListAdapter.setListItems(mDisplayInputLockItems); 77 } 78 79 @Override 80 public void onDisplayRemoved(int displayId) { 81 if (DEBUG) { 82 Log.d(TAG, "onDisplayRemoved: display " + displayId); 83 } 84 mDisplayInputLockItems.removeIf(item -> item.mDisplayId == displayId); 85 mDisplayInputLockListAdapter.setListItems(mDisplayInputLockItems); 86 } 87 88 @Override 89 public void onDisplayChanged(int displayId) { 90 } 91 }; 92 93 static class DisplayInputLockItem { 94 public final int mDisplayId; 95 public boolean mIsLockEnabled; 96 DisplayInputLockItem(int displayId, boolean isLockEnabled)97 DisplayInputLockItem(int displayId, boolean isLockEnabled) { 98 mDisplayId = displayId; 99 mIsLockEnabled = isLockEnabled; 100 } 101 } 102 103 @Override onCreate(Bundle savedInstanceState)104 public void onCreate(Bundle savedInstanceState) { 105 super.onCreate(savedInstanceState); 106 mDisplayManager = getContext().getSystemService(DisplayManager.class); 107 } 108 109 @Nullable 110 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)111 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 112 @Nullable Bundle savedInstanceState) { 113 View view = inflater.inflate(R.layout.display_input_lock_test_fragment, container, 114 /* root= */ false); 115 connectCar(); 116 117 ListView inputLockListView = view.findViewById(R.id.display_input_lock_list); 118 initDisplayInputLockData(); 119 mDisplayInputLockListAdapter = new DisplayInputLockListAdapter(getContext(), 120 mDisplayInputLockItems, this); 121 inputLockListView.setAdapter(mDisplayInputLockListAdapter); 122 mDisplayManager.registerDisplayListener(mDisplayListener, /* handler= */ null); 123 Uri uri = Settings.Global.getUriFor(CarSettings.Global.DISPLAY_INPUT_LOCK); 124 getContext().getContentResolver() 125 .registerContentObserver(uri, /* notifyForDescendants= */ false, mSettingObserver); 126 127 return view; 128 } 129 130 @Override onDestroyView()131 public void onDestroyView() { 132 getContext().getContentResolver().unregisterContentObserver(mSettingObserver); 133 mDisplayManager.unregisterDisplayListener(mDisplayListener); 134 if (mCar != null && mCar.isConnected()) { 135 mCar.disconnect(); 136 mCar = null; 137 } 138 super.onDestroyView(); 139 } 140 141 /** 142 * Requests update to the display input lock setting value. 143 * 144 * @param displayId The display for which the input lock is updated. 145 * @param enabled Whether to enable the display input lock. 146 */ requestUpdateDisplayInputLockSetting(int displayId, boolean enabled)147 public void requestUpdateDisplayInputLockSetting(int displayId, boolean enabled) { 148 String displayUniqueId = getDisplayUniqueId(displayId); 149 if (mDisplayInputLockSetting.contains(displayUniqueId) == enabled) { 150 return; 151 } 152 if (DEBUG) { 153 Log.d(TAG, "requestUpdateDisplayInputLockSetting: displayId=" + displayId 154 + ", enabled=" + enabled); 155 } 156 if (enabled) { 157 mDisplayInputLockSetting.add(displayUniqueId); 158 } else { 159 mDisplayInputLockSetting.remove(displayUniqueId); 160 } 161 mDisplayInputLockItems.stream().filter(item -> item.mDisplayId == displayId) 162 .findAny().ifPresent(item -> item.mIsLockEnabled = enabled); 163 writeDisplayInputLockSetting(getContext().getContentResolver(), 164 CarSettings.Global.DISPLAY_INPUT_LOCK, 165 makeDisplayInputLockSetting(mDisplayInputLockSetting)); 166 } 167 168 @Nullable getDisplayInputLockSetting(@onNull ContentResolver resolver)169 private String getDisplayInputLockSetting(@NonNull ContentResolver resolver) { 170 return Settings.Global.getString(resolver, 171 CarSettings.Global.DISPLAY_INPUT_LOCK); 172 } 173 writeDisplayInputLockSetting(@onNull ContentResolver resolver, @NonNull String settingKey, @NonNull String value)174 private void writeDisplayInputLockSetting(@NonNull ContentResolver resolver, 175 @NonNull String settingKey, @NonNull String value) { 176 Settings.Global.putString(resolver, settingKey, value); 177 } 178 makeDisplayInputLockSetting(@ullable ArraySet<String> inputLockSetting)179 private String makeDisplayInputLockSetting(@Nullable ArraySet<String> inputLockSetting) { 180 if (inputLockSetting == null) { 181 return ""; 182 } 183 184 String settingValue = TextUtils.join(",", inputLockSetting); 185 if (DEBUG) { 186 Log.d(TAG, "makeDisplayInputLockSetting(): add new input lock setting: " 187 + settingValue); 188 } 189 return settingValue; 190 } 191 getDisplayUniqueId(int displayId)192 private String getDisplayUniqueId(int displayId) { 193 Display display = mDisplayManager.getDisplay(displayId); 194 if (display == null) { 195 return ""; 196 } 197 return display.getUniqueId(); 198 } 199 findDisplayIdByUniqueId(@onNull String displayUniqueId, Display[] displays)200 private int findDisplayIdByUniqueId(@NonNull String displayUniqueId, Display[] displays) { 201 for (int i = 0; i < displays.length; i++) { 202 Display display = displays[i]; 203 if (displayUniqueId.equals(display.getUniqueId())) { 204 return display.getDisplayId(); 205 } 206 } 207 return Display.INVALID_DISPLAY; 208 } 209 parseDisplayInputLockSettingValue(@onNull String settingKey, @Nullable String value)210 private void parseDisplayInputLockSettingValue(@NonNull String settingKey, 211 @Nullable String value) { 212 mDisplayInputLockSetting.clear(); 213 if (value == null || value.isEmpty()) { 214 return; 215 } 216 217 Display[] displays = mDisplayManager.getDisplays(); 218 String[] entries = value.split(","); 219 for (String uniqueId : entries) { 220 if (findDisplayIdByUniqueId(uniqueId, displays) == Display.INVALID_DISPLAY) { 221 Log.w(TAG, "Invalid display id: " + uniqueId); 222 continue; 223 } 224 mDisplayInputLockSetting.add(uniqueId); 225 } 226 } 227 initDisplayInputLockData()228 private void initDisplayInputLockData() { 229 // Read a setting value from the global setting of rear seat input lock. 230 String settingValue = getDisplayInputLockSetting(getContext().getContentResolver()); 231 parseDisplayInputLockSettingValue(CarSettings.Global.DISPLAY_INPUT_LOCK, settingValue); 232 233 List<CarOccupantZoneManager.OccupantZoneInfo> zonelist = 234 mOccupantZoneManager.getAllOccupantZones(); 235 // Make input lock list items for passenger displays with the setting value. 236 mDisplayInputLockItems.clear(); 237 for (CarOccupantZoneManager.OccupantZoneInfo zone : zonelist) { 238 if (zone.occupantType == OCCUPANT_TYPE_DRIVER) { 239 continue; 240 } 241 242 Display display = mOccupantZoneManager.getDisplayForOccupant(zone, 243 CarOccupantZoneManager.DISPLAY_TYPE_MAIN); 244 if (display != null) { 245 int displayId = display.getDisplayId(); 246 mDisplayInputLockItems.add(new DisplayInputLockItem(displayId, 247 mDisplayInputLockSetting.contains(display.getUniqueId()))); 248 } 249 } 250 } 251 updateDisplayInputLockData()252 private boolean updateDisplayInputLockData() { 253 // Read a new setting value from the global setting of rear seat input lock. 254 String settingValue = getDisplayInputLockSetting(getContext().getContentResolver()); 255 ArraySet<String> oldInputLockSetting = new ArraySet<>(mDisplayInputLockSetting); 256 parseDisplayInputLockSettingValue(CarSettings.Global.DISPLAY_INPUT_LOCK, settingValue); 257 258 if (mDisplayInputLockSetting.equals(oldInputLockSetting)) { 259 // Input lock setting is same, no need to update UI. 260 if (DEBUG) { 261 Log.d(TAG, "Input lock setting is same, no need to update UI."); 262 } 263 return false; 264 } 265 266 // Update input lock items from the setting value. 267 for (DisplayInputLockItem item : mDisplayInputLockItems) { 268 item.mIsLockEnabled = mDisplayInputLockSetting.contains( 269 getDisplayUniqueId(item.mDisplayId)); 270 } 271 272 return true; 273 } 274 refreshDisplayInputLockList()275 private void refreshDisplayInputLockList() { 276 if (updateDisplayInputLockData()) { 277 mDisplayInputLockListAdapter.setListItems(mDisplayInputLockItems); 278 } 279 } 280 281 private final ContentObserver mSettingObserver = 282 new ContentObserver(new Handler(Looper.getMainLooper())) { 283 @Override 284 public void onChange(boolean selfChange, Uri uri) { 285 super.onChange(selfChange, uri); 286 if (isResumed()) { 287 if (DEBUG) { 288 Log.d(TAG, "Content has changed for URI " + uri); 289 } 290 refreshDisplayInputLockList(); 291 } 292 } 293 }; 294 connectCar()295 private void connectCar() { 296 mCar = Car.createCar(getContext(), /* handler= */ null, 297 CAR_WAIT_TIMEOUT_WAIT_FOREVER, (car, ready) -> { 298 if (!ready) { 299 return; 300 } 301 mOccupantZoneManager = (CarOccupantZoneManager) 302 car.getCarManager(CAR_OCCUPANT_ZONE_SERVICE); 303 }); 304 } 305 } 306