תמיכה במסכים

בהמשך מפורטים העדכונים שבוצעו בתחומים הספציפיים האלה של המסכים:

שינוי הגודל של פעילויות ותצוגות

כדי לציין שאפליקציה לא תומכת במצב של חלונות מרובים או בשינוי גודל, בפעילויות נעשה שימוש במאפיין resizeableActivity=false. אלה כמה מהבעיות הנפוצות באפליקציות כשמשנים את הגודל של פעילויות:

  • לפעילות יכולה להיות הגדרה שונה מהאפליקציה או מרכיב אחר שאינו חזותי. שגיאה נפוצה היא קריאת מדדי הצגה מההקשר של האפליקציה. הערכים שיוחזרו לא יותאמו למדדים של האזור הגלוי שבו מוצגת הפעילות.
  • יכול להיות שפעילות לא תטפל בשינוי הגודל ותקרוס, תציג ממשק משתמש מעוות או תאבד את המצב שלה בגלל הפעלה מחדש ללא שמירת המצב של המכונה.
  • אפליקציה עשויה לנסות להשתמש בקואורדינטות קלט מוחלטות (במקום קואורדינטות יחסיות למיקום החלון), דבר שעלול לגרום לשיבושים בקלט במצב 'כמה חלונות בו-זמנית'.

ב-Android 7 ואילך, אפשר להגדיר אפליקציה resizeableActivity=false כך שתמיד תפעל במצב מסך מלא. במקרה כזה, הפלטפורמה מונעת מפעילויות שלא ניתן לשנות את הגודל שלהן לעבור למסך מפוצל. אם המשתמש מנסה להפעיל פעילות שלא ניתן לשנות את הגודל שלה מהמרכז להפעלת אפליקציות כשהמכשיר כבר במצב מסך מפוצל, הפלטפורמה תצא ממצב המסך המפוצל ותפעיל את הפעילות שלא ניתן לשנות את הגודל שלה במצב מסך מלא.

אסור להפעיל אפליקציות שהגדירו את המאפיין הזה ל-false במניפסט באופן מפורש במצב של חלונות מרובים, אלא אם מופעל מצב התאימות:

  • אותה הגדרה חלה על התהליך, שמכיל את כל הפעילויות והרכיבים שאינם פעילויות.
  • ההגדרה שחלה עומדת בדרישות CDD למסכים שתואמים לאפליקציות.

ב-Android 10, הפלטפורמה עדיין מונעת מפעילויות שלא ניתן לשנות את הגודל שלהן לעבור למצב מסך מפוצל, אבל אפשר לשנות את הגודל שלהן באופן זמני אם הפעילות הצהירה על כיוון או יחס גובה-רוחב קבועים. אם לא, הפעילות תשנה את הגודל כדי למלא את המסך כולו, כמו ב-Android 9 וגרסאות ישנות יותר.

בהטמעה שמוגדרת כברירת מחדל חלה המדיניות הבאה:

כשפעילות מוצהרת כבלתי תואמת למספר חלונות באמצעות השימוש במאפיין android:resizeableActivity, וכשהפעילות הזו עומדת באחד מהתנאים שמפורטים בהמשך, במקרה שבו צריך לשנות את הגדרת המסך שהוחלו, הפעילות והתהליך נשמרים עם ההגדרה המקורית והמשתמש מקבל אפשרות להפעיל מחדש את תהליך האפליקציה כדי להשתמש בהגדרת המסך המעודכנת.

  • הכיוון קבוע באמצעות החלת android:screenOrientation
  • האפליקציה כוללת יחס גובה-רוחב מקסימלי או מינימלי שמוגדר כברירת מחדל על ידי טירגוט לרמת API, או שהיא מצהירה על יחס הגובה-רוחב באופן מפורש

באיור הזה מוצגת פעילות שלא ניתן לשנות את הגודל שלה עם יחס גובה-רוחב מוגדר. כשמקפלים את המכשיר, החלון מצומצם כך שיתאים לאזור, תוך שמירה על יחס הגובה-רוחב באמצעות פורמט letterbox מתאים. בנוסף, בכל פעם שמשתנה אזור התצוגה של הפעילות, מוצגת למשתמש אפשרות להפעיל מחדש את הפעילות.

כשפותחים את המכשיר, ההגדרות, הגודל ויחס הגובה-רוחב של הפעילות לא משתנים, אבל מוצגת האפשרות להפעיל מחדש את הפעילות.

אם השדה resizeableActivity לא מוגדר (או שהוא מוגדר לערך true), האפליקציה תומכת במלואה בשינוי הגודל.

הטמעה

פעילות שלא ניתן לשנות את הגודל שלה עם כיוון או יחס גובה-רוחב קבועים נקראת בקוד 'מצב תאימות לגודל' (SCM). התנאי מוגדר ב-ActivityRecord#shouldUseSizeCompatMode(). כשפעילות SCM מופעלת, ההגדרות שקשורות למסך (כמו גודל או צפיפות) קבועות בהגדרת ההחרגה המבוקשת, כך שהפעילות כבר לא תלויה בהגדרת התצוגה הנוכחית.

אם הפעילות ב-SCM לא יכולה למלא את המסך כולו, היא תהיה מותאמת למיקום העליון ותהיה ממוקמת במרכז אופקית. הגבולות של הפעילות מחושבים על ידי AppWindowToken#calculateCompatBoundsTransformation().

כשפעילות SCM משתמשת בהגדרת מסך שונה מזו של הקונטיינר שלה (לדוגמה, שינוי גודל המסך או העברת הפעילות למסך אחר), הערך של ActivityRecord#inSizeCompatMode() הוא true ו-SizeCompatModeActivityController (ב-System UI) מקבל את קריאת החזרה (callback) כדי להציג את הלחצן להפעלה מחדש של התהליך.

גדלים ויחסי גובה-רוחב של מסכים

ב-Android 10 יש תמיכה ביחסי גובה-רוחב חדשים, החל מיחסים גבוהים של מסכים ארוכים ודקים ועד ליחסים של 1:1. אפליקציות יכולות להגדיר את ApplicationInfo#maxAspectRatio ואת ApplicationInfo#minAspectRatio של המסך שהן יכולות לטפל בו.

יחסים של אפליקציות ב-Android 10

איור 1. דוגמאות ליחסי גובה-רוחב של אפליקציות שנתמכים ב-Android 10

הטמעות במכשירים יכולות לכלול מסכים משניים בגדלים וברזולוציות קטנים יותר מאלה הנדרשים ב-Android 9, וגם קטנים יותר (רוחב או גובה מינימלי של 2.5 אינץ', 320 DP לפחות עבור smallestScreenWidth), אבל אפשר להציב בהם רק פעילויות שהבעלים הביעו הסכמה לתמוך במסכים הקטנים האלה.

כדי להביע הסכמה, מפתחי האפליקציות צריכים להצהיר על גודל מינימלי נתמך שקטן או שווה לגודל התצוגה היעד. כדי לעשות זאת, משתמשים במאפייני הפריסה של הפעילות android:minHeight ו-android:minWidth בקובץ AndroidManifest.

מדיניות בנושא רשת המדיה

ב-Android 10, מדיניות תצוגה מסוימת מופרדת ומועברת מהטמעת ברירת המחדל של WindowManagerPolicy ב-PhoneWindowManager לסיווגים לכל מסך, למשל:

  • מצב המסך וסיבוב המסך
  • מעקב אחר אירועי תנועה ומפתחות מסוימים
  • ממשק המשתמש של המערכת וחלונות לקישוט

ב-Android 9 (וגרסאות קודמות), הכיתה PhoneWindowManager טיפלה במדיניות התצוגה, במצב ובהגדרות, בסיבוב, במעקב אחר מסגרת חלון הקישוט ועוד. ב-Android 10 רוב הנתונים האלה מועברים לכיתה DisplayPolicy, מלבד מעקב אחר סיבוב, שהועתק לכיתה DisplayRotation.

הגדרות חלון התצוגה

ב-Android 10, ההגדרה של חלון לכל מסך שניתנת להתאמה אישית הורחבה וכוללת:

  • מצב ברירת המחדל של שינוי הגודל של החלונות בתצוגה
  • ערכי Overscan
  • רוטציה של משתמשים ומצב רוטציה
  • גודל, צפיפות ומצב שינוי קנה מידה מאולצים
  • מצב הסרת תוכן (כשהתצוגה מוסר)
  • תמיכה בקישוט המערכת וב-IME

הכיתה DisplayWindowSettings מכילה הגדרות לאפשרויות האלה. הם נשמרים בדיסק במחיצה /data ב-display_settings.xml בכל פעם שמשתנה הגדרה. פרטים נוספים זמינים במאמרים DisplayWindowSettings.AtomicFileStorage ו-DisplayWindowSettings#writeSettings(). יצרני המכשירים יכולים לספק ערכים שמוגדרים כברירת מחדל ב-display_settings.xml עבור הגדרות המכשיר שלהם. עם זאת, מכיוון שהקובץ מאוחסן ב-/data, יכול להיות שיהיה צורך בלוגיקה נוספת כדי לשחזר את הקובץ אם הוא נמחק על ידי מחיקה מוחלטת.

כברירת מחדל, ב-Android 10 DisplayInfo#uniqueId משמש כמזהה של תצוגה כששומרים את ההגדרות. צריך לאכלס את השדה uniqueId לכל המסכים. בנוסף, הוא יציב במסכים פיזיים ובמסכים ברשת. אפשר גם להשתמש ביציאה של צג פיזי בתור המזהה, וניתן להגדיר אותה ב-DisplayWindowSettings#mIdentifier. בכל פעם שמתבצעת כתיבת, כל ההגדרות נכתבות, כך שאפשר לעדכן בבטחה את המפתח שמשמש להצגת רשומה באחסון. למידע נוסף, קראו את המאמר מזהי תצוגה סטטיים.

ההגדרות נשמרות בספרייה /data מסיבות היסטוריות. במקור, הם שימשו לשמירת הגדרות שהמשתמשים הגדירו, כמו כיוון המסך.

מזהי תצוגה סטטיים

ב-Android 9 (וגרסאות קודמות) לא היו מזהים יציבים למסכים במסגרת. כשמסך נוסף למערכת, הערך Display#mDisplayId או DisplayInfo#displayId נוצר עבור המסך הזה על ידי הגדלת מונה סטטי. אם המערכת הוסיפה והסירה את אותו צג, התקבל מזהה שונה.

אם במכשיר יש כמה צגים שזמינים מהפעלה, יכול להיות שהצגים יקבלו מזהים שונים, בהתאם למועד. מערכת Android 9 (וגם גרסאות קודמות) כללה את DisplayInfo#uniqueId, אבל היא לא הכילה מספיק מידע כדי להבדיל בין המסכים, כי מסכים פיזיים זוהו בתור local:0 או local:1, כדי לייצג את המסך המובנה והמסך החיצוני.

ב-Android 10, השדה DisplayInfo#uniqueId משתנה כדי להוסיף מזהה יציב ולאפשר להבדיל בין צגים מקומיים, צגים ברשת וצגים וירטואליים.

סוג התצוגה פורמט
מקומי
local:<stable-id>
רשת
network:<mac-address>
וירטואלי
virtual:<package-name-and-name>

בנוסף לעדכונים של uniqueId, השדה DisplayInfo.address מכיל את השדה DisplayAddress, מזהה תצוגה שמישן גם אחרי הפעלות מחדש. ב-Android 10, DisplayAddress תומך במסכים פיזיים ובמסכים ברשת. השדה DisplayAddress.Physical מכיל מזהה תצוגה יציב (זהה למזהה ב-uniqueId) וניתן ליצור אותו באמצעות DisplayAddress#fromPhysicalDisplayId().

ב-Android 10 יש גם שיטה נוחה לקבלת פרטי יציאות (Physical#getPort()). אפשר להשתמש בשיטה הזו במסגרת כדי לזהות באופן סטטי מסכים. לדוגמה, הוא משמש ב-DisplayWindowSettings. השדה DisplayAddress.Network מכיל את כתובת ה-MAC, וניתן ליצור אותו באמצעות DisplayAddress#fromMacAddress().

התוספות האלה מאפשרות ליצרני המכשירים לזהות מסכים במערכות סטטיות עם כמה מסכים, ולהגדיר הגדרות ותכונות שונות של המערכת באמצעות מזהים סטטיים של מסכים, כמו יציאות למסכים פיזיים. השיטות האלה מוסתרות ומיועדות לשימוש רק ב-system_server.

כשנותנים לה את מזהה המסך של HWC (שיכול להיות אטום ולא תמיד יציב), השיטה מחזירה את מספר היציאה בן 8 הביט (שספציפי לפלטפורמה) שמזהה את המחבר הפיזי של פלט המסך, וגם את ה-blob של EDID של המסך. ‏SurfaceFlinger מחלץ מ-EDID מידע על היצרן או הדגם כדי ליצור את מזהי המסך היציבים של 64 ביט שגלויים למסגרת. אם השיטה הזו לא נתמכת או שהיא מחזירה שגיאה, SurfaceFlinger חוזר למצב MD הקודם, שבו DisplayInfo#address הוא null ו-DisplayInfo#uniqueId מקודד באופן קבוע, כפי שמתואר למעלה.

כדי לוודא שהתכונה הזו נתמכת, מריצים את הפקודה:

$ dumpsys SurfaceFlinger --display-id
# Example output.
Display 21691504607621632 (HWC display 0): port=0 pnpId=SHP displayName="LQ123P1JX32"
Display 9834494747159041 (HWC display 2): port=1 pnpId=HWP displayName="HP Z24i"
Display 1886279400700944 (HWC display 1): port=2 pnpId=AUS displayName="ASUS MB16AP"

שימוש ביותר משני מסכים

ב-Android 9 (וגרסאות קודמות), SurfaceFlinger ו-DisplayManagerService הניחו שיש לכל היותר שני מסכים פיזיים עם מזהי 0 ו-1 שמוגדרים בקוד.

החל מ-Android 10, ל-SurfaceFlinger יש אפשרות להשתמש ב-API של Hardware Composer‏ (HWC) כדי ליצור מזהי מסך יציבים, וכך לנהל מספר שרירותי של מסכים פיזיים. למידע נוסף, ראו מזהי תצוגה סטטיים.

המסגרת יכולה לחפש את האסימון IBinder של צג פיזי דרך SurfaceControl#getPhysicalDisplayToken אחרי קבלת מזהה הצג בן 64 הביט מ-SurfaceControl#getPhysicalDisplayIds או מאירוע DisplayEventReceiver של חיבור חם.

ב-Android 10 (וגרסאות ישנות יותר), המסך הפנימי הראשי הוא TYPE_INTERNAL וכל המסכים המשניים מסומנים כ-TYPE_EXTERNAL, ללא קשר לסוג החיבור. לכן, מסכים פנימיים נוספים נחשבים כחיצוניים. כפתרון עקיף, קוד ספציפי למכשיר יכול להניח ערכים ל-DisplayAddress.Physical#getPort אם ה-HWC ידוע והלוגיקה של הקצאת השקעים צפויה.

המגבלה הזו הוסרה ב-Android 11 (ומעלה).

  • ב-Android 11, המסך הראשון שמדווח במהלך האתחול הוא המסך הראשי. סוג החיבור (פנימי או חיצוני) לא רלוונטי. עם זאת, עדיין לא ניתן לנתק את המסך הראשי, ולכן הוא חייב להיות מסך פנימי בפועל. לתשומת ליבכם, בטלפונים מתקפלים מסוימים יש כמה מסכים פנימיים.
  • מסכים משניים מסווגים כראוי כ-Display.TYPE_INTERNAL או כ-Display.TYPE_EXTERNAL (לשעבר Display.TYPE_BUILT_IN ו-Display.TYPE_HDMI, בהתאמה) בהתאם לסוג החיבור שלהם.

הטמעה

ב-Android 9 ובגרסאות ישנות יותר, המסכים מזוהים באמצעות מזהי 32 ביט, כאשר 0 הוא המסך הפנימי, 1 הוא המסך החיצוני, [2, INT32_MAX] הם מסכים וירטואליים של HWC ו--1 מייצג מסך לא תקין או מסך וירטואלי שאינו של HWC.

החל מ-Android 10, למסכים מוקצית זהות יציבה וקבועה, שמאפשרת ל-SurfaceFlinger ול-DisplayManagerService לעקוב אחרי יותר משני מסכים ולזהות מסכים שצוינו בעבר. אם ה-HWC תומך ב-IComposerClient.getDisplayIdentificationData ומספק נתוני זיהוי של המסך, SurfaceFlinger מנתח את המבנה של EDID ומקצה מזהי מסך יציבים של 64 ביט למסכים פיזיים ולמסכים וירטואליים של HWC. המזהים מפורטים באמצעות סוג אפשרות, שבו הערך null מייצג תצוגה לא חוקית או תצוגה וירטואלית שאינה HWC. ללא תמיכה ב-HWC, SurfaceFlinger חוזר לפעולה מהדור הקודם עם עד שני מסכים פיזיים.

התמקדות בכל מסך בנפרד

כדי לתמוך בכמה מקורות קלט שמטרגטים מסכים נפרדים בו-זמנית, אפשר להגדיר את Android 10 כך שיתמוך בכמה חלונות ממוקדים, עד אחד לכל מסך. התכונה הזו מיועדת רק לסוגים מיוחדים של מכשירים, שבהם כמה משתמשים מקיימים אינטראקציה עם אותו מכשיר בו-זמנית ומשתמשים בשיטות קלט או במכשירים שונים, כמו Android Automotive.

מומלץ מאוד לא להפעיל את התכונה הזו במכשירים רגילים, כולל מכשירים עם כמה מסכים או מכשירים שמשמשים לחוויית שימוש שדומה למחשב. הסיבה העיקרית לכך היא שיקולי אבטחה, כי יכול להיות שהמשתמשים לא ידעו באיזה חלון מופיע המיקוד של הקלט.

נניח שהמשתמש מזין מידע מאובטח בשדה להזנת טקסט, למשל כשהוא מתחבר לאפליקציית בנק או מזין טקסט שמכיל מידע רגיש. אפליקציה זדונית עשויה ליצור תצוגה וירטואלית מחוץ למסך כדי לבצע פעילות, כולל שדה להזנת טקסט. הן פעילויות לגיטימיות והן פעילויות זדוניות נמצאות במוקד, ובשתיהן מוצג אינדיקטור קלט פעיל (סמן מהבהב).

עם זאת, מכיוון שהקלט ממקלדת (חומרה או תוכנה) מוזן לפעילות העליונה בלבד (האפליקציה שהופעל לאחרונה), אפליקציה זדונית יכולה ליצור מסך וירטואלי מוסתר כדי לקבל את הקלט של המשתמש, גם אם משתמשים במקלדת תוכנה במסך הראשי של המכשיר.

משתמשים ב-com.android.internal.R.bool.config_perDisplayFocusEnabled כדי להגדיר את מיקום המיקוד בכל תצוגה.

תאימות

הבעיה: ב-Android 9 ובגרסאות קודמות, רק חלון אחד במערכת יכול להיות ממוקד בכל פעם.

פתרון: במקרה הנדיר שבו שני חלונות מאותו תהליך יהיו בפוקוס, המערכת תתמקד רק בחלון שנמצא גבוה יותר בסדר Z. האיסור הזה יוסר באפליקציות שמטרגטות את Android 10, והן אמורות לתמוך בכמה חלונות שאפשר להתמקד בהם בו-זמנית.

הטמעה

WindowManagerService#mPerDisplayFocusEnabled קובע את הזמינות של התכונה הזו. ב-ActivityManager, עכשיו נעשה שימוש ב-ActivityDisplay#getFocusedStack() במקום במעקב גלובלי במשתנה. ActivityDisplay#getFocusedStack() קובע את המיקוד על סמך סדר Z במקום לשמור את הערך במטמון. כך רק מקור אחד, WindowManager, צריך לעקוב אחרי סדר ה-Z של הפעילויות.

ActivityStackSupervisor#getTopDisplayFocusedStack() משתמש בגישה דומה במקרים שבהם צריך לזהות את מקבץ ה-stack העליון שממוקד במערכת. המערכת עוברת על הערימות מלמעלה למטה, ומחפשת את הערימה הראשונה שעומדת בדרישות.

עכשיו אפשר להציג ב-InputDispatcher כמה חלונות ממוקדים (אחד לכל מסך). אם אירוע קלט הוא ספציפי למסך, הוא נשלח לחלון שממוקד במסך המתאים. אחרת, ההודעה תישלח לחלון הממוקד במסך הממוקד, כלומר המסך שבו המשתמש קיים אינטראקציה לאחרונה.

פרטים נוספים זמינים בInputDispatcher::mFocusedWindowHandlesByDisplay ובInputDispatcher::setFocusedDisplay(). אפליקציות מוקדמות מתעדכנות גם בנפרד ב-InputManagerService דרך NativeInputManager::setFocusedApplication().

ב-WindowManager, מתבצע גם מעקב נפרד אחרי חלונות שמתמקדים בהם. אפשר לעיין ב-DisplayContent#mCurrentFocus וב-DisplayContent#mFocusedApp ובשימושים המתאימים. שיטות המעקב והעדכון של מוקד העניין הועברו מ-WindowManagerService ל-DisplayContent.