I am trying to check to see whether the android navigation bar is present on load so that I can adjust a layout accordingly, does anyone have any suggestions?
This is the navigation bar I am trying to detect:
P.S. All I have found so far are 'bad' ways to try and remove the bar, which I dont want to do.
Took me some time but I've found a more reliable way than relying on hasPermanentMenuKey() which doesn't work for newer phones like the HTC One which have no menu key but do have home & back keys so don't need (or show) the soft navigation bar. To get around this try the following code which checks for a back button too:
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
if(!hasMenuKey && !hasBackKey) {
// Do whatever you need to do, this device has a navigation bar
}
There's no reliable way to check for a navigation bar. Using KeyCharacterMap.deviceHasKey you can check if certain physical keys are present on the device, but this information is not very useful since devices with physical keys can still have a navigation bar. Devices like the OnePlus One, or any device running a custom rom, have an option in the settings that disables the physical keys, and adds a navigation bar. There's no way to check if this option is enabled, and deviceHasKey still returns true for the keys that are disabled by this option.
This is the closest you can get:
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);
if (hasBackKey && hasHomeKey) {
// no navigation bar, unless it is enabled in the settings
} else {
// 99% sure there's a navigation bar
}
If the back and home button are not both physically present on the device, it must have a navigation bar, because the user otherwise wouldn't be able to navigate at all. However, you can never be 100% sure about this, since manufacturers can implement deviceHasKey wrong.
Another solution
(a part of my class UtilsUISystem )
public static boolean hasNavBar (Resources resources)
{
//Emulator
if (Build.FINGERPRINT.startsWith("generic"))
return true;
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
return id > 0 && resources.getBoolean(id);
}
Here is a quick answer that combines Pauland's and Philask's solutions. I'm afraid I don't have enough devices available to test if it works everywhere, though. I'd be interested to hear others' results.
boolean hasNavBar(Context context) {
Resources resources = context.getResources();
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
return resources.getBoolean(id);
} else { // Check for keys
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
return !hasMenuKey && !hasBackKey;
}
}
I've done like this, it works on every device I tested, and even on emulators:
public static boolean hasNavigationBar(Activity activity) {
Rect rectangle = new Rect();
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rectangle);
activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
return displayMetrics.heightPixels != (rectangle.top + rectangle.height());
}
you could add this code to your activity's onCreate() method:
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
#Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
// TODO: The navigation bar is visible. Make any desired
// adjustments to your UI, such as showing the action bar or
// other navigational controls.
} else {
// TODO: The navigation bar is NOT visible. Make any desired
// adjustments to your UI, such as hiding the action bar or
// other navigational controls.
}
}
});
This method worked for me
int id = getResources().getIdentifier("config_showNavigationBar","bool","android");
boolean result = id > 0 && getResources().getBoolean(id);
//
if(result) {
// Do whatever you need to do, this device has a soft Navigation Bar
}
It worked for me and tested in many devices.
Something that should probably work better is to measure the screen.
Starting with API 17 there's getWindowManager().getDefaultDisplay().getRealSize(), which can be compared to size returned by getWindowManager().getDefaultDisplay().getSize().
If you get different results I think it's safe to say that there is a nav bar and if you get the same results there isn't one. One thing to pay attention to is your target SDK and supported screens, which might cause the result of getSize() to be scaled if Android thinks your app wouldn't work well on the current device without scaling.
Below API 17 you can measure the screen via getWindowManager().getDefaultDisplay().getMetrics() in both landscape and portrait mode, and again, different results probably mean there's a nav bar.
However, if you get the same results, you don't actually know, as phones can keep the nav bar on the shorter edge even when in landscape. An educated guess would be that if either the width or the height is 4% to 8% smaller than standard sizes like 1280x800, 1280x720, 1024x600, while the other dimension is equal, then again there probably is a nav bar. Don't bet on it, though. There are too many resolutions, which differ too little from one another for this to work well.
boolean hasNavBar(Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// navigation bar was introduced in Android 4.0 (API level 14)
Resources resources = context.getResources();
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
return resources.getBoolean(id);
} else { // Check for keys
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
return !hasMenuKey && !hasBackKey;
}
} else {
return false;
}
}
On Android 10 (API level 29), you can also check for the bottom window inset:
#RequiresApi(api = Build.VERSION_CODES.Q)
private boolean hasNavigationBar() {
final WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
if (windowInsets == null) {
throw new RuntimeException("Window is not attached");
}
return windowInsets.getTappableElementInsets().bottom > 0;
}
Note that the window has to be attached for getRootWindowInsets() to return a non-null value, so you likely want to call this in onAttachedToWindow.
This solution is also used by LineageOS's launcher app Trebuchet (source), which is how I learned of it.
I see the answers above, I want to indicate
that the "not exist" can be regard as the height of 0;
so it can be like this:
public static int getScreenH(Context context) {
DisplayMetrics dm = new DisplayMetrics();
dm = context.getResources().getDisplayMetrics();
int h = dm.heightPixels;
return h;
}
public static int getDpi(Context context) {
DisplayMetrics displayMetrics1 = context.getResources().getDisplayMetrics();
int height1 = displayMetrics1.heightPixels;
int dpi = 0;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
#SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
#SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, displayMetrics);
dpi = displayMetrics.heightPixels;
} catch (Exception e) {
e.printStackTrace();
}
return dpi;
}
public static int getBottomStatusHeight(Context context) {
int totalHeight = getDpi(context);
int contentHeight = getScreenH(context);
return totalHeight - contentHeight;
}
```
Solution: Only devices without permanent hardware keys have the navigation bar hence you can check for the API version and use hasPermanentMenuKey() to find hardware keys
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
Related
I'm writing an Android app where a user has to select an option using a ChipGroup and Chips.
Everything is working fine, it's just a bit clunky as there is no animation except the default ripple when selecting a Chip.
I've read the Material Design 3 Docs and found this video showing a nice animation that I'd like to implement, but I don't know how.
I've tried:
enabling
android:animateLayoutChanges="true"
but that only animates the adding and removing of a Chip, not the checking and unchecking.
using
TransitionManager.beginDelayedTransition(chipGroup);
and that works fine on the chipGroup but the content of the Chip (tick appearing and text rescaling) does not animate.
Please tell me if I'm doing something wrong, here is also the method I use to add and select those Chips:
ChipAdapter adapter = new ChipAdapter(getContext());
for(int i = 0; i < adapter.getCount(); i++){
View chip = adapter.getView(i, chipGroup, chipGroup);
if(chip instanceof Chip) {
chip.setId(i);
chip.setOnClickListener(v -> {
for(int p = 0; p < chipGroup.getChildCount(); p++){
chipGroup.getChildAt(p).setSelected(false);
}
chip.setSelected(true);
});
chipGroup.addView(chip);
}
}
Update: Appended Jetpack Compose answer.
With XML
So as far as I can tell there is no embed way to simply enable this animation, but I found two ways to mimic the animation shown in your linked video.
Results
Option 1
Option 2
Code & Explaination
Option 1
This option works by enabling the animateLayoutChanges option of the ChipGroup that contains your chips
android:animateLayoutChanges="true"
and adding the following code for your ChipGroup:
for (view in chipGroup.children) {
val chip = view as Chip
chip.setOnCheckedChangeListener { buttonView, _ ->
val index = chipGroup.indexOfChild(buttonView)
chipGroup.removeView(buttonView)
chipGroup.addView(buttonView, index)
}
}
This code will automatically remove the chip and instantly add it back to the ChipGroup whenever the selection state of a chip changes.
Drawbacks
The animation is rather a transition of the form stateBefore -> invisible -> stateAfter than stateBefore -> stateAfter what results in the chip "flashing".
Option 2
For this option add the following custom Chip class (Kotlin) to your project and change your chips to be instances of CheckAnimationChip instead of com.google.android.material.chip.Chip:
import android.animation.ObjectAnimator
import android.content.Context
import android.util.AttributeSet
import androidx.core.animation.doOnEnd
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
private const val CHIP_ICON_SIZE_PROPERTY_NAME = "chipIconSize"
// A value of '0f' would be interpreted as 'use the default size' by the ChipDrawable, so use a slightly larger value.
private const val INVISIBLE_CHIP_ICON_SIZE = 0.00001f
/**
* Custom Chip class which will animate transition between the [isChecked] states.
*/
class CheckAnimationChip #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = com.google.android.material.R.attr.chipStyle
) : Chip(context, attrs, defStyleAttr) {
private var onCheckedChangeListener: OnCheckedChangeListener? = null
private var _chipDrawable: ChipDrawable
private var defaultCheckedIconSize: Float
private var currentlyScalingDown = false
var animationDuration = 200L
init {
// Set default values for this category of chip.
isCheckable = true
isCheckedIconVisible = true
_chipDrawable = chipDrawable as ChipDrawable
defaultCheckedIconSize = _chipDrawable.chipIconSize
super.setOnCheckedChangeListener { buttonView, isChecked ->
if (currentlyScalingDown) {
// Block the changes caused by the scaling-down animation.
return#setOnCheckedChangeListener
}
onCheckedChangeListener?.onCheckedChanged(buttonView, isChecked)
if (isChecked) {
scaleCheckedIconUp()
} else if (!isChecked) {
scaleCheckedIconDown()
}
}
}
/**
* Scale the size of the Checked-Icon from invisible to its default size.
*/
private fun scaleCheckedIconUp() {
ObjectAnimator.ofFloat(_chipDrawable, CHIP_ICON_SIZE_PROPERTY_NAME,
INVISIBLE_CHIP_ICON_SIZE, defaultCheckedIconSize)
.apply {
duration = animationDuration
start()
doOnEnd {
_chipDrawable.chipIconSize = defaultCheckedIconSize
}
}
}
/**
* Scale the size of the Checked-Icon from its default size down to invisible. To achieve this, the
* [isChecked] property needs to be manipulated. It is set to be true till the animation has ended.
*/
private fun scaleCheckedIconDown() {
currentlyScalingDown = true
isChecked = true
ObjectAnimator.ofFloat(_chipDrawable, CHIP_ICON_SIZE_PROPERTY_NAME,
defaultCheckedIconSize, INVISIBLE_CHIP_ICON_SIZE)
.apply {
duration = animationDuration
start()
doOnEnd {
isChecked = false
currentlyScalingDown = false
_chipDrawable.chipIconSize = defaultCheckedIconSize
}
}
}
override fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
onCheckedChangeListener = listener
}
}
This class changes the size of the chip's icon by using an ObjectAnimator. Therefore it accesses the ChipDrawable of the chip and changes the chipIconSize property with the animator.
Drawbacks (rather picky)
This will only animate the icon size and not a complete transition between the drawables of the chip like in the linked video (e.g. there is no smooth transition of the border or background in this implementation).
You can observe a flickering of adjacent chips during the animation (see chip "Last 4 Weeks" in the gif), however I could only observe this problem on the emulator and did not notice it on a physical device.
Jetpack Compose
In Jetpack Compose you can make use of the animateConentSize() Modifier:
FilterChip(
selected = selected,
onClick = { /* Handle Click */ },
leadingIcon = {
Box(
Modifier.animateContentSize(keyframes { durationMillis = 200 })
) {
if (selected) {
Icon(
imageVector = Icons.Default.Done,
contentDescription = null,
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
}
},
label = { /* Text */ }
)
The important part here is to always have a composable (here the Box) for the leadingIcon that holds the check-icon if the chip is selected and is empty if not. This composable can then be animated smoothly with the animateContentSize() modifier.
So I am facing a weird bug I cannot explain - I cannot even reproduce it sometimes.
Basic context:
I have an application, which lists objects. Every object has a name and a point value. For every object, the addCustomSpinner function creates a "ticket" (a custom view, kind-of-spinner) and shows them in a scrollview so the user can select the one needed. There are four different 'containers' for four different kind of objects - so the layout can be populated with four kind of "ticket" package.
The data for the objects are collected from a database. The addCustomSpinner is called with a for cycle for every object in the database, and - Important - before the for method, the Layout it populates with the tickets is cleared (removeAllViews).
Inside addCustomSpinner, everything is created as "new" - like the button in question.
addCustomSpinner creates this button and adds a new onClickListener. Inside onClickListener, a new boolean is created - this is used to show a different animation when the button is clicked again. On first click (boolean = true), the arrow turns 180 degrees and faces upwards, on second click (boolean = false) the arrow turns 180 degrees and faces downwards. Works like a charm, until...
The bug I am facing:
Sometimes - as I already mentioned, not every time - if I click the button for one "ticket", then leave it 'opened' and click on an another one, and leave it 'opened' also, THEN I choose to populate the layout with a different kind of "ticket" package - The arrow faces upwards by default on every ticket in every package! Sometimes - again, just sometimes - with the same pattern I can turn it back, but it happens just "by accident".
I don't understand how the animation and state of the buttons can be connected, if every created ticket is new, every button is new, every onClickListener is new, and every boolean inside onClickListener is new. And if these are connected somehow, then why can that be that every behavior is "unique" for the buttons, nothing else shows any connection - even this is just a "sometimes" bug, a pretty rare one.
Can anybody help me why this happens?
What I tried:
Well, tried to trace the issue - but since it happens just by accident, I have no clue, I just searched if I can do anything else than the boolean to add different animation for the clicks. Sadly using ObjectAnimator is not a good solution for me - not the same result at least, since my animated arrow not only rotates, but it also changes its color. Shapeshifter seemed like a good idea to create animations easily, but now as I see it, maybe a simple rotation will be my ultimate solution.
Here's the code for the button:
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
EDIT:
The full addCustomSpinner():
private void addCustomSpinner(Routes mRouteItemToAdd, String placeName) {
//creating a new View for my custom layout created in xml
View customRoutesView = new View(this);
LinearLayout.LayoutParams customViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
customRoutesView.setLayoutParams(customViewParams);
customRoutesView = LayoutInflater.from(this).inflate(
R.layout.custom_view_layout, routeLayout, false
);
//Setting up the views inside the custom view
ImageView imageViewDiffImage = customRoutesView.findViewById(R.id.routeDiffImageView);
TextView textViewRouteName = customRoutesView.findViewById(R.id.routeNameTextView);
TextView textViewRouteDiff = customRoutesView.findViewById(R.id.routeDiffTextView);
ImageButton customButton = customRoutesView.findViewById(R.id.customButton);
RadioButton climberNameOne = customRoutesView.findViewById(R.id.climberNameOne);
RadioButton climberNameTwo = customRoutesView.findViewById(R.id.climberNameTwo);
Button climbedItButton = customRoutesView.findViewById(R.id.climbed_it_button);
RadioGroup climberNameRadioGroup = customRoutesView.findViewById(R.id.climberNameRadioGroup);
RadioGroup climbingStyleRadioGroup = customRoutesView.findViewById(R.id.styleNameRadioGroup);
RelativeLayout routeWhoClimbed = customRoutesView.findViewById(R.id.routeWhoClimbedRelativeLayout);
imageViewDiffImage.setImageResource(R.mipmap.muscle);
textViewRouteName.setText(mRouteItemToAdd.name);
textViewRouteDiff.setText("Difficulty: " + (int) mRouteItemToAdd.difficulty);
climberNameOne.setText(climberName1);
climberNameTwo.setText(climberName2);
routeWhoClimbed.setVisibility(GONE);
//Here comes the button with the animated image
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
//Button, works like an 'OK' or something, and I have no
//problem with this
climbedItButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int checkedNameButton = climberNameRadioGroup.getCheckedRadioButtonId();
int checkedStyleButton = climbingStyleRadioGroup.getCheckedRadioButtonId();
RadioButton checkedNameRadioButton = (RadioButton) findViewById(checkedNameButton);
RadioButton checkedStyleRadioButton = (RadioButton) findViewById(checkedStyleButton);
String checkedName = (String) checkedNameRadioButton.getText();
String checkedStyle = (String) checkedStyleRadioButton.getText();
addClimbToDatabase(user.getUid(), checkedName, mRouteItemToAdd, placeName, checkedStyle);
}
});
//And finally, I add this new "ticket" with the custom view to the layout i want to show it. Again, this also works like a charm, no problem here.
routeLayout.addView(customRoutesView);
}
Ultimately, I did not manage to understand the problem throughly, but I was able to eliminate it.
So during my fixing tries I narrowed down the problem to the animated drawable state - credit to #avalerio for his pro tip, but the answer wasn't addig an id to the button. I think somehow and sometime, the state of the first animation (turning the arrow 180 degrees) stuck in the end position - causing the other views using this animatedDrawable showing it in end position on start.
.reset() did not help, since it resets the animatedVectorDrawable object, not the animation xml drawable state. My solution is a kind of workaround, but it is working: when the custom-view 'ticket' is created with the animated-drawable-imagebutton, I set the imageResource of the button to a not-animated xml drawable - this drawable is basically the start position of my animated-drawable. This way, when the 'tickets' are generated, the imagebutton is 'hardcoded' in the start position.
Not elegant, but works. BUT(!) I would really appreciate if someone could explain to me how this weird behavior is possible - just sometimes, randomly, with no pattern I can reproduce intentionally.
I am currently able to update the status bar text color from light to dark using the following inside my base activity:
private fun toggleStatusBarTextColor(light: Boolean) {
// clear any existing flags
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
if(light) {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
} else {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
}
}
systemUiVisibility is now showing deprecated on API 30, and although the deprecated methods will still function for the time being, I would prefer to replace them with the newer way to accomplish this. I have read that we should now use the WindowInsetsController functions, but it is not clear to be how to accomplish this from the docs. Can someone point me in the right direction?
For API 30 you can use WindowInsetsController.setSystemBarsAppearance (int appearance, int mask):
To make status bar light:
window.insetsController?.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
To clear the flag:
window.insetsController?.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
Note that getInsetsController is nullable hence the ? check.
Alternatively (and for lower APIs) you can use WindowInsetControllerCompat:
val windowInsetController = ViewCompat.getWindowInsetsController(window.decorView)
windowInsetController?.isAppearanceLightStatusBars = true // or false
Note: if clearing flag doesn't work check value of window.decorView.windowSystemUiVisibility - if it contains View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR that means your view hierarchy contains a View with this flag which is propagated and affects systemUiVisibility calculation.
I, as others, couldn't get the new API that #Pawel recommended to work on all Android OS versions. I unfortunately found I had to use both the older API as well as the new to get it working on Android 11 & below:
fun setStatusBarLightText(window: Window, isLight: Boolean) {
setStatusBarLightTextOldApi(window, isLight)
setStatusBarLightTextNewApi(window, isLight)
}
private fun setStatusBarLightTextOldApi(window: Window, isLight: Boolean) {
val decorView = window.decorView
decorView.systemUiVisibility =
if (isLight) {
decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
} else {
decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}
private fun setStatusBarLightTextNewApi(window: Window, isLightText: Boolean) {
ViewCompat.getWindowInsetsController(window.decorView)?.apply {
// Light text == dark status bar
isAppearanceLightStatusBars = !isLightText
}
}
It should be an easily searchable question yet I can't find a single result. I can find how to retrieve screen brightness but not how to retrieve the status of auto brightness.
And a second question, Android Studio forces me to surround this statement with a try/catch block, which I guess means it won't work all the time. Is there a more reliable way to retrieve screen brightness?
int currentBrightness = Settings.System.getInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
When I hover the mouse over getInt I get this message: Unhandled exception: android.provider.Settings.SettingNotFoundException
I have to turn it into this:
try {
int currentBrightness = Settings.System.getInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
how to retrieve the status of auto brightness
If you Ctrl+click SCREEN_BRIGHTNESS in Android Studio you can see the source code. Right below where SCREEN_BRIGHTNESS is there's also SCREEN_BRIGHTNESS_MODE and constants for the two modes:
/** Control whether to enable automatic brightness mode. */
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
/** SCREEN_BRIGHTNESS_MODE value for manual mode. */
public static final int SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
/** SCREEN_BRIGHTNESS_MODE value for automatic mode. */
public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
See also Settings.java in Android Code Search.
Android Studio forces me to surround this statement with a try/catch block, which I guess means it won't work all the time.
Android Studio doesn't force you to do anything, it's trying to tell you something, probably, that you should do something differently. At the minimum, log the exception so you can share it in your SO question, so people can help you figure out what's going on.
Update your question with the exact exception stack trace and more code.
android.provider.Settings.SettingNotFoundException
SettingNotFoundException happens in getInt when
the setting is unset
the setting set but is not a number
If you don't want to deal with it use the overload that takes 3 parameters and doesn't throw:
ContentResolver contentResolver = getContext().getContentResolver();
int currentBrightness = Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS, /* default value */ 0);
Per documentation:
The default value will be returned if the setting is not defined or not an integer.
If you want to know if the user has the auto brightness mode enabled in the device, you can try a function like that:
int getBrightnessMode() {
try {
int brightnessmode = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE);
return brightnessmode;
} catch (Exception e) {
return 0;
}
}
This function will return a number, 1 for auto brightness enabled and 0 for auto brightness disabled.
If you want to change the brightness manually, your app needs a special permission, because the screen brightness is a device parameter.
To give permission to the app and change the brightness, use this code:
if (!Settings.System.canWrite(getApplicationContext())) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
startActivity(intent);
}
if (Settings.System.canWrite(getApplicationContext())) {
Settings.System.putInt(getApplicationContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, myBrightness);
}
You will need to add the Write Settings permission to the AndroidManifest file:
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
Tested on Nokia 6.2 & Samsung Tab Running Android 10 and Android 5.1
private int getMobileBrightnessMode() {
// works for adaptive brightness option as well as auto for old models
// adaptive brighness Android Pie onwards
// Auto Brightness for old versions
ContentResolver contentResolver = getContentResolver();
int brightnessMode = 0;
try {
brightnessMode = Settings.System.getInt(contentResolver,Settings.System.SCREEN_BRIGHTNESS_MODE);
} catch (Exception e) {
Log.d("tag", e.toString());
}
return brightnessMode;
// if 0 auto mode is off
// if 1 auto mode is on
}
I realise that this is somewhat of an unusual request, since most ViewParts layed out by the workspace and may be in stacks and not have unilateral access to their own size.
However, what i am trying to do is slightly different. I am extending a find-replace bar for eclipse that is reminiscent of the one you see in firefox (and much less obtrusive than the default).
So the desire for this is for it to be a fixed size at the bottom of the editor pane. However, I have not succeeded in setting the size of my view. I have tried
using an ISizeProvider:
public int getSizeFlags(boolean width) {
if (width) {
return SWT.FILL;
}
return SWT.MIN | SWT.MAX;
}
#Override
public int computePreferredSize(boolean width, int availableParallel,
int availablePerpendicular, int preferredResult) {
if (width) {
return preferredResult;
}
return fixedHeight;
}
calling setSize() on my root pane and on its parent pane (NB it is not surprising this doesn't work because the parent's layout manager will likely override it).
Using the internal PartPane class to get the bordering sashes to attempt to move IT programattically. (Don't flame me for doing this, I am just trying to explore the possibilities).
private void updateSize(IWorkbenchPartReference pPartRef) {
// FIXME -- this is all internal stuff. is there a public way to do this?
if (pPartRef instanceof WorkbenchPartReference) {
PartPane lPane = ((WorkbenchPartReference) pPartRef).getPane();
Sashes sashes = new Sashes();
if (lPane.getContainer() != null) {
lPane.getContainer().findSashes(lPane, sashes );
Sash topSash = sashes.top;
if (okToUse(topSash)) {
Rectangle parentBounds = topSash.getParent().getBounds();
int topSashTop = parentBounds.height - fixedHeight - topSash.getSize().y;
sashes.top.setLocation(sashes.top.getLocation().x, topSashTop);
topSash.getParent().layout();
System.out.println("sashtop = " + topSashTop);
}
}
}
}
I've also tried calling PartPane.flushLayout() and PartPane.getContainer.resizeChild() and none of these do anything.
None of this works. Is there a way to achieve what I want?