/*
* Copyright 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.acra.collector;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.hardware.display.DisplayManagerCompat;
import android.util.DisplayMetrics;
import android.util.SparseArray;
import android.view.Display;
import android.view.Surface;
import org.acra.ACRA;
import org.acra.ReportField;
import org.acra.builder.ReportBuilder;
import org.acra.model.ComplexElement;
import org.acra.model.Element;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.util.Arrays;
/**
* Collects information about the connected display(s)
*
* @author (original author unknown) & F43nd1r
*/
final class DisplayManagerCollector extends Collector {
private final Context context;
private final SparseArray<String> flagNames = new SparseArray<String>();
DisplayManagerCollector(Context context) {
super(ReportField.DISPLAY);
this.context = context;
}
@NonNull
@Override
Element collect(ReportField reportField, ReportBuilder reportBuilder) {
final ComplexElement result = new ComplexElement();
for (Display display : DisplayManagerCompat.getInstance(context).getDisplays()) {
try {
result.put(String.valueOf(display.getDisplayId()), collectDisplayData(display));
} catch (JSONException e) {
ACRA.log.w(ACRA.LOG_TAG, "Failed to collect data for display " + display.getDisplayId(), e);
}
}
return result;
}
@NonNull
private JSONObject collectDisplayData(@NonNull Display display) throws JSONException {
final DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
final JSONObject result = new JSONObject();
collectCurrentSizeRange(display, result);
collectFlags(display, result);
collectMetrics(display, result);
collectRealMetrics(display, result);
collectName(display, result);
collectRealSize(display, result);
collectRectSize(display, result);
collectSize(display, result);
collectRotation(display, result);
collectIsValid(display, result);
result.put("orientation", display.getRotation())
.put("refreshRate", display.getRefreshRate());
//noinspection deprecation
result.put("height", display.getHeight())
.put("width", display.getWidth())
.put("pixelFormat", display.getPixelFormat());
return result;
}
private static void collectIsValid(@NonNull Display display, JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
container.put("isValid", display.isValid());
}
}
private static void collectRotation(@NonNull Display display, JSONObject container) throws JSONException {
container.put("rotation", rotationToString(display.getRotation()));
}
@NonNull
private static String rotationToString(int rotation) {
switch (rotation) {
case Surface.ROTATION_0:
return "ROTATION_0";
case Surface.ROTATION_90:
return "ROTATION_90";
case Surface.ROTATION_180:
return "ROTATION_180";
case Surface.ROTATION_270:
return "ROTATION_270";
default:
return String.valueOf(rotation);
}
}
private static void collectRectSize(@NonNull Display display, JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
final Rect size = new Rect();
display.getRectSize(size);
container.put("rectSize", new JSONArray(Arrays.asList(size.top, size.left, size.width(), size.height())));
}
}
private static void collectSize(@NonNull Display display, JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
final Point size = new Point();
display.getSize(size);
container.put("size", new JSONArray(Arrays.asList(size.x, size.y)));
}
}
private static void collectRealSize(@NonNull Display display, JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
final Point size = new Point();
display.getRealSize(size);
container.put("realSize", new JSONArray(Arrays.asList(size.x, size.y)));
}
}
private static void collectCurrentSizeRange(@NonNull Display display, @NonNull JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
final Point smallest = new Point();
final Point largest = new Point();
display.getCurrentSizeRange(smallest, largest);
final JSONObject result = new JSONObject();
result.put("smallest", new JSONArray(Arrays.asList(smallest.x, smallest.y)));
result.put("largest", new JSONArray(Arrays.asList(largest.x, largest.y)));
container.put("currentSizeRange", result);
}
}
private void collectFlags(@NonNull Display display, @NonNull JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
final int flags = display.getFlags();
for (Field field : display.getClass().getFields()) {
if (field.getName().startsWith("FLAG_")) {
try {
flagNames.put(field.getInt(null), field.getName());
} catch (IllegalAccessException ignored) {
}
}
}
container.put("flags", activeFlags(flags));
}
}
private static void collectName(@NonNull Display display, JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
container.put("name", display.getName());
}
}
private static void collectMetrics(@NonNull Display display, JSONObject container) throws JSONException {
final DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
final JSONObject result = new JSONObject();
collectMetrics(metrics, result);
container.put("metrics", result);
}
private static void collectRealMetrics(@NonNull Display display, JSONObject container) throws JSONException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
final DisplayMetrics metrics = new DisplayMetrics();
display.getRealMetrics(metrics);
final JSONObject result = new JSONObject();
collectMetrics(metrics, result);
container.put("realMetrics", result);
}
}
private static void collectMetrics(@NonNull DisplayMetrics metrics, JSONObject container) throws JSONException {
container.put("density", metrics.density)
.put("densityDpi", metrics.densityDpi)
.put("scaledDensity", "x" + metrics.scaledDensity)
.put("widthPixels", metrics.widthPixels)
.put("heightPixels", metrics.heightPixels)
.put("xdpi", metrics.xdpi)
.put("ydpi", metrics.ydpi);
}
/**
* Some fields contain multiple value types which can be isolated by
* applying a bitmask. That method returns the concatenation of active
* values.
*
* @param bitfield The bitfield to inspect.
* @return The names of the different values contained in the bitfield,
* separated by '+'.
*/
@NonNull
private String activeFlags(int bitfield) {
final StringBuilder result = new StringBuilder();
// Look for masks, apply it an retrieve the masked value
for (int i = 0; i < flagNames.size(); i++) {
final int maskValue = flagNames.keyAt(i);
final int value = bitfield & maskValue;
if (value > 0) {
if (result.length() > 0) {
result.append('+');
}
result.append(flagNames.get(value));
}
}
return result.toString();
}
}