8

I'm looking for a technique to determine in Java if the Android Wear device screen is round or rectangular. Note that this isn't just about layouts; my code actually needs to know which shape it's working with, because they're handled differently.

As far as I can see from code samples online, two different approaches should be possible - but I've been unable to get either of them to work. I'll include them here to eliminate them from the running, or for possible troubleshooting (if anyone can see the problem with them). Please don't refer me to another SO post that just reiterates the solutions that aren't working for me here.

Note that all code here is running on the watch. Also, I'm still using Eclipse, FWIW.

The most straightforward method I've seen involves adding an onApplyWindowInsets() listener to a view in my layout. So I created a listener that looks like this:

@Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
    if (insets.isRound()) {
     displayShape = "round";
    } else {
     displayShape = "rectangular";
    }
    return null;
}

and added it to the root view of my layout with code like this:

view.setOnApplyWindowInsetsListener(this);

in my onCreate() method. Looks OK as far as it goes - but the listener never gets called. I also found advice saying that I needed to invoke it manually, as such:

view.requestApplyInsets();

but that didn't seem to make any difference. I've experimented with putting it on different views, in different lifecycle methods, and so forth, but never once saw it actually get called in my app. This is running on my LG G Watch, BTW.

The second approach is something of a hack, and is based on the published WatchViewStub helper class. I jumped through the hoops to get the wearable support library imported into an Eclipse project, then added the following to my root layout:

<android.support.wearable.view.WatchViewStub 
    android:id="@+id/watch_view_stub"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:rectLayout="@layout/rect"
    app:roundLayout="@layout/round"
    />

and created rect.xml as such:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:id="@+id/layout_type"
    android:text="rectangular"
    />

and round.xml like this:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:id="@+id/layout_type"
    android:text="round"
    />

Finally, in my onCreate() I added the following Java code:

    final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
    stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
        @Override
        public void onLayoutInflated(WatchViewStub stub) {
            TextView layoutType = (TextView) findViewById(R.id.layout_type);
            displayShape = layoutType.getText().toString();
        }
    });

It's a long way around the block, but it should work, right? Not so much... displayShape is always set to "rectangular", indicating that it's always rect.xml that gets used, even when running on a round emulator. [I don't have round-screened hardware to try it on just yet.]

So does anyone see where I've gone wrong with either of these two approaches? Or can you suggest a third way which actually works?

Sterling
  • 6,365
  • 2
  • 32
  • 40

4 Answers4

13

After several days spent chasing false leads, I've finally found the answer. It turns out that it's the android:theme of the application in the manifest that makes the difference.

In order for WatchViewStub to use the correct rect/round layouts, it appears that your application must use @android:style/Theme.DeviceDefault as its theme. Here's an example:

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@android:style/Theme.DeviceDefault">

I expect it would also work if you used a theme that inherited from DeviceDefault, though I haven't tested that. But it appears that if you use any other custom theme, WatchViewStub will not function correctly.

@WaynePiekarski, it'd be nice if this was documented somehere.

Also, here are a couple of other tips that I learned along the way:

  1. The rectangular layout always inflates before the round layout; IOW, on a round device, you'll get two onLayoutInflated() callbacks. This is kind of a pain if you're using the layout inflation to get the screen shape into your Java code, and that turns out to be necessary, because...

  2. Calling setOnApplyWindowInsetsListener() on WatchViewStub prevents the round layout from loading at all (at least in my testing). So if you try to use this callback to determine the screen shape, round devices will still get the square layout.

Finally, a bonus question: Is there any good reason why Android Wear doesn't just report its screen shape as a resource qualifier? You know, like -land, -large, and so on. Why on earth do we need to mess around with WatchViewStub at all?

Sterling
  • 6,365
  • 2
  • 32
  • 40
  • Thanks for the feedback. I will look into the theme issue and see what can be done here. – Wayne Piekarski Oct 16 '14 at 06:27
  • For the resource qualifiers, there is more to a watch display than just round vs square. The Moto 360 has an inset at the bottom of the round display, while the round emulator has no insets. So they are different there as well. So we use WindowInsets https://developer.android.com/reference/android/view/WindowInsets.html to encode the differences between the displays for this. – Wayne Piekarski Oct 16 '14 at 06:29
  • This is absolutely the issue I was having with my `OnApplyWindowInsetsListener.onApplyWindowInsets()` method not being called. The root cause was that I was building a _libgdx_ project that used the theme `GdxTheme` based on `android:Theme`. When I changed `GdxTheme` to inherit from `@android:style/Theme.DeviceDefault` the insets callback method was invoked. Here's the fixed version of `GdxTheme`: ` – outofcoffee Dec 21 '14 at 19:48
3

I'm not sure why it's necessary in your case to employ a callback, but an answer the general question of screen shape is here: Is there any way to detect if the clock is round?

That is, to acquire the current context, and test

context.getResources().getConfiguration().isScreenRound()
Steve White
  • 373
  • 5
  • 9
  • 1
    That is indeed the preferred technique on modern Wear OS. But you'll notice that `isScreenRound` was added in API 23; it didn't exist when this question was first asked. Hence the round-about techniques required back then. – Sterling Jul 21 '20 at 20:05
  • I had to check... You're right, but, API 23 came out October 5, 2015, about a year after the question was asked. This is perforce the correct answer for all Wear OS versions. – Steve White Jul 22 '20 at 21:34
2

In CanvasWatchFaceService.Engine their is a overide method available setOnApplyWindowInsets you can check whether insets is round or square

@Override
    public void onApplyWindowInsets(WindowInsets insets) {
        super.onApplyWindowInsets(insets);
        if(insets.isRound()){
          //round
        } 
        else{
          //square 
        }

    }
rana_sadam
  • 1,216
  • 10
  • 18
  • How is that any different than the `onApplyWindowInsets` method I had in my original post, or which bf2020's answer from Sep 20 discussed? The issue wasn't that I didn't know about this method, it's that I had it in my code and the platform wasn't calling it. – Sterling Mar 10 '15 at 20:43
1

https://plus.google.com/+NicolasPomepuy/posts/ZJ3KZK6uu2e#+NicolasPomepuy/posts/ZJ3KZK6uu2e

and from https://github.com/PomepuyN/WatchviewStubIssue/blob/bcad0de7fa473c757dc27f9dfe65e31561c6097f/wear/src/main/java/com/example/watchviewstubissue/ViewService.java

        mainView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { 
51             @Override 
52             public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 
53                 if (insets.isRound()) { 
54                     Log.d("ViewService", "Round"); 
55                 } else { 
56                     Log.d("ViewService", "Square"); 
57                 } 
58                 return insets; 
59             } 
60         }); 

The diff between your code and his is that you are returning null and he is returning insets.

bf2020
  • 742
  • 4
  • 7
  • Noted, thanks. But that has no bearing on whether `onApplyWindowInsets()` is actually called - which it still isn't after I've made this change in my code. – Sterling Sep 03 '14 at 18:32
  • Also, the G+ post in your first link discussed some problematic emulator instances. I can't dismiss the possibility that this is an emulator problem, but in my AVD Device list I have 4 instances of Android Wear Round, and they all give this same result. – Sterling Sep 03 '14 at 23:55
  • You should only have three instances of AndroidWearRound in your list. I don't know how you can have 4, perhaps you have some left overs remaining from the Preview SDK from the start of the year? Also, the three instances are not all the same, some of them actually pretend to be square devices, and this is being fixed in the next release. Make sure you use the 2nd profile, I know that one works. Otherwise you will always get "Square" as your result. – Wayne Piekarski Sep 04 '14 at 00:23
  • @WaynePiekarski I was willing to believe it was down to emulator issues - until I got a Moto 360. Testing on-device, I still have the same results (WatchViewStub always inflates rect.xml, and onApplyWindowInsets() never gets called). Any ideas? – Sterling Sep 15 '14 at 16:48
  • @String: Have you tried starting up Android Studio and creating a new template project for Android Wear? If you build and run that, it will use XML files and inflate either and round or square layout. I'd like to see you get that working first, to make sure that you can at least get something to work, and we can work from there. – Wayne Piekarski Sep 15 '14 at 21:02
  • @String: Another thing is are you doing all this from an Activity or a Service? WindowInsets do not work the way you would expect when you do them from something that is not an Activity. – Wayne Piekarski Sep 15 '14 at 21:04
  • @WaynePiekarski thanks for the continued help. Before my last comment, I actually created a new project (this is in Eclipse, BTW) and used the default-generated Wear activity template. Same results as in my existing app, and as described in my last comment. So no, I actually can't get any of this to work here. – Sterling Sep 16 '14 at 03:58
  • @String: can you download this app to your phone and then run it on the watch? https://play.google.com/store/apps/details?id=net.waynepiekarski.android.samples.watchviewstub When it runs on the Moto 360, it will show if it is round or square on your display. This demo works perfectly everywhere I have tried it. – Wayne Piekarski Sep 16 '14 at 18:45
  • @String: also, can you try this in Android Studio for me? Something does not make sense here, and I need to get this working somehow and then we can backtrack from there. – Wayne Piekarski Sep 16 '14 at 18:45
  • @WaynePiekarski your example app worked fine (showed round layout on round devices), leading me to believe that it was a problem with my Eclipse build environment. So I embarked on installing, learning, and migrating to Android Studio, and my minimal test case app worked OK there. But when I brought my real app's code over to AS, it still didn't work. A day's worth of testing changes line-by-line finally revealed the solution; see my answer below. Thanks for all your help. – Sterling Oct 15 '14 at 22:14