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