תמיכה במסכים

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

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

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

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

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

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

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

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

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

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

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

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

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

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

הטמעה

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

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

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

גדלי תצוגה ויחסי גובה-רוחב

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

יחסי גובה-רוחב של אפליקציות ב-Android 10

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

יכול להיות שבמכשירים מסוימים יש מסכים משניים עם גדלים ורזולוציות קטנים יותר מאלה שנדרשים ב-Android 9 ומגרסאות קודמות (רוחב או גובה מינימלי של 2.5 אינץ', מינימום של 320 DPI ל-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 (ובגרסאות קודמות) לא סופקו מזהים יציבים לתצוגות ב-framework. כשמסך נוסף למערכת, נוצר עבורו 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() משתמש בגישה דומה במקרים שבהם צריך לזהות את מחסנית הפוקוס העליונה במערכת. המעבר בין המערכים מתבצע מלמעלה למטה, בחיפוש אחר המערך הראשון שעומד בדרישות.

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

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

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