עדכונים שבוצעו באזורים הספציפיים לתצוגה מפורטים בהמשך:
- שינוי הגודל של פעילויות ותצוגות
- גדלי תצוגה ויחסי גובה-רוחב
- מדיניות הפרסום ברשת המדיה
- הגדרות חלון התצוגה
- מזהים לתצוגה סטטיים
- שימוש ביותר משני מסכים
- מיקוד לכל רשת המדיה
שינוי הגודל של פעילויות ותצוגות
כדי לציין שאפליקציה לא תומכת במצב של חלונות מרובים או בשינוי גודל, בפעילויות נעשה שימוש במאפיין 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
של המסך שבו הן יכולות לטפל.
איור 1. דוגמאות ליחסי גובה-רוחב של אפליקציות שנתמכים ב-Android 10
בהטמעות של מכשירים יכולים להיות מסכים משניים עם גדלים ורזולוציות קטנים יותר מאלה שנדרשים ב-Android 9, ונמוכים יותר (לפחות רוחב או גובה של 2.5 אינץ', מינימום 320DP ל-smallestScreenWidth
). עם זאת, אפשר לבצע כאן רק פעילויות שמותאמות לתמיכה במסכים קטנים כאלה.
אפליקציות יכולות להצטרף באמצעות הצהרה על גודל נתמך מינימלי שקטן מגודל התצוגה של היעד או שווה לו. כדי לעשות זאת, צריך להשתמש במאפיינים של פריסת הפעילות android:minHeight
ו-android:minWidth
ב-AndroidManifest.
כללי המדיניות של רשת המדיה
מערכת Android 10 מפרידה ומעבירה כללי מדיניות מסוימים לרשת המדיה מהטמעת ברירת המחדל WindowManagerPolicy
ב-PhoneWindowManager
לקטגוריות של מודעות לרשת המדיה, כמו:
- מצב המסך וסיבוב המסך
- מעקב אחר אירועי תנועה ומפתחות מסוימים
- ממשק המשתמש וחלונות העיצוב של המערכת
ב-Android 9 (וגרסאות קודמות), המחלקה PhoneWindowManager
טיפלה במדיניות התצוגה, במצב ובהגדרות, בסיבוב, במעקב אחרי מסגרת החלונות ועוד. מערכת Android 10 מעבירה את רוב החלק הזה למחלקה DisplayPolicy
, חוץ מהמעקב אחר סבב, שהועברה ל-DisplayRotation
.
הגדרות חלון התצוגה
ב-Android 10, ההגדרה של ניהול החלונות שניתנת להגדרה לכל תצוגה הורחבה, והיא כוללת:
- מצב ברירת המחדל של חלון התצוגה
- ערכי סריקת יתר
- רוטציה של משתמשים ומצב רוטציה
- גודל, צפיפות ומצב שינוי קנה מידה מאולצים
- מצב הסרת תוכן (כשהתצוגה מוסר)
- תמיכה בקישוטים וב-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()
). אפשר להשתמש בשיטה הזו ב-framework כדי לזהות מסכים באופן סטטי. לדוגמה, משתמשים בה ב-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) כדי ליצור מזהי תצוגה יציבים, וכך לנהל מספר שרירותי של מסכים פיזיים. למידע נוסף, ראו מזהים לתצוגה סטטיים.
תוכנת ה-framework יכולה לחפש את האסימון 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.
החל מגרסה 10 של Android, למסכים מוקצית זהות יציבה וקבועה, שמאפשרת ל-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
.