1 /*
2  * Copyright (C) 2015 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.compatibility.common.deviceinfo;
17 
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.hardware.devicestate.DeviceState;
22 import android.hardware.devicestate.DeviceStateManager;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.server.wm.jetpack.extensions.util.ExtensionsUtil;
26 import android.server.wm.jetpack.extensions.util.SidecarUtil;
27 import android.server.wm.jetpack.extensions.util.Version;
28 import android.util.DisplayMetrics;
29 import android.view.Display;
30 import android.view.WindowManager;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.RequiresApi;
35 
36 import com.android.compatibility.common.util.ApiLevelUtil;
37 import com.android.compatibility.common.util.DeviceInfoStore;
38 import com.android.compatibility.common.util.DummyActivity;
39 
40 import java.io.IOException;
41 import java.lang.reflect.Method;
42 import java.util.Arrays;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.Optional;
46 
47 /**
48  * Screen device info collector.
49  */
50 public final class ScreenDeviceInfo extends DeviceInfo {
51 
52     @Override
collectDeviceInfo(DeviceInfoStore store)53     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
54         DisplayMetrics metrics = new DisplayMetrics();
55         WindowManager windowManager =
56                 (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
57 
58         Display display = windowManager.getDefaultDisplay();
59         display.getRealMetrics(metrics);
60 
61         store.addResult("width_pixels", metrics.widthPixels);
62         store.addResult("height_pixels", metrics.heightPixels);
63         store.addResult("x_dpi", metrics.xdpi);
64         store.addResult("y_dpi", metrics.ydpi);
65         store.addResult("density", metrics.density);
66         store.addResult("density_dpi", metrics.densityDpi);
67 
68         Configuration configuration = getContext().getResources().getConfiguration();
69         store.addResult("screen_size", getScreenSize(configuration));
70         store.addResult("smallest_screen_width_dp", configuration.smallestScreenWidthDp);
71 
72         // Add WindowManager Jetpack Library version and available display features.
73         addDisplayFeaturesIfPresent(store);
74 
75         addPhysicalResolutionAndRefreshRate(store, display);
76 
77         // Add device states from DeviceStateManager if available.
78         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
79             addDeviceStatesIfAvailable(store);
80         }
81     }
82 
addDisplayFeaturesIfPresent(DeviceInfoStore store)83     private void addDisplayFeaturesIfPresent(DeviceInfoStore store) throws Exception {
84         // Try to get display features from extensions. If extensions is not present, try sidecar.
85         // If neither is available, do nothing.
86         // TODO (b/202855636) store info from both extensions and sidecar if both are present
87         if (ExtensionsUtil.isExtensionVersionValid()) {
88             // Extensions is available on device.
89             final int extensionVersion = ExtensionsUtil.getExtensionVersion();
90             store.addResult("wm_jetpack_version",
91                     "[Extensions]" + extensionVersion);
92             final Activity activity = ScreenDeviceInfo.this.launchActivity(
93                     "com.android.compatibility.common.deviceinfo",
94                     DummyActivity.class,
95                     new Bundle());
96             int[] displayFeatureTypes = ExtensionsUtil.getExtensionDisplayFeatureTypes(activity);
97             store.addArrayResult("display_features", displayFeatureTypes);
98         } else if (SidecarUtil.isSidecarVersionValid()) {
99             // Sidecar is available on device.
100             final Version sidecarVersion = SidecarUtil.getSidecarVersion();
101             store.addResult("wm_jetpack_version", "[Sidecar]" + sidecarVersion.toString());
102             final Activity activity = ScreenDeviceInfo.this.launchActivity(
103                     "com.android.compatibility.common.deviceinfo",
104                     DummyActivity.class,
105                     new Bundle());
106             int[] displayFeatureTypes = SidecarUtil.getSidecarDisplayFeatureTypes(activity);
107             store.addArrayResult("display_features", displayFeatureTypes);
108         }
109     }
110 
111     @RequiresApi(Build.VERSION_CODES.S)
addDeviceStatesIfAvailable(DeviceInfoStore store)112     private void addDeviceStatesIfAvailable(DeviceInfoStore store) throws IOException {
113         DeviceStateManager deviceStateManager = getContext().getSystemService(
114                 DeviceStateManager.class);
115 
116         // Get the supported device states on device if DeviceStateManager is available
117         if (deviceStateManager != null) {
118             store.addArrayResult("device_states", getDeviceStateIdentifiers(deviceStateManager));
119         }
120     }
121 
122     /**
123      * Returns the array of device state identifiers from {@link DeviceStateManager}. Due to GTS and
124      * ATS running tests on many different sdk-levels, this method may be running on a newer or
125      * older Android version, possibly bringing in issues if {@link DeviceStateManager}'s API
126      * surface has changed. This method uses reflection to call the correct API if that has
127      * occurred.
128      *
129      * b/329875626 for reference.
130      */
131     @NonNull
getDeviceStateIdentifiers(@onNull DeviceStateManager deviceStateManager)132     private int[] getDeviceStateIdentifiers(@NonNull DeviceStateManager deviceStateManager) {
133         try {
134             final List<DeviceState> deviceStates = deviceStateManager.getSupportedDeviceStates();
135             final int[] identifiers = new int[deviceStates.size()];
136             for (int i = 0; i < deviceStates.size(); i++) {
137                 identifiers[i] = deviceStates.get(i).getIdentifier();
138             }
139             return identifiers;
140         } catch (NoSuchMethodError e) {
141             return getDeviceStateIdentifiersUsingReflection(deviceStateManager);
142         }
143     }
144 
145     /**
146      * Attempts to retrieve the array of device state identifiers from {@link DeviceStateManager}
147      * using reflection.
148      */
149     @NonNull
getDeviceStateIdentifiersUsingReflection( @onNull DeviceStateManager deviceStateManager)150     private int[] getDeviceStateIdentifiersUsingReflection(
151             @NonNull DeviceStateManager deviceStateManager) {
152         Method getSupportedStatesMethod = getMethod(deviceStateManager.getClass(),
153                 "getSupportedStates");
154         if (getSupportedStatesMethod == null) {
155             return new int[0];
156         }
157 
158         try {
159             final int[] identifiers = (int[]) getSupportedStatesMethod.invoke(deviceStateManager);
160             return identifiers;
161         } catch (Exception ignored) {
162             return new int[0];
163         }
164     }
165 
166     /**
167      * Returns the {@link Method} for the provided {@code methodName} on the provided
168      * {@code classToCheck}. If that method does not exist, return {@code null};
169      */
170     @Nullable
getMethod(@onNull Class<?> classToCheck, @NonNull String methodName)171     private Method getMethod(@NonNull Class<?> classToCheck, @NonNull String methodName) {
172         try {
173             return classToCheck.getMethod(methodName);
174         } catch (NoSuchMethodException e) {
175             return null;
176         }
177     }
178 
addPhysicalResolutionAndRefreshRate( DeviceInfoStore store, Display display)179     private static void addPhysicalResolutionAndRefreshRate(
180             DeviceInfoStore store, Display display) throws IOException {
181         if (ApiLevelUtil.isBefore(Build.VERSION_CODES.M)) {
182             return;
183         }
184 
185         // Add properties of an active display mode.
186         Display.Mode activeMode = display.getMode();
187         addDisplayModeProperties(store, activeMode, "");
188 
189         // Add properties of a supported mode with max resolution and refresh rate.
190         Display.Mode[] supportedModes = display.getSupportedModes();
191         Comparator<Display.Mode> modeComparator = Comparator.comparingInt(
192                 Display.Mode::getPhysicalWidth).thenComparingInt(
193                 Display.Mode::getPhysicalHeight).thenComparingDouble(
194                 Display.Mode::getRefreshRate);
195         Optional<Display.Mode> maxMode = Arrays.stream(supportedModes).max(modeComparator);
196         if (maxMode.isPresent()) {
197             addDisplayModeProperties(store, maxMode.get(), "max_");
198         }
199     }
200 
addDisplayModeProperties( DeviceInfoStore store, Display.Mode mode, String propertyPrefix)201     private static void addDisplayModeProperties(
202             DeviceInfoStore store, Display.Mode mode, String propertyPrefix) throws IOException {
203         store.addResult(propertyPrefix + "physical_width_pixels", mode.getPhysicalWidth());
204         store.addResult(propertyPrefix + "physical_height_pixels", mode.getPhysicalHeight());
205         store.addResult(propertyPrefix + "refresh_rate", mode.getRefreshRate());
206     }
207 
getScreenSize(Configuration configuration)208     private static String getScreenSize(Configuration configuration) {
209         int screenLayout = configuration.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
210         String screenSize = String.format("0x%x", screenLayout);
211         switch (screenLayout) {
212             case Configuration.SCREENLAYOUT_SIZE_SMALL:
213                 screenSize = "small";
214                 break;
215 
216             case Configuration.SCREENLAYOUT_SIZE_NORMAL:
217                 screenSize = "normal";
218                 break;
219 
220             case Configuration.SCREENLAYOUT_SIZE_LARGE:
221                 screenSize = "large";
222                 break;
223 
224             case Configuration.SCREENLAYOUT_SIZE_XLARGE:
225                 screenSize = "xlarge";
226                 break;
227 
228             case Configuration.SCREENLAYOUT_SIZE_UNDEFINED:
229                 screenSize = "undefined";
230                 break;
231         }
232         return screenSize;
233     }
234 }
235