I'm trying to change the display resolution of my primary display on Android 8.1. I get an event from the kernel (based on EDID) that tells me I need to change mode.
I then basically do:
sp<IBinder> display(SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
status_t res = SurfaceComposerClient::setActiveConfig(display, new_mode);
This updates surfaceflinger, the display hal and the kernel correctly, but windowmanager/display does not change resolution until I run:
wm size 1920x1080
wm density 240
But wm calls these function, which seem a bit too harsh, and also store the size/density in persistent setting:
public void setForcedDisplaySize(int displayId, int width, int height);
public void setForcedDisplayDensityForUser(int displayId, int density, int userId);
I've been looking at code in windowmanager/display without finding an API that I should use for this.
Is there a way to notify windowmanager/display about the change in surfaceflinger, or an API I've missed that I can use?
Am I going about this the wrong way? Is there a proper way you're meant to change resolution/mode of the primary display, that I haven't found?
Related
I'm upgrading my Android OpenGL app to run on my new 4K phone and am intending to have a fullscreen activity whose dimensions are both logically and physically 4K. However, without special-casing for 4K, I'm running into the evidently expected scenario that my activity is only 1080p in size and I wish to bump it up to 4K.
I have followed the available documentation involving querying the display modes, which thankfully does enumerate both 1080p and 4K as available display modes. The pseudo code getBestDisplayModeId() below represents this step, which I consider to be functioning accurately. This function can be assumed to correctly return the display mode id of the 4K display mode. I then set the preferredDisplayModeId in the LayoutParams for the window to that optimal display mode id and use setAttributes() to hopefully apply it to the window.
This is executed during the onCreate() for my fullscreen Activity. Note that the activity was created via the fullscreen activity wizard in Android Studio and it essentially contains just a GLSurfaceView and associated Renderer (the typical OpenGL setup).
My (GL) Renderer class was temporarily modified (for debugging purposes) to display a Toast (via IPC), showing its dimensions whenever its onSurfaceChanged() overridden method is called. The Toast is always reporting that the dimensions are 1080p, rather than the intended 4K.
How can I correctly apply the preferredDisplayModeId and then get an Activity/View/GLSurfaceView that is truly the intended 4K size?
int getBestDisplayModeId()
{
// 1. query available display modes for display 0 via DisplayManager
// 2. return display mode corresponding to the highest resolution
}
FullscreenActivity.onCreate()
{
Window w = getWindow();
WindowManager.LayoutParams p = w.getAttributes();
p.preferredDisplayModeId = getBestDisplayModeId(); // get 4K id
w.setAttributes(p); // set 4K resolution
// when does the switch to 4K occur?
mGLView = new MyGLSurfaceView();
setContentView(mGLView);
}
MyRenderer.onSurfaceChanged(int w, int h)
{
// only shows 1080p resolution, never 4K
}
Background: Android N comes with a feature to change system Display Size from settings, in addition to the previously present feature of changing Font Size.
Change Display Size:
Image Source: pcmag.com
Question:
If an app has android.permission.WRITE_SETTINGS permission to change the settings, there are ways to change the system font size programatically as mentioned in How to programmatically change font settings of Device: font style and font size?. However I couldn't find a way to change the display size programmatically. Is it possible?
What I've tried?
I've checked the possible options in the list of Settings.System convenience functions provided for changing settings programmatically.
Update:
I've opened a feature request for the same here: https://code.google.com/p/android/issues/detail?id=214124 . If you feel it would be useful please star it.
Just share my approach to tackle this requirement. I achieve this function by using the dirty Java reflection method - although it's not that elegant.
The main reference source code files are:
ScreenZoomSettings.java (https://github.com/aosp-mirror/platform_packages_apps_settings/blob/master/src/com/android/settings/display/ScreenZoomSettings.java)
DisplayDensityUtils.java (http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SettingsLib/src/com/android/settingslib/display/DisplayDensityUtils.java)
WindowManagerGlobal.java (http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/view/WindowManagerGlobal.java)
And, follow the below steps, then you can get the required control:
Read ZoomScreenSettings.java's onCreate() and commit(). They demonstrate how to correctly get and set the density value into the framework.
Read DisplayDensityUtils.java. It shows how to use WindowManagerService to control the system density. Because we can't get the instance of DisplayDensityUtils via reflection, we need to understand which WindowManagerService methods are utilized.
Use reflection to get WindowManagerService's instance, and write a DisplayDensityUtils-like class into your project.
// Where wm is short for window manager
val wmGlobalClz = Class.forName("android.view.WindowManagerGlobal")
val getWmServiceMethod = wmGlobalClz.getDeclaredMethod("getWindowManagerService")
val wmService = getWmServiceMethod.invoke(wmGlobalClz)
val wmInterfaceClz = Class.forName("android.view.IWindowManager")
// Now, we already have the ability to do many things we want.
// For instance, to get the default density value.
val getInitialDisplayDensityMethod = wmInterfaceClz.getDeclaredMethod(
"getInitialDisplayDensity",
Integer.TYPE
)
val defaultDensity = getInitialDisplayDensityMethod.invoke(
wmService,
Display.DEFAULT_DISPLAY
) as Int
Set or get the density value with your DisplayDensityUtils-like class. One thing just to mention is that, if you want to pass an index value (e.g., 2 for large display size), please feed it to your DisplayDensityUtils-like class's mValues array to get the actual density value which is the right one to pass to the framework. Getting the current density index also applies the same concept.
In manifest, under application: android:configChanges="density"
In an activity/application:
public void adjustDisplayScale(Configuration configuration) {
if (configuration != null) {
Log.d("TAG", "adjustDisplayScale: " + configuration.densityDpi);
if(configuration.densityDpi >= 485) //for 6 inch device OR for 538 ppi
configuration.densityDpi = 500; //decrease "display size" by ~30
else if(configuration.densityDpi >= 300) //for 5.5 inch device OR for 432 ppi
configuration.densityDpi = 400; //decrease "display size" by ~30
else if(configuration.densityDpi >= 100) //for 4 inch device OR for 233 ppi
configuration.densityDpi = 200; //decrease "display size" by ~30
DisplayMetrics metrics = getResources().getDisplayMetrics();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metrics);
metrics.scaledDensity = configuration.densityDpi * metrics.density;
this.getResources().updateConfiguration(configuration, metrics);
}
}
Call it just after super.onCreate(..):
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adjustDisplayScale( getResources().getConfiguration());
This will set the Display Size in-between the "Smaller" and "Small" settings of "Display Size" overriding any Display Size settings set by user.
It self-adjusts correctly for 4in, 5.5in, 6in etc devices.. but I'm sure there would a better way than using those if statements.
While referring Settings.System , there is a [putConfiguration(ContentResolver cr, Configuration config)](https://developer.android.com/reference/android/provider/Settings.System.html#putConfiguration(android.content.ContentResolver, android.content.res.Configuration)) method.
Use of this method is:
Convenience function to write a batch of configuration-related settings from a Configuration object.
In Configuration
This includes both user-specified configuration options (locale list and scaling) as well as device configurations (such as input modes, screen size and screen orientation).
Set configuration with SCREENLAYOUT_SIZE_MASK values for screen size.
It is:
The SCREENLAYOUT_SIZE_MASK bits define the overall size of the screen. They may be one of SCREENLAYOUT_SIZE_SMALL, SCREENLAYOUT_SIZE_NORMAL, SCREENLAYOUT_SIZE_LARGE, or SCREENLAYOUT_SIZE_XLARGE.
I hope its helps you.
i looked at these 2 function in the documentation here
i want to get the desired wallaper dimensions,
running those functions on an SGS3 (1280x720) with stock launcher,
i got both minDesiredWidth + minDesiredHight: 1280x1280
same thing with a Note 3 (1920x1080) i got 1920x1920
i want to know the desired ratio of wallpaper the device wants, and i thought i would get it from those 2 functions.
both those devices stock launchers have a static background image of their respective screen resolutions, so why does getDesiredMinimumWidth doesn't give me 1280/1080 for each device respectively?
how do i know the proper ratio for the device?
This is the intended result of the methods, the code used in the WallpaperManager class is:
return sGlobals.mService.getHeightHint();
and
return sGlobals.mService.getWidthHint();
It isn't mentioned anywhere why they return the same value, but to get the true values of WxH, you should use:
Point displaySize = new Point();
getWindowManager().getDefaultDisplay().getRealSize(displaySize);
and refer to the values with int width = displaySize.x and int height = displaySize.y
I'm developing in qt 5.3 on android device. I can't get the screen resolution.
With the old qt 5 version this code worked:
QScreen *screen = QApplication::screens().at(0);
largh=screen->availableGeometry().width();
alt =screen->availableGeometry().height();
However now it doesn't work (returns a screen size 00x00). Is there another way to do it? thanks
Size holds the pixel resolution
screen->size().width()
screen->size().height();
Whereas, availableSize holds the size excluding window manager reserved areas...
screen->availableSize().width()
screen->availableSize().height();
More info on the QScreen class.
for more information, screen availableSize is not ready at the very beginning, so you have to wait for it, here is the code:
Widget::Widget(QWidget *parent){
...
QScreen *screen = QApplication::screens().at(0);
connect(screen, SIGNAL(virtualGeometryChanged(QRect)), this,SLOT(getScreen(QRect)));
}
void Widget::getScreen(QRect rect)
{
int screenY = screen->availableSize().height();
int screenX = screen->availableSize().width();
this->setGeometry(0,0,screenX,screenY);
}
I found that there are several ways to obtain the device resolution, each outputs the same results and thankfully works across all Os-es supported by Qt...
1) My favorite is to write a static function using QDesktopWidget in a reference class and use it all across the code:
QRect const CGenericWidget::getScreenSize()
{
//Note: one might implement caching of the value to optimize processing speed. This however will result in erros if screen resolution is resized during execution
QDesktopWidget scr;
return scr.availableGeometry(scr.primaryScreen());
}
Then you can just call across your code the function like this:
qDebug() << CGenericWidget::getScreenSize();
It will return you a QRect const object that you can use to obtain the screen size without the top and bottom bars.
2) Another way to obtain the screen resolution that works just fine if your app is full screen is:
QWidget *activeWindow = QApplication::activeWindow();
m_sw = activeWindow->width();
m_sh = activeWindow->height();
3) And of course you have the option that Zeus recommended:
QScreen *screen = QApplication::screens().at(0);
largh=screen->availableSize().width();
alt =screen->availableSize().height();
I have a weird problem.
Before you come to an idea to lash out on me, I am working on a custom Jelly Bean. Therefore the "usual nice approaches" might not work here, and dirty workarounds have to be made.
I have an APK which contains the following in assets:
layout
layout-mdpi
layout-land
layout-large-mdpi
layout-large-land-mdpi
layout-large-hdpi
layout-large-xhdpi
And some other metrics code returned this:
D/AppDemo( 2091): measured width: 1920 PE width: 1920 scaleFactor = 1.0
D/AppDemo( 2091): [ANDROID] measured width: 1920 measured height: 1080
D/AppDemo( 2091): [ANDROID] scale for resources: 2.0 xdpi: 320.0 ydpi: 320.0
D/AppDemo( 2091): [ANDROID] screen density: xhdpi
D/AppDemo( 2091): [ANDROID] screen size: large
D/AppDemo( 2091): [ANDROID] using layout: layout-mdpi
So, looking at the metrics, why isn't layout-large-xhdpi being loaded?
Please, tell me where I can look this up. I really need to find a way to force the Layout/Resource/AssetManager to load a specific layout.
I am aware the most popular comment on this issue is "you do not need / why do you have layout-xhdpi, you should have drawable-xhdpi and layout-large" but, bear with me.
I would very appreciate even small hints as to where to look at, and what to look for. So far, AssetManager seems like the place to start digging/logging.
When I omit layout-mdpi, the application crashes on me, with missing resources. The bug seems to be that even though the code returns xhdpi, it assumes mdpi somewhere else. I need to find this, and fix it so my apps look as nice as they did on ICS :)
I am figuring out which layout is loaded in a simple manner - all root layouts have a android:tag element, and when I setContentView(R.layout.main_layout) I grab the tag on the root element and know which folder got loaded. Apart from the visual feedback, this will eventually have to match with my device configuration.
Thanks in advance.
Interesting problem. I had a look, but it all goes a bit down the rabbit hole. No answer as such, but maybe my analysis will set you in the right direction.
I looked at what happened from setContentView forwards. Obviously at that point you are talking in terms of a density-agnostic layout reference (e.g. R.layout.main_layout) and then later it will be turned into a reference to a specific file in the APK. When and where is your question.
I used landscape/portrait qualifiers so that I could change the quality at runtime, and I used a debugger with the Android source attached.
Here's a flow, starting some way into it.
Resources.getLayout(int)
Resources.loadXmlResourceParser(int, String)
Resources.getValue(int, TypedValue, boolean)
AssetManager.getResourceValue(int, int, TypedValue, boolean)
StringBlock.get(int)
StringBlock.nativeGetString(int,int)
Let's work backwards.
Step 6 is a native (C) method that returns the qualified reference, e.g. /res/layout-land/yourview.xml. Its parameter is an index, however, and this changes based on whether we are in landscape or portrait.
To see where that came from we have to go back to step 4. The index is a field within the TypedValue, but it is not initially set correctly when it is passed in to this method.
Step 4 calls another native method, AssetManager.loadResourceValue(), which alters the passed in TypedValue and sets the index appropriately.
Maybe you can start looking at the C for that and see how you get on.
There are so many hacked devices in the market like Micromax Funbook which have a screen size large but uses the mdpi resources trust me I have worked with them and it was very frustrating.
As you mentioned your app is working great with every other device it's just not working with this praticular tablet you may need to implement such kinda solution posted here
http://android-developers.blogspot.in/2011/07/new-tools-for-managing-screen-sizes.html
You must read the last segment on this page it will surely help.
to summarize it here the suggested approach is to create a seperate layout with the different name using the different resources altogether.
You are driving the resource picking process here not the system. which may be time consuming but will surely help.
public class MyActivity extends Activity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate();
Configuration config = getResources().getConfiguration();
if (config.smallestScreenWidthDp >= 600) {
setContentView(R.layout.main_activity_tablet);
} else {
setContentView(R.layout.main_activity);
}
}
}
I don't know if i get you right, you are building a custom AndroidOS and you want this custom AndroidOS to always load the same layout resource, in your case layout-large-xhdpi?
If so, i think you have customize the Configuration Class. If you want it quick and dirty, you could maybe override the int-Constants on line 72 and below.
Concrete, change:
....
100 public static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
108 public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
to:
....
100 public static final int SCREENLAYOUT_SIZE_LARGE = 0x04;
108 public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
Other way could be to override the Constructor and the setToDefaults()-Method so it always loads the same screenlayout.
I didn't tried it out, I do not know if it is working, and I have no credible and/or official sources (Excluded offical Android Code), but i hope i could help you.