How to detect height of black bar on Moto 360? - wear-os

I'm developing an Android Wear app and content on the very bottom of the screen is cropped because of the black bar.
This video says that we should get the height of the bar like this:
#Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
int barHeight = insets.getSystemWindowInsetBottom();
}
but in reality barHeight is always 0.
Right now I'm hacking it with
if (Build.MODEL.equals("Moto 360")) {
}
but that's not very future-proof. Any hints?

I use the window insets to determine the "chin" height in an Activity of a Wear app and in a watch face engine, so it does work. I've tested on a Moto 360. This is an extract from an Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
#Override
public void onLayoutInflated(WatchViewStub stub) {
stub.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
#Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
int chinHeight = insets.getSystemWindowInsetBottom();
// chinHeight = 30;
return insets;
}
});
}
});
}

Related

How do I get LocationManager.addNmeaListener() to work?

Here are the relevant parts of my code:-
public class MainActivity extends Activity
implements SensorEventListener, OnNmeaMessageListener {
private LocationManager m_locationManager;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ac = this;
m_locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
}
...
protected void onResume() {
...
if (m_locationManager != null) {
m_gpsSensor = new SensorView(this);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
m_locationManager.addNmeaListener(getMainExecutor(), this);
} else {
// no-op in later versions
m_locationManager.addNmeaListener(this);
}
m_gpsSensor.lineBreak("gps: ", "no messages yet");
} catch (Exception ignore) {
m_gpsSensor.lineBreak("gps: ", getString(R.string.permissiondenied));
}
topLayout.addView(m_gpsSensor);
}
}
#Override
public void onNmeaMessage(String message, long timestamp) {
long nanos = timestamp * 1000000;
if (nanos > m_gpsSensor.lastNanos + UPDATE_NANOS) {
m_gpsSensor.lastNanos = nanos;
m_gpsSensor.lineBreak("gps: ", message);
}
}
}
onNmeaMessage is never called (lastNanos is initialised to zero) and m_gpsSensor (a subclass of View) displays "no messages yet". My device does have GPS, and the app has permission to access it (otherwise it would display "Permission denied"), and GPS does work, because it can see satellites and get a fix with Satstat
I tried
addNmeaListener (OnNmeaMessageListener listener, Handler handler)
which doesn't work either. m_locationManager isn't null, because in that case it wouldn't display anything at all.
The device is a Samsung Galaxy S21 Ultra 5G running Android 12.
What am I doing wrong?
Apparently addNmeaListener doesn't start the GPS, and there doesn't seem to be an explicit call to do so. You have to call one of the requestLocationUpdates variants.

Xamarin: Detect device rotation when orientation is locked? For iOS and/or Android?

I am working on a Xamarin.Forms app that plays videos similar to a YouTube type app. I want the video to go full screen when the device rotates (like youtube does) but I also want the orientation to be locked in to portrait. Every post or tutorial I've found points to using custom renders for detecting orientation change to determine when the device rotates, but when orientation is locked those events do not fire.
Is there a way to detect device rotation without depending on orientation changing?
On iOS you would get device orientation with:
var orientation = UIDevice.CurrentDevice.Orientation;
On Android you need to ask the Window Manager:
var windowManager = ApplicationContext.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
var orientation = windowManager.DefaultDisplay.Rotation;
You don't need a custom renderer, but you could suffice with a service you register in the service locator. This could looks something like.
In shared code:
public enum Orientation
{
None,
PortraitUp,
PortraitDown,
LandscapeLeft,
LandscapeRight
}
public interface IOrientationService
{
Orientation GetCurrentOrientation();
}
On Android:
[assembly: Dependency(typeof(AndroidOrientationService))]
public class AndroidOrientationService : IOrientationService
{
private readonly IWindowManager _windowManager;
public AndroidOrientationService()
{
_windowManager = ApplicationContext.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
}
public Orientation GetCurrentOrientation()
{
switch (_windowManager.DefaultDisplay.Rotation)
{
case SurfaceOrientation.Rotation0:
return Orientation.PortraitUp;
case SurfaceOrientation.Rotation180:
return Orientation.PortraitDown;
case SurfaceOrientation.Rotation90:
return Orientation.LandscapeLeft;
case SurfaceOrientation.Rotation270:
return Orientation.LandscapeRight;
default:
return Orientation.None;
}
}
}
Similarly on iOS:
[assembly: Dependency(typeof(IosOrientationService))]
public class IosOrientationService : IOrientationService
{
public Orientation GetCurrentOrientation()
{
switch (UIDevice.CurrentDevice.Orientation)
{
case UIDeviceOrientation.LandscapeLeft:
return Orientation.LandscapeLeft;
case UIDeviceOrientation.LandscapeRight:
return Orientation.LandscapeRight;
case UIDeviceOrientation.Portrait:
return Orientation.PortraitUp;
case UIDeviceOrientation.PortraitUpsideDown:
return Orientation.PortraitDown;
default:
return Orientation.None;
}
}
}
Then in your code you should be able to get the orientation like:
var orientationService = DependencyService.Get<IOrientationService>();
var orientation = orientationService.GetCurrentOrientation();
EDIT: detecting orientation changes
If you want to detect orientation changes on iOS you can do that by adding an observer for UIDeviceOrientation.
UIDevice.Notifications.ObserveOrientationDidChange(OnOrientationChanged);
Similarly on Android you can use SensorManager to listen to SensorType.Orientation changes. It has a bit more moving parts but looks something like follows.
You need to create a ISensorEventListener class:
class MyOrientationListner : Java.Lang.Object, ISensorEventListener
{
public event EventHandler OrientationChanged;
public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
{
}
public void OnSensorChanged(SensorEvent e)
{
OrientationChanged?.Invoke(this, EventArgs.Empty);
}
}
Then you need to get the sensor manager from the current Context and start listening to orientation change events:
_sensorManager = context.GetSystemService(Context.SensorService).JavaCast<SensorManager>();
var sensor = _sensorManager.GetDefaultSensor(SensorType.Orientation);
var listener = new MyOrientationListner();
listener.OrientationChanged += OnOrientationChanged;
_sensorManager.RegisterListener(listener, sensor, SensorDelay.Normal);
private void OnOrientationChanged(object sender, EventArgs e)
{
OrientationChanged?.Invoke(this, GetCurrentOrientation());
}
Where OrientationChanged is a event in the IOrientationService:
event EventHandler<Orientation> OrientationChanged;
Then you can listen to that event where needed.
For iOS
In AppDelegate.cs override the below method
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application,UIWindow forWindow)
{
if (Xamarin.Forms.Application.Current == null || Xamarin.Forms.Application.Current.MainPage == null)
{
return UIInterfaceOrientationMask.Portrait;
}
var mainPage = Xamarin.Forms.Application.Current.MainPage;
if (mainPage is YourPage || (mainPage is NavigationPage &&
((NavigationPage)mainPage).CurrentPage is YourPage) || (mainPage.Navigation != null &&
mainPage.Navigation.ModalStack.LastOrDefault() is YourPage))
{
if (Configuration.IsFullScreen)
{
return UIInterfaceOrientationMask.Landscape;
}
}
return UIInterfaceOrientationMask.Portrait;
}
In a Dependency Service write the below method
public void ChangeLandscapeOrientation()
{
UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)UIInterfaceOrientation.LandscapeLeft), new NSString("orientation"));
UINavigationController.AttemptRotationToDeviceOrientation();
}
Call the ChangeLandscapeOrientation method wherever you need it.
For Android
In a Dependency Service write the below method to change the orientation to Landscape
public void ChangeLandscapeOrientation()
{
var activity = (Activity)Xamarin.Forms.Forms.Context;
{
activity.RequestedOrientation = ScreenOrientation.Landscape;
var attrs = activity.Window.Attributes;
_originalFlags = attrs.Flags;
attrs.Flags |= Android.Views.WindowManagerFlags.Fullscreen;
activity.Window.Attributes = attrs;
}
}
Below code to change the orientation to Portrait
public void ChangePortraitOrientation()
{
var activity = (Activity)Xamarin.Forms.Forms.Context;
{
activity.RequestedOrientation = ScreenOrientation.Portrait;
var attrs = activity.Window.Attributes;
attrs.Flags = _originalFlags;
activity.Window.Attributes = attrs;
}
}
Hope it helps!

Gluon Mobile Charm 5.0 Cannot Hide Layer

I have a loading gif for all backend requests. Prior to Charm 5.0.0, it worked fine in which the loading gif would show, backend would finish what it needed to, then the loading gif would be hidden. Now, the loading gif shows, but it doesn't hide.
addLayerFactory(LOADING_GIF, () -> new Layer() {
private final Node root;
private final double sizeX = getGlassPane().getWidth();
private final double sizeY = getGlassPane().getHeight();
{
ProgressIndicator loading = new ProgressIndicator();
loading.setRadius(50);
loading.setStyle("-fx-text-fill:white");
root = new StackPane(loading);
root.setStyle("-fx-background-color: rgba(0,0,0,0);");
getChildren().add(root);
this.setStyle("-fx-background-color:rgba(255,255,255,0.7)");
this.setShowTransitionFactory(v -> {
FadeInTransition ft = new FadeInTransition(v);
ft.setRate(2);
return ft;
});
}
#Override
public void show() {
this.setBackgroundFade(0.0);
super.show();
Layer pane = this;
Task<Integer> task = new Task<Integer>() {
#Override
protected Integer call() throws Exception {
int iterations = 0;
int max = DataService.readOutTime / 1000;
while (iterations <= max) {
Thread.sleep(1000);
iterations++;
}
Platform.runLater(new Runnable() {
#Override
public void run() {
if (pane.isVisible()) {
pane.setShowTransitionFactory(v -> {
FadeOutTransition ft = new FadeOutTransition(v);
ft.setRate(2);
return ft;
});
pane.hide();
MobileApplication.getInstance().showMessage("There was an error in sending your data.");
}
}
});
return iterations;
}
};
Thread thread = new Thread(task);
thread.start();
}
#Override
public void hide() {
this.setBackgroundFade(0.0);
super.hide();
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(sizeX, sizeY);
resizeRelocate((getGlassPane().getWidth() - sizeX) / 2, (getGlassPane().getHeight() - sizeY) / 2, sizeX, sizeY);
}
});
I have a couple of utility methods that show and hide the loader:
public void showLoader() {
MobileApplication.getInstance().showLayer(App.LOADING_GIF);
}
public void hideLoader() {
MobileApplication.getInstance().hideLayer(App.LOADING_GIF);
}
Interestingly, the custom timeout I created (to hide the loader in case there is a stall in the backend) doesn't hide the layer either.
There is an issue with your code: you are overriding Layer::layoutChildren, but you are not calling super.layoutChildren().
If you check the JavaDoc:
Override this method to add the layout logic for your layer. Care should be taken to call this method in overriden methods for proper functioning of the Layer.
This means that you are getting rid of some important parts of the Layer control, such as animations, events and visibility control.
This should work:
#Override
public void layoutChildren() {
super.layoutChildren();
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(sizeX, sizeY);
resizeRelocate(getGlassPane().getWidth() - sizeX) / 2, getGlassPane().getHeight() - sizeY) / 2, sizeX, sizeY);
}
On a side note, for the hide transition, you should use setHideTransitionFactory.
So this is what I have done to solve this. From the Gluon Docs on the hide() method:
If this layer is showing, calling this method will hide it. If a hide transition is present, it is played before hiding the Layer. Care should be taken to call this only once LifecycleEvent.SHOWN has been fired.
Thus, I was realizing that the response from the backend was coming before the layer was fully shown. Thus, I modified the overridden hide() method as follows:
#Override
public void hide() {
if (this.isShowing()) {
this.setOnShown(e -> {
this.setBackgroundFade(0.0);
super.hide();
});
} else {
super.hide();
}
}
So if the layer is still in LifecycleEvent.SHOWING mode when being told to hide, make sure that it hides when it is shown. Otherwise it is already shown so hide it.

Android action bar tabs

Currently I'm working with action bar tabs they are shown perfectly with 4.1 device but when i run on same screen size in lower version 4.0 then the action bar tabs are shown as "ActionBar spinner navigation"
I want them to be shown like this one:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ParseAnalytics.trackAppOpened(getIntent());
ParseUser currentUser = ParseUser.getCurrentUser();
if(currentUser == null) {
navigateToLogin();
} else {
Log.i(TAG, currentUser.getUsername());
}
final ActionBar actionBar = getActionBar();
//actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mSectionPargerAdapter = new SectionPargerAdapter(this, getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionPargerAdapter);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
for (int i = 0; i < mSectionPargerAdapter.getCount(); i++) {
// Create a tab with text corresponding to the page title defined by
// the adapter. Also specify this Activity object, which implements
// the TabListener interface, as the callback (listener) for when
// this tab is selected.
actionBar.addTab(actionBar.newTab()
.setText( mSectionPargerAdapter.getPageTitle(i))
.setTabListener(MainActivity.this));}
}
});
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
This is optimization done by system. The second view is called stacked action bar and first is called action bar with spinner. You can achieve what you want by first adding all tabs and then setting the mode of action bar as
setNavigationMode(ActionBar.NAVIGATION_MODE_TABS)

My AsyncTask does not update UI smoothly (animation)

I want to make a TextView appear little by little, like animation. The problem is, the animation is not smooth. It gets stuck for a little while sometimes and then resumes. Sometimes even worse, it goes back... I mean, the TextView gets bigger and bigger but suddenly gets smaller then bigger again. Could anyone help me?
private class UnfoldTask extends AsyncTask<Integer, Integer, Integer> {
View view;
public UnfoldTask(View v) {
this.view = v;
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = 0;
view.setLayoutParams(pa);
}
#Override
protected Integer doInBackground(Integer... maxHeight) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
while (pa.height < maxHeight[0]) {
pa.height += (int) (24 * getResources().getDisplayMetrics().density + 0.5f);
sleep(100);
publishProgress(pa.height);
}
return maxHeight[0];
}
private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
#Override
protected void onProgressUpdate(Integer... values) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = values[0];
view.setLayoutParams(pa);
}
#Override
protected void onPostExecute(Integer result) {
ViewGroup.LayoutParams pa = view.getLayoutParams();
pa.height = result;
view.setLayoutParams(pa);
}
}
You should be using a scale animation for this. Here's an example:
ScaleAnimation animation = new ScaleAnimation(1, 2, 1, 2, centerX, centerY); // Scales from normal size (1) to double size (2). centerX/Y is the center of your text view. Change this to set the pivot point of your animation.
animation.setDuration(1000);
myTextView.startAnimation(animation);
You can use droidQuery to simplify this:
//this will set the height of myView to 0px.
$.with(myView).height(0);
//when you are ready to animate to height (in pixels):
$.with(myView).animate("{height:" + height + "}", new AnimationOptions());
Check the documentation if you want to get fancy - such as adding duration, and event callbacks. If you are still noticing non-smooth animation, consider adding the application attribute to your AndroidManifest:
android:hardwareAccelerated="true"

Resources