1 /*
2  * Copyright (C) 2017 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.power;
18 
19 import android.car.hardware.power.CarPowerManager;
20 import android.car.settings.CarSettings;
21 import android.content.Context;
22 import android.hardware.display.DisplayManager;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.PowerManager;
26 import android.os.Process;
27 import android.os.SystemClock;
28 import android.provider.Settings;
29 import android.util.Log;
30 import android.util.SparseArray;
31 import android.view.Display;
32 import android.view.DisplayAddress;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.ArrayAdapter;
37 import android.widget.Button;
38 import android.widget.RadioButton;
39 import android.widget.RadioGroup;
40 import android.widget.RadioGroup.OnCheckedChangeListener;
41 import android.widget.Spinner;
42 import android.widget.TextView;
43 
44 import androidx.fragment.app.Fragment;
45 
46 import com.google.android.car.kitchensink.KitchenSinkActivity;
47 import com.google.android.car.kitchensink.R;
48 
49 import java.util.ArrayList;
50 
51 public class PowerTestFragment extends Fragment {
52     private static final boolean DBG = false;
53     private static final String TAG = "PowerTestFragment";
54 
55     private CarPowerManager mCarPowerManager;
56     private DisplayManager mDisplayManager;
57     private Spinner mDisplaySpinner;
58     private ViewGroup mPowerModeViewGroup;
59     private SparseArray<RadioGroup> mRadioGroupList;
60 
61     private static final int MODE_OFF = 0;
62     private static final int MODE_ON = 1;
63     private static final int MODE_ALWAYS_ON = 2;
64 
65     private final CarPowerManager.CarPowerStateListener mPowerListener =
66             (state) -> {
67                 Log.i(TAG, "onStateChanged() state = " + state);
68             };
69 
70     @Override
onCreate(Bundle savedInstanceState)71     public void onCreate(Bundle savedInstanceState) {
72         final Runnable r = () -> {
73             mCarPowerManager = ((KitchenSinkActivity) getActivity()).getPowerManager();
74             try {
75                 mCarPowerManager.setListener(getContext().getMainExecutor(), mPowerListener);
76             } catch (IllegalStateException e) {
77                 Log.e(TAG, "CarPowerManager listener was not cleared");
78             }
79         };
80         ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
81                 new Handler(getContext().getMainLooper()));
82         super.onCreate(savedInstanceState);
83         mDisplayManager = getContext().getSystemService(DisplayManager.class);
84         mRadioGroupList = new SparseArray<>();
85     }
86 
87     @Override
onDestroy()88     public void onDestroy() {
89         super.onDestroy();
90         mCarPowerManager.clearListener();
91     }
92 
93     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)94     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
95         View v = inflater.inflate(R.layout.power_test, container, false);
96 
97         Button b = v.findViewById(R.id.btnPwrRequestShutdown);
98         b.setOnClickListener(this::requestShutdownBtn);
99 
100         b = v.findViewById(R.id.btnPwrShutdown);
101         b.setOnClickListener(this::shutdownBtn);
102 
103         b = v.findViewById(R.id.btnPwrSleep);
104         b.setOnClickListener(this::sleepBtn);
105 
106         b = v.findViewById(R.id.btnDisplayOn);
107         b.setOnClickListener(this::displayOnBtn);
108 
109         b = v.findViewById(R.id.btnDisplayOff);
110         b.setOnClickListener(this::displayOffBtn);
111 
112         mDisplaySpinner = v.findViewById(R.id.display_spinner);
113         mPowerModeViewGroup = v.findViewById(R.id.power_mode_layout);
114 
115         if(DBG) {
116             Log.d(TAG, "Starting PowerTestFragment");
117         }
118 
119         return v;
120     }
121 
122     @Override
onViewCreated(View view, Bundle savedInstanceState)123     public void onViewCreated(View view, Bundle savedInstanceState) {
124         super.onViewCreated(view, savedInstanceState);
125         mDisplaySpinner.setAdapter(new ArrayAdapter<>(getContext(),
126                 android.R.layout.simple_spinner_item, getDisplays()));
127 
128         // Display power mode for each passenger display is set to {@code PowerTestFragment.MODE_ON}
129         // whenever this fragment is created.
130         updateRadioGroups();
131         updateDisplayModeSetting();
132     }
133 
requestShutdownBtn(View v)134     private void requestShutdownBtn(View v) {
135         mCarPowerManager.requestShutdownOnNextSuspend();
136     }
137 
shutdownBtn(View v)138     private void shutdownBtn(View v) {
139         if(DBG) {
140             Log.d(TAG, "Calling shutdown method");
141         }
142         PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
143         pm.shutdown(/* confirm */ false, /* reason */ null, /* wait */ false);
144         Log.d(TAG, "shutdown called!");
145     }
146 
sleepBtn(View v)147     private void sleepBtn(View v) {
148         if(DBG) {
149             Log.d(TAG, "Calling sleep method");
150         }
151         // NOTE:  This doesn't really work to sleep the device.  Actual sleep is implemented via
152         //  SystemInterface via libsuspend::force_suspend()
153         PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
154         pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN,
155                      PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
156     }
157 
displayOnBtn(View v)158     private void displayOnBtn(View v) {
159         if (DBG) {
160             Log.d(TAG, "Calling display on method");
161         }
162         int selectedDisplayId = (Integer) mDisplaySpinner.getSelectedItem();
163         mCarPowerManager.setDisplayPowerState(selectedDisplayId, /* enable */ true);
164     }
165 
displayOffBtn(View v)166     private void displayOffBtn(View v) {
167         if (DBG) {
168             Log.d(TAG, "Calling display off method");
169         }
170         int selectedDisplayId = (Integer) mDisplaySpinner.getSelectedItem();
171         mCarPowerManager.setDisplayPowerState(selectedDisplayId, /* enable */ false);
172     }
173 
updateRadioGroups()174     private void updateRadioGroups() {
175         mPowerModeViewGroup.removeAllViews();
176         for (Display display : mDisplayManager.getDisplays()) {
177             int displayId = display.getDisplayId();
178             if (!getDisplays().contains(displayId)) {
179                 continue;
180             }
181             RadioButton butnOff = new RadioButton(getContext());
182             butnOff.setText("OFF");
183             RadioButton btnOn = new RadioButton(getContext());
184             btnOn.setText("ON");
185             RadioButton btnAlwaysOn = new RadioButton(getContext());
186             btnAlwaysOn.setText("ALWAYS ON");
187 
188             RadioGroup group = new RadioGroup(getContext());
189             group.addView(butnOff, MODE_OFF);
190             group.addView(btnOn, MODE_ON);
191             group.addView(btnAlwaysOn, MODE_ALWAYS_ON);
192             group.check(btnOn.getId());
193             group.setOnCheckedChangeListener(mListener);
194 
195             TextView tv = new TextView(getContext());
196             tv.setText("Display: " + displayId);
197             mPowerModeViewGroup.addView(tv);
198             mPowerModeViewGroup.addView(group);
199             mRadioGroupList.put(displayId, group);
200         }
201     }
202 
updateDisplayModeSetting()203     private void updateDisplayModeSetting() {
204         StringBuilder sb = new StringBuilder();
205         int displayPort = getDisplayPort(Display.DEFAULT_DISPLAY);
206         sb.append(displayPort).append(":").append(MODE_ALWAYS_ON);
207         for (int i = 0; i < mRadioGroupList.size(); i++) {
208             if (sb.length() != 0) {
209                 sb.append(",");
210             }
211             int displayId = mRadioGroupList.keyAt(i);
212             RadioGroup group = mRadioGroupList.get(displayId);
213             RadioButton btnMode = group.findViewById(group.getCheckedRadioButtonId());
214             int mode = textToValue(btnMode.getText().toString());
215 
216             displayPort = getDisplayPort(displayId);
217             sb.append(displayPort).append(":").append(mode);
218         }
219         String value = sb.toString();
220         if (DBG) {
221             Log.d(TAG, "Setting value to " + value);
222         }
223         Settings.Global.putString(getContext().getContentResolver(),
224                 CarSettings.Global.DISPLAY_POWER_MODE, value);
225     }
226 
getDisplayPort(int displayId)227     private int getDisplayPort(int displayId) {
228         Display display = mDisplayManager.getDisplay(displayId);
229         if (display != null) {
230             DisplayAddress address = display.getAddress();
231             if (address instanceof DisplayAddress.Physical) {
232                 DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
233                 return physicalAddress.getPort();
234             }
235         }
236         return Display.INVALID_DISPLAY;
237     }
238 
239     private OnCheckedChangeListener mListener = new OnCheckedChangeListener() {
240         @Override
241         public void onCheckedChanged(RadioGroup group, int checkedId) {
242             updateDisplayModeSetting();
243         }
244     };
245 
246     DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
247         @Override
248         public void onDisplayAdded(int displayId) {
249             updateRadioGroups();
250         }
251 
252         @Override
253         public void onDisplayRemoved(int displayId) {
254             updateRadioGroups();
255         }
256 
257         @Override
258         public void onDisplayChanged(int displayId) {
259             // do nothing
260         }
261     };
262 
textToValue(String mode)263     private static int textToValue(String mode) {
264         switch (mode) {
265             case "OFF":
266                 return MODE_OFF;
267             case "ON":
268                 return MODE_ON;
269             case "ALWAYS ON":
270             default:
271                 return MODE_ALWAYS_ON;
272         }
273     }
274 
getDisplays()275     private ArrayList<Integer> getDisplays() {
276         ArrayList<Integer> displayIds = new ArrayList<>();
277         Display[] displays = mDisplayManager.getDisplays();
278         int uidSelf = Process.myUid();
279         for (Display disp : displays) {
280             if (!disp.hasAccess(uidSelf)) {
281                 continue;
282             }
283             if (disp.getDisplayId() == Display.DEFAULT_DISPLAY) {
284                 continue;
285             }
286             displayIds.add(disp.getDisplayId());
287         }
288         return displayIds;
289     }
290 }
291