playMaker

Author Topic: Notches Hell: knowing which smartphones are infected  (Read 5824 times)

Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772
Notches Hell: knowing which smartphones are infected
« on: May 27, 2018, 07:57:45 AM »



Notches


Hi,

As you may have noted nowadays, some ultra posh smartphones come with screens that occupy almost all of the phone's front.
iPhone X started the trend.
Here's a list of Android phones that do it too: https://www.theandroidsoul.com/android-phones-with-notch-like-iphone-10/

This becomes a problem for the management and display of GUIs.
The notches represent a sort of dead space and if it weren't bad enough, they're also rounded!
A space that you now have to fill in a way or another. Previously, the tops of screens were flat and rectangular, so you'd just anchor your top UI elements to the top of the screen for example and that was it!

In order to load a different UI for these kinds of phones or at least bring a change through code to the UI elements glued to the top of the screen, I thought that I'd use resolutions as an indicator that a phone has notches, but it's not reliable since some smartphones have high resolutions but no notches.

So I have to target devices specifically, or at least groups of them.
Now, I suppose iOS and Android have sort of flags that inform an app that the device it's running on. Flags, like a kind of label if you want, would obviously put some smartphones in a special category: the Notched Ones. :D
So, as a PlayMaker developer, if such a flag exists, is there a way to get that information?

(Annoying) alternative:

If not, one would have to create and maintain a list of all most known notched phones. Then check the device's reference and compare it with the *official* names of each known notched phone, therefore comparing strings.
It is understood that such a list would be character sensitive, so that's why an official list would be needed.
This requires obtaining the reference of a device.
So...
Is the action Get System Info capable of obtaining the exact data needed to target specific smartphones?

« Last Edit: May 28, 2018, 12:38:56 PM by Broken Stylus »

Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772
Re: Notched Hell: knowing which smartphones are infected
« Reply #1 on: May 28, 2018, 08:59:03 AM »
I'll post links to useful pages.

Starting with Apple's interface design guidelines for the iPhone X.
https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/
Of course this won't apply to Android phones but the issues are the same. Beware the SIX rounded corners. Yes, the notches (or insets) have two corners because of the central element at the top of the screen.

An older thread at Unity:
https://forum.unity.com/threads/iphone-x-notch-in-screen.495028/

Quote from: jason_yak
Hi all ... if this is helpful to anyone, I've been able to work out the exact pixel values for the safe area for iPhone X using Xcode 9.0, assuming you don't use native UI Kit views. So this is for landscape orientation:

iPhone X overall dimensions
2436 x 1125 pixels

Overall safe area
2172 x 1062 pixels

Left & right insets (accounts for the notch and curved corners, plus a margin)
132 pixels each

Bottom inset (accounts for the bottom nav bar)
63 pixels

Top inset
zero pixels

Quote from: jason_yak
If anyone wants to pipe in their own solution, you could use the good old uidevice-extenision:
https://stackoverflow.com/questions...evice-make-and-model-on-ios/11197770#11197770

New model codes are apparently this, they are untested though obviously... so take care to research further if you're relying on them:
iPhone10,1 - iPhone 8 CDMA
iPhone10,4 - iPhone 8 GSM
iPhone10,2 - iPhone 8 Plus CDMA
iPhone10,5 - iPhone 8 Plus GSM
iPhone10,3 - iPhone X CDMA
iPhone10,6 - iPhone X GSM

Another thread at Unity's board, official:
https://forum.unity.com/threads/iphone-8-8plus-x-support.498078/
Be aware that there's a current bug that prevents Unity's player from recognizing an iOS device properly when building for the SDK Simulator (instead of targeting for a "real" app, in which case the bug isn't present):
https://issuetracker.unity3d.com/issues/ios-simulator-unityengine-dot-ios-dot-devicegeneration-always-returns-unknown-on-ios-simulator-builds


Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772
Re: Notched Hell: knowing which smartphones are infected
« Reply #2 on: May 28, 2018, 10:13:07 AM »
iOS centric post.

I'm not sure right now how much support Unity provides for Xcode's storyboards.
In any case, if the build needs to be modified manually, here's a way to do so:
https://oleb.net/blog/2014/08/replacing-launch-images-with-storyboards/

Also, quoting from the long Unity thread about the iPhone X:

Quote from: jason_yak
Quote from: mehradmbs
and a question about iPhone-X in unity, i use this code to find out if its iphone X or not:
if(SystemInfo.deviceModel == "iPhone10,3" || SystemInfo.deviceModel == "iPhone10,6")...
you think it works?
 

atest versions of Unity have this now: Device.generation == DeviceGeneration.iPhoneX

Another way could be the res. It's not recommended, but if this code only runs on iOS then there's no other devices at the moment with that resolution. if ( (Screen.width == 2436 && Screen.height == 1125) || Device.generation == DeviceGeneration.iPhoneX ){

If any of you have downloaded the 'Get System Info' action from the Ecosystem, it uses the old SystemInfo.--- functions. An action with Device.generation would be prefered.
As seen here, this new action could recognize the iPhoneX. Would only work for iOS.

Example:
https://answers.unity.com/questions/1432365/how-to-detect-iphone-x-or-ipad-using-iosdevicegene.html

Code: [Select]
bool deviceIsIphoneX = UnityEngine.iOS.Device.generation == UnityEngine.iOS.DeviceGeneration.iPhoneX;
 if (deviceIsIphoneX) {
     // Do something for iPhone X
 }





Unity has an API for returning safe areas.

Kindly provided by _Adriaan, here's a script you may want to attach to every single UI canvas of your game:
https://forum.unity.com/threads/canvashelper-resizes-a-recttransform-to-iphone-xs-safe-area.521107/

Code: [Select]
     
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.Events;
     
    [RequireComponent(typeof(Canvas))]
    public class CanvasHelper : MonoBehaviour
    {
       public static UnityEvent onOrientationChange = new UnityEvent();
       public static UnityEvent onResolutionChange = new UnityEvent();
       public static bool isLandscape { get; private set; }
       
       private static List<CanvasHelper> helpers = new List<CanvasHelper>();
       
       private static bool screenChangeVarsInitialized = false;
       private static ScreenOrientation lastOrientation = ScreenOrientation.Unknown;
       private static Vector2 lastResolution = Vector2.zero;
       private static Vector2 lastSafeArea = Vector2.zero;
       
       private static Vector2 wantedReferenceResolution = new Vector2(2048f, 2048f);
       private static Camera wantedCanvasCamera;
       
       private Canvas canvas;
       private CanvasScaler scaler;
       private RectTransform rectTransform;
       
       private RectTransform safeAreaTransform;
       
       void Awake()
       {
           if(!helpers.Contains(this))
               helpers.Add(this);
           
           canvas = GetComponent<Canvas>();
           scaler = GetComponent<CanvasScaler>();
           rectTransform = GetComponent<RectTransform>();
           
           UpdateReferenceResolution();
           UpdateCanvasCamera();
           
           safeAreaTransform = transform.Find("SafeArea") as RectTransform;
           
           if(!screenChangeVarsInitialized)
           {
               lastOrientation = Screen.orientation;
               lastResolution.x = Screen.width;
               lastResolution.y = Screen.height;
               lastSafeArea = Screen.safeArea.size;
           
               screenChangeVarsInitialized = true;
           }
       }
       
       void Start()
       {
           ApplySafeArea();
       }
       
       void Update()
       {
           if(helpers[0] != this)
               return;
               
           if(Application.isMobilePlatform)
           {
               if(Screen.orientation != lastOrientation)
                   OrientationChanged();
               
               if(Screen.safeArea.size != lastSafeArea)
                   SafeAreaChanged();
           }
           else
           {
               //resolution of mobile devices should stay the same always, right?
               // so this check should only happen everywhere else
               if(Screen.width != lastResolution.x || Screen.height != lastResolution.y)
                   ResolutionChanged();
           }
       }
       
       void ApplySafeArea()
       {
           if(safeAreaTransform == null)
               return;
           
           var safeArea = Screen.safeArea;
           
           var anchorMin = safeArea.position;
           var anchorMax = safeArea.position + safeArea.size;
           anchorMin.x /= canvas.pixelRect.width;
           anchorMin.y /= canvas.pixelRect.height;
           anchorMax.x /= canvas.pixelRect.width;
           anchorMax.y /= canvas.pixelRect.height;
           
           safeAreaTransform.anchorMin = anchorMin;
           safeAreaTransform.anchorMax = anchorMax;
           
           //Debug.Log(
           //    "ApplySafeArea:" +
           //    "\n Screen.orientation: " + Screen.orientation +
           //    #if UNITY_IOS
           //    "\n Device.generation: " + UnityEngine.iOS.Device.generation.ToString() +
           //    #endif
           //    "\n Screen.safeArea.position: " + Screen.safeArea.position.ToString() +
           //    "\n Screen.safeArea.size: " + Screen.safeArea.size.ToString() +
           //    "\n Screen.width / height: (" + Screen.width.ToString() + ", " + Screen.height.ToString() + ")" +
           //    "\n canvas.pixelRect.size: " + canvas.pixelRect.size.ToString() +
           //    "\n anchorMin: " + anchorMin.ToString() +
           //    "\n anchorMax: " + anchorMax.ToString());
       }
       
       void UpdateCanvasCamera()
       {
           if(canvas.worldCamera == null && wantedCanvasCamera != null)
               canvas.worldCamera = wantedCanvasCamera;
       }
       
       void UpdateReferenceResolution()
       {
           if(scaler.referenceResolution != wantedReferenceResolution)
               scaler.referenceResolution = wantedReferenceResolution;
       }
       
       void OnDestroy()
       {
           if(helpers != null && helpers.Contains(this))
               helpers.Remove(this);
       }
       
       private static void OrientationChanged()
       {
           //Debug.Log("Orientation changed from " + lastOrientation + " to " + Screen.orientation + " at " + Time.time);
           
           lastOrientation = Screen.orientation;
           lastResolution.x = Screen.width;
           lastResolution.y = Screen.height;
           
           isLandscape = lastOrientation == ScreenOrientation.LandscapeLeft || lastOrientation == ScreenOrientation.LandscapeRight || lastOrientation == ScreenOrientation.Landscape;
           onOrientationChange.Invoke();
           
       }
       
       private static void ResolutionChanged()
       {
           if(lastResolution.x == Screen.width && lastResolution.y == Screen.height)
               return;
           
           //Debug.Log("Resolution changed from " + lastResolution + " to (" + Screen.width + ", " + Screen.height + ") at " + Time.time);
           
           lastResolution.x = Screen.width;
           lastResolution.y = Screen.height;
           
           isLandscape = Screen.width > Screen.height;
           onResolutionChange.Invoke();
       }
       
       private static void SafeAreaChanged()
       {
           if(lastSafeArea == Screen.safeArea.size)
               return;
           
           //Debug.Log("Safe Area changed from " + lastSafeArea + " to " + Screen.safeArea.size + " at " + Time.time);
           
           lastSafeArea = Screen.safeArea.size;
           
           for (int i = 0; i < helpers.Count; i++)
           {
               helpers[i].ApplySafeArea();
           }
       }
       
       public static void SetAllCanvasCamera(Camera cam)
       {
           if(wantedCanvasCamera == cam)
               return;
           
           wantedCanvasCamera = cam;
           
           for (int i = 0; i < helpers.Count; i++)
           {
               helpers[i].UpdateCanvasCamera();
           }
       }
       
       public static void SetAllReferenceResolutions(Vector2 newReferenceResolution)
       {
           if(wantedReferenceResolution == newReferenceResolution)
               return;
               
           //Debug.Log("Reference resolution changed from " + wantedReferenceResolution + " to " + newReferenceResolution + " at " + Time.time);
           
           wantedReferenceResolution = newReferenceResolution;
           
           for (int i = 0; i < helpers.Count; i++)
           {
               helpers[i].UpdateReferenceResolution();
           }
       }
       
       public static Vector2 CanvasSize()
       {
           return helpers[0].rectTransform.sizeDelta;
       }
       
       public static Vector2 SafeAreaSize()
       {
           for (int i = 0; i < helpers.Count; i++)
           {
               if(helpers[i].safeAreaTransform != null)
               {
                   return helpers[i].safeAreaTransform.sizeDelta;
               }
           }
           
           return CanvasSize();
       }
       
       public static Vector2 GetReferenceResolution()
       {
           return wantedReferenceResolution;
       }
       
       
    }
     

Quote from: _Adriaan
- put the script on the GameObject called 'Canvas' (or the GameObject with the Canvas script)
- add a child RectTransform to that Canvas that is called 'SafeArea', and exactly that.
- add anything you want to adhere to the safe area as a child to the SafeArea object

Save this code in a cs file, perhaps "iphonexsafearea.cs" for example, and add it as a component to the canvas.
I haven't tested this yet but if I get it right, the UI elements in that "Safe Area" child will logically not expand into the notches' screen zones, theoretically leaving you the possibility to fill these notches with UI elements placed in another canvas or I suspect, any other child not named "SafeArea".
In other words, the UI elements under this child will be limited to the safe rectangle, as described in Apple's Human Interface Guideline.
The script does a few other things too that you may want to test out.
I can't tell from it if it supports all three types of canvas scaling.
You'll need a very recent version of Unity and Xcode for this to work btw.


Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772
Re: Notched Hell: knowing which smartphones are infected
« Reply #3 on: May 28, 2018, 12:37:05 PM »
Android

It looks like you will have to build for Android P (API level: P) and from there, do a few tests with your apk.
https://developer.android.com/preview/features#cutout
There's no automagic way I know of right now to handle this in Unity other than doing a SystemInfo scan and comparing the string to a list of device names, either in a XML fille or a small array.

https://support.google.com/googleplay/answer/1727131?hl=en
Using the list from the first post, here are the official Android device names that use notches (data taken from the pdf):


Brand                Marketing name                               Device                         Model                         
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei
Huawei

Asus
Asus
Asus
Asus
Asus
Asus
Asus

Oppo
Oppo

Vivo
Vivo
Vivo
Vivo
Vivo

OnePlus

LGE
LGE
LGE
LGE

Leagoo

Oukitel

Sharp
Sharp
Sharp
Sharp
Sharp
Sharp
P20
P20
P20
P20
P20 Pro
P20 Pro
P20 Pro
P20 Pro
P20 Pro
P20 Pro
P20 Pro
P20 Pro
P20 lite
P20 lite
P20 lite
Honor 10
Honor 10
Honor 10
Honor 10

ZenFone 5
ZenFone 5
ZenFone 5 (A500CG)
ZenFone 5 (A500CG)
ZenFone 5 (A501CG)
ZenFone 5 (A502CG)
ZenFone 5 (ZE620KL)

R15
R15 Pro

V9
V9 Youth
X21
X21i A
X21 UD

OnePlus 6

LG G7 ThinQ
LG G7 ThinQ
LG G7 ThinQ
LG G7 ThinQ

Leagoo S9

Oukitel U18

SHARP AQUOS S2
SHARP AQUOS S2
SHARP AQUOS S2 Plus
SHARP AQUOS S2 Plus
SHARP AQUOS S3
SHARP AQUOS S3
HWEML
HWEML
HWEML
HWEML
HW-01K
HWCLT
HWCLT
HWCLT
HWCLT
HWCLT
HWCLT
HWCLT
HWANE
HWANE
HWANE
HWCOL
HWCOL
HWCOL
HWCOL

ASUS_T00F
ASUS_T00J
ASUS_T00F
ASUS_T00F1
ASUS_T00J1
ASUS_T00K
ASUS_X00QD

-
-

1727ID
-
-
PD1801
-

OnePlus6

judyln
judyln
judyln
judyln

-

U18

SH-Z01
SS2
SAT
SAT
HH1
SD1
EML-AL00
EML-L09
EML-L29
EML-TL00
HW-01K
CLT-AL00
CLT-AL01
CLT-L04
CLT-L09
CLT-L29
CLT-TL00
CLT-TL01
ANE-LX1
ANE-LX2
ANE-LX3
COL-AL00
COL-AL10
COL-L29
COL-TL10

ASUS_T00F
ASUS_T00J
ASUS_T00F
ASUS_T00F
ASUS_T00J
ASUS_T00K
ASUS_X00QD

-
-

vivo 1727
-
-
vivo X21i A
-

ONEPLUS A6003

LG-G710
LM-G710
LM-G710N
LM-G710VM

-

U18

SH-Z01
FS8010
FS8008
FS8016
FS8032
FS8015





I verified each smartphone reference through GSM Arena's catalog of Android phones.

An interesting article (not related to Unity):
https://v-play.net/cross-platform-app-development/notch-developer-guide-ios-android
And another note on display cutout settings:
http://www.yudiz.com/android-p-features-api/


« Last Edit: May 28, 2018, 12:41:51 PM by Broken Stylus »

Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772
Re: Notches Hell: knowing which smartphones are infected
« Reply #4 on: March 19, 2019, 06:53:31 AM »
I don't know if there's been any evolution on PlayMaker's side, and I haven't been able to think of a better idea than putting all elements of a canvas into a greater parent, called Safe Area, and this one you resize after detecting if the phone has notches or not if you don't want to cram some buttons in the notches and intend on either leaving the space empty (with more UI padding) or having black bars.
Absolutely mischievous, don't forget to leave an area for the slide button at the bottom too!

Here are some references to how Unity handles this more or less natively.
https://forum.unity.com/threads/iphone-8-8plus-x-support.498078/
https://connect.unity.com/p/updating-your-gui-for-the-iphone-x-and-other-notched-devices
https://forum.unity.com/threads/iphone-x-notch-in-screen.495028/
https://forum.unity.com/threads/support-for-screen-safearea-on-notch-iphone-x-like-android-devices.538924/

Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772
Re: Notches Hell: knowing which smartphones are infected
« Reply #5 on: March 19, 2019, 12:41:16 PM »
If you want to handle this on Android, you'll have to update Unity to version 2018.3.0 at the very least.
Back up everything before!

There's a free asset to help you edit and manage the notches and safe areas for iPhoneX and Xs. Proper Rect values are saved inside the SafeArea.cs script.
Safe Area Helper

In the notes there's something interesting to catch:

Code: [Select]
// Safe area implementation for notched mobile devices. Usage:
    ///  (1) Add this component to the top level of any GUI panel.
    ///  (2) If the panel uses a full screen background image, then create an immediate child and put the component on that instead, with all other elements childed below it.
    ///      This will allow the background image to stretch to the full extents of the screen behind the notch, which looks nicer.
    ///  (3) For other cases that use a mixture of full horizontal and vertical background stripes, use the Conform X & Y controls on separate elements as needed.

The idea is to query the safe area. They provide a script for that to stick on a uGUI Panel. Transforming this short script into a PM action would be very useful to obtain the proper Rect Transform dimensions to apply to the main UI parent.

That's taken from the blog post but I think you can bypass (2) and avoid using an intermediary UI element if you use a plain rectangular background that fills the whole screen by making sure that your background panel stretches beyond the top and bottom screen borders. Simply add a lot of extra pixels that reach offscreen, but still leave the anchors locked onto the corners of the Safe Area parent, so that they cover more than the height or width of pixels you'll have to sacrifice. These phones are high resolution devices so be generous. :-)
Do the same for left, right* and bottom borders if you use the app in Landscape display mode.
This should allow you to have ALL the elements of a canvas inside one single parent.

* there won't be anything on that side but it's done for the sake of symmetry of your GUI.

Unity allows devs to grab the rect data: Screen.safeArea.

Has anyone made a custom action for that already? I looked on ecosystem but my search fu sucks some days, couldn't find any. Perhaps it's named differently?

Android's safe area (API 28) is handled through the option 'Render outside safe area' in Player Settings.
« Last Edit: March 19, 2019, 01:00:16 PM by Broken Stylus »

Broken Stylus

  • Beta Group
  • Hero Member
  • *
  • Posts: 772