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.display; 18 19 import android.annotation.Nullable; 20 import android.app.ActivityManager; 21 import android.content.Context; 22 import android.content.pm.UserInfo; 23 import android.os.UserManager; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.Display; 27 import android.view.View; 28 import android.widget.Button; 29 import android.widget.EditText; 30 import android.widget.LinearLayout; 31 32 import com.google.android.car.kitchensink.R; 33 import com.google.android.car.kitchensink.users.UsersSpinner; 34 35 import java.io.PrintWriter; 36 import java.util.List; 37 38 /** 39 * Custom view that hosts a virtual display and a button to create / remove it 40 */ 41 42 public final class SelfManagedVirtualDisplayView extends LinearLayout { 43 44 private static final String TAG = SelfManagedVirtualDisplayView.class.getSimpleName(); 45 46 private static final String NO_ID_TEXT = "N/A"; 47 48 private final VirtualDisplayView mVirtualDisplayView; 49 50 private final LinearLayout mHeader; 51 private final EditText mDisplayIdEditText; 52 private final Button mCreateDisplayButton; 53 private final Button mDeleteDisplayButton; 54 private final UsersSpinner mUsersSpinner; 55 private final Button mSwitchUserButton; 56 57 private int mDisplayId = Display.INVALID_DISPLAY; 58 59 @Nullable 60 private List<UserInfo> mUsers; 61 SelfManagedVirtualDisplayView(Context context, AttributeSet attrs)62 public SelfManagedVirtualDisplayView(Context context, AttributeSet attrs) { 63 this(context, VirtualDisplayView.getName(context, attrs)); 64 } 65 SelfManagedVirtualDisplayView(Context context, String name)66 public SelfManagedVirtualDisplayView(Context context, String name) { 67 super(context); 68 69 inflate(context, R.layout.self_managed_virtual_display_view, this); 70 71 mHeader = findViewById(R.id.header); 72 mVirtualDisplayView = findViewById(R.id.virtual_display); 73 mDisplayIdEditText = findViewById(R.id.display_id); 74 mCreateDisplayButton = findViewById(R.id.create); 75 mDeleteDisplayButton = findViewById(R.id.delete); 76 77 mUsersSpinner = findViewById(R.id.users); 78 mSwitchUserButton = findViewById(R.id.switch_user); 79 80 if (name != null) { 81 mVirtualDisplayView.setName(name); 82 } 83 toggleCreateDeleteButtons(/* create= */ true); 84 mDisplayIdEditText.setText(NO_ID_TEXT); 85 mCreateDisplayButton.setOnClickListener((v) -> createDisplayAndToastMessage()); 86 mDeleteDisplayButton.setOnClickListener((v) -> deleteDisplayAndToastMessage()); 87 88 mSwitchUserButton.setOnClickListener((v) -> switchUser()); 89 setUserSwitchingVisible(false); 90 } 91 92 // TODO: ideally it should be part of the constructor / AttributeSet, and it should use a 93 // boolean to indicated it's enabled (rather than relying on mUsers being null) enableUserSwitching()94 void enableUserSwitching() { 95 mUsers = getContext().getSystemService(UserManager.class).getAliveUsers(); 96 Log.d(TAG, "Enabling user switching. Users = " + mUsers); 97 mUsersSpinner.init(mUsers); 98 } 99 setUserSwitchingVisible(boolean visible)100 private void setUserSwitchingVisible(boolean visible) { 101 int visibility = visible && mUsers != null ? View.VISIBLE : View.GONE; 102 mUsersSpinner.setVisibility(visibility); 103 mSwitchUserButton.setVisibility(visibility); 104 } 105 switchUser()106 private void switchUser() { 107 UserInfo user = mUsersSpinner.getSelectedUser(); 108 Log.d(TAG, "Starting user " + user.toFullString() + " on display " + mDisplayId); 109 try { 110 boolean started = mContext.getSystemService(ActivityManager.class) 111 .startUserInBackgroundVisibleOnDisplay(user.id, mDisplayId); 112 ToastUtils.logAndToastMessage(getContext(), "%s user %d on display %d", 113 (started ? "Started" : "Failed to start"), user.id, mDisplayId); 114 } catch (Exception e) { 115 ToastUtils.logAndToastError(getContext(), e, 116 "Error starting user %d on display %d", user.id, mDisplayId); 117 } 118 } 119 setHeaderVisible(boolean visible)120 void setHeaderVisible(boolean visible) { 121 toggleView(mHeader, visible); 122 } 123 getName()124 String getName() { 125 return mVirtualDisplayView.getName(); 126 } 127 release()128 void release() { 129 mVirtualDisplayView.release(); 130 } 131 dump(String prefix, PrintWriter writer, String[] args)132 void dump(String prefix, PrintWriter writer, String[] args) { 133 dumpView(prefix, writer, mHeader, "Buttons panel"); 134 dumpView(prefix, writer, mCreateDisplayButton, "Create display button"); 135 dumpView(prefix, writer, mDeleteDisplayButton, "Delete display button"); 136 137 writer.printf("%sDisplay id: %s\n", prefix, mDisplayIdEditText.getText()); 138 writer.printf("%sVirtual Display View:\n", prefix); 139 mVirtualDisplayView.dump(prefix + " ", writer); 140 } 141 createDisplayAndToastMessage()142 private void createDisplayAndToastMessage() { 143 Log.i(TAG, "Creating virtual display"); 144 try { 145 createDisplay(); 146 ToastUtils.logAndToastMessage(getContext(), 147 "Created virtual display with id %d", mDisplayId); 148 } catch (Exception e) { 149 ToastUtils.logAndToastError(getContext(), e, "Failed to create virtual display"); 150 } 151 } 152 153 /** 154 * Creates the display and return its id. 155 */ createDisplay()156 int createDisplay() { 157 mDisplayId = mVirtualDisplayView.createVirtualDisplay(); 158 mDisplayIdEditText.setText(String.valueOf(mDisplayId)); 159 toggleCreateDeleteButtons(/* create= */ false); 160 setUserSwitchingVisible(/* visible= */ true); 161 return mDisplayId; 162 } 163 deleteDisplayAndToastMessage()164 private void deleteDisplayAndToastMessage() { 165 Log.i(TAG, "Deleting display"); 166 try { 167 deleteDisplay(); 168 ToastUtils.logAndToastMessage(getContext(), "Virtual display deleted"); 169 } catch (Exception e) { 170 ToastUtils.logAndToastError(getContext(), e, "Failed to delete virtual display"); 171 } 172 } 173 174 /** 175 * Deletes the display. 176 */ deleteDisplay()177 void deleteDisplay() { 178 mVirtualDisplayView.deleteVirtualDisplay(); 179 mDisplayId = Display.INVALID_DISPLAY; 180 mDisplayIdEditText.setText(NO_ID_TEXT); 181 toggleCreateDeleteButtons(/* create= */ true); 182 setUserSwitchingVisible(/* visible= */ false); 183 } 184 toggleCreateDeleteButtons(boolean create)185 private void toggleCreateDeleteButtons(boolean create) { 186 toggleView(mCreateDisplayButton, create); 187 toggleView(mDeleteDisplayButton, !create); 188 } 189 190 // TODO(b/231499090): move plumbing below to common code 191 dumpView(String prefix, PrintWriter writer, View view, String name)192 private void dumpView(String prefix, PrintWriter writer, View view, String name) { 193 writer.printf("%s%s: %s %s\n", prefix, name, 194 (view.isEnabled() ? "enabled" : "disabled"), 195 visibilityToString(view.getVisibility())); 196 } 197 toggleView(View view, boolean on)198 private void toggleView(View view, boolean on) { 199 view.setEnabled(on); 200 view.setVisibility(on ? View.VISIBLE : View.GONE); 201 } 202 printMessage(PrintWriter writer, String format, Object... args)203 protected void printMessage(PrintWriter writer, String format, Object... args) { 204 String message = String.format(format, args); 205 writer.printf("%s\n", message); 206 } 207 printError(PrintWriter writer, Exception e, String format, Object... args)208 protected void printError(PrintWriter writer, Exception e, String format, Object... args) { 209 String message = String.format(format, args); 210 if (e != null) { 211 writer.printf("%s: %s\n", message, e); 212 } else { 213 writer.printf("%s\n", message); 214 } 215 } 216 visibilityToString(int visibility)217 public static String visibilityToString(int visibility) { 218 switch (visibility) { 219 case View.VISIBLE: 220 return "VISIBLE"; 221 case View.INVISIBLE: 222 return "INVISIBLE"; 223 case View.GONE: 224 return "GONE"; 225 default: 226 return "UNKNOWN-" + visibility; 227 } 228 } 229 } 230