Hỗ trợ quảng cáo hiển thị

Dưới đây là nội dung cập nhật đối với các khu vực dành riêng cho màn hình này:

Thay đổi kích thước hoạt động và màn hình

Để cho biết một ứng dụng có thể không hỗ trợ chế độ nhiều cửa sổ hoặc đổi kích thước, các hoạt động sẽ sử dụng thuộc tính resizeableActivity=false. Sau đây là một số vấn đề phổ biến mà ứng dụng gặp phải khi các hoạt động được đổi kích thước:

  • Một hoạt động có thể có cấu hình khác với ứng dụng hoặc một thành phần không phải hình ảnh khác. Một lỗi thường gặp là đọc các chỉ số hiển thị từ ngữ cảnh ứng dụng. Các giá trị được trả về sẽ không được điều chỉnh theo các chỉ số về khu vực hiển thị mà một hoạt động hiển thị.
  • Một hoạt động có thể không xử lý được việc đổi kích thước và gặp sự cố, hiển thị giao diện người dùng bị méo hoặc mất trạng thái do khởi chạy lại mà không lưu trạng thái thực thể.
  • Ứng dụng có thể cố gắng sử dụng toạ độ đầu vào tuyệt đối (thay vì toạ độ tương ứng với vị trí cửa sổ), điều này có thể làm hỏng dữ liệu đầu vào ở chế độ nhiều cửa sổ.

Trong Android 7 (trở lên), bạn có thể đặt resizeableActivity=false của một ứng dụng để luôn chạy ở chế độ toàn màn hình. Trong trường hợp này, nền tảng sẽ ngăn các hoạt động không thể đổi kích thước chuyển sang chế độ chia đôi màn hình. Nếu người dùng cố gắng gọi một hoạt động không thể đổi kích thước từ trình chạy trong khi đang ở chế độ chia đôi màn hình, thì nền tảng sẽ thoát khỏi chế độ chia đôi màn hình và chạy hoạt động không thể đổi kích thước ở chế độ toàn màn hình.

Không được chạy các ứng dụng đặt thuộc tính này thành false một cách rõ ràng trong tệp kê khai ở chế độ nhiều cửa sổ, trừ khi áp dụng chế độ tương thích:

  • Cấu hình tương tự được áp dụng cho quy trình, trong đó chứa tất cả các hoạt động và thành phần không phải hoạt động.
  • Cấu hình được áp dụng đáp ứng các yêu cầu của CDD đối với màn hình tương thích với ứng dụng.

Trong Android 10, nền tảng này vẫn ngăn các hoạt động không thể đổi kích thước chuyển sang chế độ chia đôi màn hình, nhưng các hoạt động đó có thể được điều chỉnh theo tỷ lệ tạm thời nếu hoạt động đã khai báo hướng hoặc tỷ lệ khung hình cố định. Nếu không, hoạt động sẽ đổi kích thước để lấp đầy toàn bộ màn hình như trong Android 9 trở xuống.

Phương thức triển khai mặc định áp dụng chính sách sau:

Khi một hoạt động được khai báo là không tương thích với chế độ nhiều cửa sổ thông qua việc sử dụng thuộc tính android:resizeableActivity và khi hoạt động đó đáp ứng một trong các điều kiện được mô tả bên dưới, thì khi cấu hình màn hình đã áp dụng phải thay đổi, hoạt động và quy trình sẽ được lưu bằng cấu hình ban đầu và người dùng sẽ được cung cấp một khả năng để khởi chạy lại quy trình ứng dụng nhằm sử dụng cấu hình màn hình đã cập nhật.

  • Được cố định hướng thông qua việc áp dụng android:screenOrientation
  • Ứng dụng có tỷ lệ khung hình tối đa hoặc tối thiểu mặc định bằng cách nhắm đến cấp độ API hoặc khai báo tỷ lệ khung hình một cách rõ ràng

Hình này cho thấy một hoạt động không thể đổi kích thước với tỷ lệ khung hình đã khai báo. Khi gập thiết bị, cửa sổ sẽ được điều chỉnh theo tỷ lệ để vừa với khu vực trong khi vẫn duy trì tỷ lệ khung hình bằng cách sử dụng hộp thư phù hợp. Ngoài ra, người dùng sẽ được cung cấp tuỳ chọn khởi động lại hoạt động mỗi khi khu vực hiển thị của hoạt động thay đổi.

Khi mở thiết bị, cấu hình, kích thước và tỷ lệ khung hình của hoạt động sẽ không thay đổi, nhưng tuỳ chọn khởi động lại hoạt động sẽ xuất hiện.

Khi bạn không đặt resizeableActivity (hoặc đặt thành true), ứng dụng sẽ hỗ trợ đầy đủ việc đổi kích thước.

Triển khai

Hoạt động không thể đổi kích thước có hướng hoặc tỷ lệ khung hình cố định được gọi là chế độ tương thích với kích thước (SCM) trong mã. Điều kiện được xác định trong ActivityRecord#shouldUseSizeCompatMode(). Khi một hoạt động SCM được khởi chạy, cấu hình liên quan đến màn hình (chẳng hạn như kích thước hoặc mật độ) sẽ được cố định trong cấu hình ghi đè được yêu cầu, vì vậy, hoạt động không còn phụ thuộc vào cấu hình hiển thị hiện tại.

Nếu không thể lấp đầy toàn bộ màn hình, hoạt động SCM sẽ được căn chỉnh trên cùng và căn giữa theo chiều ngang. Các giới hạn hoạt động được tính toán bằng AppWindowToken#calculateCompatBoundsTransformation().

Khi một hoạt động SCM sử dụng cấu hình màn hình khác với vùng chứa của hoạt động đó (ví dụ: màn hình được đổi kích thước hoặc hoạt động được chuyển sang một màn hình khác), ActivityRecord#inSizeCompatMode() sẽ là true và SizeCompatModeActivityController (trong Giao diện người dùng hệ thống) sẽ nhận được lệnh gọi lại để hiển thị nút khởi động lại quy trình.

Kích thước hiển thị và tỷ lệ khung hình

Android 10 hỗ trợ các tỷ lệ khung hình mới, từ tỷ lệ cao của màn hình dài và mỏng đến tỷ lệ 1:1. Ứng dụng có thể xác định ApplicationInfo#maxAspectRatioApplicationInfo#minAspectRatio của màn hình mà ứng dụng có thể xử lý.

tỷ lệ ứng dụng trong Android 10

Hình 1. Ví dụ về tỷ lệ ứng dụng được hỗ trợ trong Android 10

Việc triển khai thiết bị có thể có màn hình phụ có kích thước và độ phân giải nhỏ hơn kích thước và độ phân giải mà Android 9 yêu cầu và thấp hơn (chiều rộng hoặc chiều cao tối thiểu là 2, 5 inch, tối thiểu là 320 DP cho smallestScreenWidth), nhưng chỉ những hoạt động chọn hỗ trợ các màn hình nhỏ này mới có thể được đặt ở đó.

Các ứng dụng có thể chọn sử dụng tính năng này bằng cách khai báo kích thước tối thiểu được hỗ trợ nhỏ hơn hoặc bằng kích thước màn hình mục tiêu. Sử dụng các thuộc tính bố cục hoạt động android:minHeightandroid:minWidth trong AndroidManifest để thực hiện việc này.

Chính sách về quảng cáo hiển thị

Android 10 tách biệt và di chuyển một số chính sách hiển thị nhất định từ cách triển khai WindowManagerPolicy mặc định trong PhoneWindowManager sang các lớp trên mỗi màn hình, chẳng hạn như:

  • Trạng thái hiển thị và độ xoay
  • Một số phím và tính năng theo dõi sự kiện chuyển động
  • Giao diện người dùng hệ thống và cửa sổ trang trí

Trong Android 9 (và các phiên bản thấp hơn), lớp PhoneWindowManager xử lý các chính sách hiển thị, trạng thái và chế độ cài đặt, chế độ xoay, theo dõi khung cửa sổ trang trí, v.v. Android 10 di chuyển hầu hết các tính năng này sang lớp DisplayPolicy, ngoại trừ tính năng theo dõi độ xoay đã được chuyển sang DisplayRotation.

Cài đặt cửa sổ hiển thị

Trong Android 10, chế độ cài đặt chế độ cửa sổ có thể định cấu hình cho mỗi màn hình đã được mở rộng để bao gồm:

  • Chế độ cửa sổ hiển thị mặc định
  • Giá trị tràn màn hình
  • Chế độ xoay và xoay của người dùng
  • Kích thước, mật độ và chế độ điều chỉnh theo tỷ lệ bắt buộc
  • Chế độ xoá nội dung (khi màn hình bị xoá)
  • Hỗ trợ các phần trang trí hệ thống và IME

Lớp DisplayWindowSettings chứa các chế độ cài đặt cho các tuỳ chọn này. Các giá trị này được lưu vào ổ đĩa trong phân vùng /data trong display_settings.xml mỗi khi một chế độ cài đặt thay đổi. Để biết thông tin chi tiết, hãy xem DisplayWindowSettings.AtomicFileStorageDisplayWindowSettings#writeSettings(). Nhà sản xuất thiết bị có thể cung cấp giá trị mặc định trong display_settings.xml cho cấu hình thiết bị của họ. Tuy nhiên, vì tệp được lưu trữ trong /data, nên có thể cần thêm logic để khôi phục tệp nếu bị xoá bằng tính năng xoá sạch.

Theo mặc định, Android 10 sử dụng DisplayInfo#uniqueId làm giá trị nhận dạng cho màn hình khi duy trì chế độ cài đặt. Bạn phải điền uniqueId cho tất cả màn hình. Ngoài ra, công cụ này cũng ổn định cho màn hình thực và màn hình mạng. Bạn cũng có thể sử dụng cổng của màn hình thực tế làm giá trị nhận dạng. Bạn có thể đặt giá trị này trong DisplayWindowSettings#mIdentifier. Trong mỗi lần ghi, tất cả các chế độ cài đặt sẽ được ghi để bạn có thể cập nhật an toàn khoá dùng cho mục hiển thị trong bộ nhớ. Để biết thông tin chi tiết, hãy xem phần Mã nhận dạng màn hình tĩnh.

Chế độ cài đặt được lưu giữ trong thư mục /data vì lý do trước đây. Ban đầu, các chế độ này được dùng để duy trì các chế độ cài đặt do người dùng đặt, chẳng hạn như chế độ xoay màn hình.

Giá trị nhận dạng màn hình tĩnh

Android 9 (và các phiên bản thấp hơn) không cung cấp giá trị nhận dạng ổn định cho màn hình trong khung. Khi một màn hình được thêm vào hệ thống, Display#mDisplayId hoặc DisplayInfo#displayId sẽ được tạo cho màn hình đó bằng cách tăng bộ đếm tĩnh. Nếu hệ thống thêm và xoá cùng một màn hình, thì một mã khác sẽ xuất hiện.

Nếu một thiết bị có nhiều màn hình từ khi khởi động, thì các màn hình đó có thể được gán các giá trị nhận dạng khác nhau, tuỳ thuộc vào thời gian. Mặc dù Android 9 (và các phiên bản trước) có DisplayInfo#uniqueId, nhưng phiên bản này không chứa đủ thông tin để phân biệt giữa các màn hình vì màn hình thực tế được xác định là local:0 hoặc local:1 để biểu thị màn hình tích hợp và màn hình ngoài.

Android 10 thay đổi DisplayInfo#uniqueId để thêm một giá trị nhận dạng ổn định và để phân biệt giữa màn hình cục bộ, màn hình mạng và màn hình ảo.

Loại màn hình Định dạng
Địa phương
local:<stable-id>
Mạng
network:<mac-address>
Ảo
virtual:<package-name-and-name>

Ngoài các bản cập nhật cho uniqueId, DisplayInfo.address còn chứa DisplayAddress, một giá trị nhận dạng màn hình ổn định trên các lần khởi động lại. Trong Android 10, DisplayAddress hỗ trợ màn hình thực và màn hình mạng. DisplayAddress.Physical chứa một mã hiển thị ổn định (tương tự như trong uniqueId) và có thể được tạo bằng DisplayAddress#fromPhysicalDisplayId().

Android 10 cũng cung cấp một phương thức thuận tiện để lấy thông tin cổng (Physical#getPort()). Bạn có thể sử dụng phương thức này trong khung để xác định tĩnh màn hình. Ví dụ: nó được dùng trong DisplayWindowSettings). DisplayAddress.Network chứa địa chỉ MAC và có thể được tạo bằng DisplayAddress#fromMacAddress().

Những nội dung bổ sung này cho phép nhà sản xuất thiết bị xác định màn hình trong chế độ thiết lập nhiều màn hình tĩnh và định cấu hình các chế độ cài đặt và tính năng hệ thống khác nhau bằng cách sử dụng giá trị nhận dạng màn hình tĩnh, chẳng hạn như cổng cho màn hình thực. Các phương thức này bị ẩn và chỉ dùng được trong system_server.

Với mã màn hình HWC (có thể mờ và không phải lúc nào cũng ổn định), phương thức này sẽ trả về số cổng 8 bit (dành riêng cho nền tảng) giúp xác định đầu nối vật lý cho đầu ra màn hình, cũng như blob EDID của màn hình. SurfaceFlinger trích xuất thông tin nhà sản xuất hoặc kiểu máy từ EDID để tạo mã màn hình 64 bit ổn định hiển thị cho khung. Nếu phương thức này không được hỗ trợ hoặc gặp lỗi, SurfaceFlinger sẽ quay lại chế độ MD cũ, trong đó DisplayInfo#address là rỗng và DisplayInfo#uniqueId được mã hoá cứng, như mô tả ở trên.

Để xác minh rằng tính năng này được hỗ trợ, hãy chạy:

$ 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"

Sử dụng nhiều màn hình

Trong Android 9 (và thấp hơn), SurfaceFlinger và DisplayManagerService giả định rằng có tối đa hai màn hình thực có mã nhận dạng được mã hoá cứng là 0 và 1.

Kể từ Android 10, SurfaceFlinger có thể tận dụng API Trình tổng hợp phần cứng (HWC) để tạo mã màn hình ổn định, cho phép ứng dụng này quản lý số lượng màn hình thực tế tuỳ ý. Để tìm hiểu thêm, hãy xem phần Giá trị nhận dạng hiển thị tĩnh.

Khung này có thể tra cứu mã thông báo IBinder cho màn hình thực thông qua SurfaceControl#getPhysicalDisplayToken sau khi lấy mã màn hình 64 bit từ SurfaceControl#getPhysicalDisplayIds hoặc từ sự kiện hotplug DisplayEventReceiver.

Trong Android 10 (và thấp hơn), màn hình trong chính là TYPE_INTERNAL và tất cả màn hình phụ đều được gắn cờ là TYPE_EXTERNAL bất kể loại kết nối. Do đó, các màn hình nội bộ bổ sung được coi là màn hình bên ngoài. Để khắc phục, mã dành riêng cho thiết bị có thể đưa ra giả định về DisplayAddress.Physical#getPort nếu biết HWC và logic phân bổ cổng có thể dự đoán được.

Giới hạn này đã bị xoá trong Android 11 (trở lên).

  • Trong Android 11, màn hình đầu tiên được báo cáo trong quá trình khởi động là màn hình chính. Loại kết nối (nội bộ so với bên ngoài) không liên quan. Tuy nhiên, vẫn đúng là không thể ngắt kết nối màn hình chính và theo đó, màn hình chính phải là màn hình nội bộ trong thực tế. Xin lưu ý rằng một số điện thoại có thể gập lại có nhiều màn hình nội bộ.
  • Màn hình phụ được phân loại chính xác là Display.TYPE_INTERNAL hoặc Display.TYPE_EXTERNAL (trước đây gọi là Display.TYPE_BUILT_INDisplay.TYPE_HDMI tương ứng) tuỳ thuộc vào loại kết nối.

Triển khai

Trong Android 9 trở xuống, màn hình được xác định bằng mã nhận dạng 32 bit, trong đó 0 là màn hình trong, 1 là màn hình ngoài, [2, INT32_MAX] là màn hình ảo HWC và -1 biểu thị màn hình không hợp lệ hoặc màn hình ảo không phải HWC.

Kể từ Android 10, màn hình được cấp mã nhận dạng ổn định và liên tục, cho phép SurfaceFlinger và DisplayManagerService theo dõi nhiều màn hình và nhận dạng màn hình đã thấy trước đó. Nếu HWC hỗ trợ IComposerClient.getDisplayIdentificationData và cung cấp dữ liệu nhận dạng màn hình, SurfaceFlinger sẽ phân tích cú pháp cấu trúc EDID và phân bổ mã màn hình 64 bit ổn định cho màn hình thực và màn hình ảo HWC. Các mã nhận dạng được biểu thị bằng một loại tuỳ chọn, trong đó giá trị rỗng biểu thị màn hình không hợp lệ hoặc màn hình ảo không phải HWC. Nếu không có tính năng hỗ trợ HWC, SurfaceFlinger sẽ quay lại hành vi cũ với tối đa 2 màn hình thực.

Tiêu điểm trên mỗi màn hình

Để hỗ trợ một số nguồn đầu vào nhắm đến từng màn hình cùng một lúc, bạn có thể định cấu hình Android 10 để hỗ trợ nhiều cửa sổ được lấy làm tâm điểm, tối đa là một cửa sổ trên mỗi màn hình. Điều này chỉ dành cho các loại thiết bị đặc biệt khi nhiều người dùng tương tác với cùng một thiết bị cùng một lúc và sử dụng các phương thức nhập hoặc thiết bị khác nhau, chẳng hạn như Android Automotive.

Bạn không nên bật tính năng này cho các thiết bị thông thường, bao gồm cả thiết bị nhiều màn hình hoặc thiết bị dùng cho trải nghiệm giống như máy tính. Điều này chủ yếu là do mối lo ngại về bảo mật có thể khiến người dùng thắc mắc cửa sổ nào có tiêu điểm nhập.

Hãy tưởng tượng người dùng nhập thông tin bảo mật vào trường nhập văn bản, có thể là đăng nhập vào ứng dụng ngân hàng hoặc nhập văn bản chứa thông tin nhạy cảm. Ứng dụng độc hại có thể tạo màn hình ảo ngoài màn hình để thực thi một hoạt động, cũng có trường nhập văn bản. Các hoạt động hợp pháp và độc hại đều có tiêu điểm và đều hiển thị chỉ báo đầu vào đang hoạt động (con trỏ nhấp nháy).

Tuy nhiên, vì dữ liệu đầu vào từ bàn phím (phần cứng hoặc phần mềm) chỉ được nhập vào hoạt động trên cùng (ứng dụng được khởi chạy gần đây nhất), nên bằng cách tạo màn hình ảo ẩn, ứng dụng độc hại có thể lấy dữ liệu đầu vào của người dùng, ngay cả khi sử dụng bàn phím phần mềm trên màn hình chính của thiết bị.

Sử dụng com.android.internal.R.bool.config_perDisplayFocusEnabled để đặt tiêu điểm cho mỗi màn hình.

Khả năng tương thích

Vấn đề: Trong Android 9 trở xuống, mỗi lần chỉ có tối đa một cửa sổ trong hệ thống có tiêu điểm.

Giải pháp: Trong trường hợp hiếm gặp khi hai cửa sổ từ cùng một quy trình được lấy tiêu điểm, hệ thống chỉ cung cấp tiêu điểm cho cửa sổ có thứ tự Z cao hơn. Quy định hạn chế này sẽ bị xoá đối với các ứng dụng nhắm đến Android 10. Tại thời điểm đó, các ứng dụng này dự kiến có thể hỗ trợ nhiều cửa sổ được lấy làm tâm điểm cùng lúc.

Triển khai

WindowManagerService#mPerDisplayFocusEnabled kiểm soát khả năng sử dụng tính năng này. Trong ActivityManager, ActivityDisplay#getFocusedStack() hiện được sử dụng thay vì tính năng theo dõi toàn cục trong một biến. ActivityDisplay#getFocusedStack() xác định tiêu điểm dựa trên thứ tự Z thay vì lưu giá trị vào bộ nhớ đệm. Điều này là để chỉ một nguồn, WindowManager, cần theo dõi thứ tự Z của các hoạt động.

ActivityStackSupervisor#getTopDisplayFocusedStack() sử dụng phương pháp tương tự cho những trường hợp cần xác định ngăn xếp được lấy tiêu điểm trên cùng trong hệ thống. Các ngăn xếp được duyệt từ trên xuống dưới, tìm kiếm ngăn xếp đủ điều kiện đầu tiên.

InputDispatcher hiện có thể có nhiều cửa sổ được lấy tiêu điểm (một cửa sổ cho mỗi màn hình). Nếu một sự kiện đầu vào dành riêng cho màn hình, thì sự kiện đó sẽ được gửi đến cửa sổ được lấy tiêu điểm trong màn hình tương ứng. Nếu không, thông báo sẽ được gửi đến cửa sổ được lấy tiêu điểm trong màn hình được lấy tiêu điểm, tức là màn hình mà người dùng tương tác gần đây nhất.

Hãy xem InputDispatcher::mFocusedWindowHandlesByDisplayInputDispatcher::setFocusedDisplay(). Các ứng dụng được lấy làm tâm điểm cũng được cập nhật riêng trong InputManagerService thông qua NativeInputManager::setFocusedApplication().

Trong WindowManager, các cửa sổ được lấy tiêu điểm cũng được theo dõi riêng biệt. Xem DisplayContent#mCurrentFocusDisplayContent#mFocusedApp cũng như các trường hợp sử dụng tương ứng. Các phương thức theo dõi và cập nhật tiêu điểm liên quan đã được chuyển từ WindowManagerService sang DisplayContent.