Merge "Remove vkDeviceWaitIdle call when destroying VulkanSurface" into udc-dev
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2e67225..4dea4a7 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -36,7 +36,6 @@
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.companion.utils.FeatureUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1227,11 +1226,6 @@
@Nullable
public IntentSender buildPermissionTransferUserConsentIntent(int associationId)
throws DeviceNotAssociatedException {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling"
- + " buildPermissionTransferUserConsentIntent, but this API is disabled by the"
- + " system.");
- }
try {
PendingIntent pendingIntent = mService.buildPermissionTransferUserConsentIntent(
mContext.getOpPackageName(),
@@ -1264,10 +1258,6 @@
@Deprecated
@UserHandleAware
public void startSystemDataTransfer(int associationId) throws DeviceNotAssociatedException {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this API"
- + " is disabled by the system.");
- }
try {
mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
associationId, null);
@@ -1300,10 +1290,6 @@
@NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CompanionException> result)
throws DeviceNotAssociatedException {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this API"
- + " is disabled by the system.");
- }
try {
mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
associationId, new SystemDataTransferCallbackProxy(executor, result));
diff --git a/core/java/android/companion/utils/FeatureUtils.java b/core/java/android/companion/utils/FeatureUtils.java
index 157eef8..a382e09 100644
--- a/core/java/android/companion/utils/FeatureUtils.java
+++ b/core/java/android/companion/utils/FeatureUtils.java
@@ -16,6 +16,7 @@
package android.companion.utils;
+import android.os.Binder;
import android.os.Build;
import android.provider.DeviceConfig;
@@ -31,8 +32,19 @@
private static final String PROPERTY_PERM_SYNC_ENABLED = "perm_sync_enabled";
public static boolean isPermSyncEnabled() {
- return Build.isDebuggable() || DeviceConfig.getBoolean(NAMESPACE_COMPANION,
- PROPERTY_PERM_SYNC_ENABLED, false);
+ // Permissions sync is always enabled in debuggable mode.
+ if (Build.isDebuggable()) {
+ return true;
+ }
+
+ // Clear app identity to read the device config for feature flag.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(NAMESPACE_COMPANION,
+ PROPERTY_PERM_SYNC_ENABLED, false);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
private FeatureUtils() {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c221d72..06635ee 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -481,7 +481,7 @@
* If binding from a top app and its target SDK version is at or above
* {@link android.os.Build.VERSION_CODES#R}, the app needs to
* explicitly use BIND_INCLUDE_CAPABILITIES flag to pass all capabilities to the service so the
- * other app can have while-use-use access such as location, camera, microphone from background.
+ * other app can have while-in-use access such as location, camera, microphone from background.
* If binding from a top app and its target SDK version is below
* {@link android.os.Build.VERSION_CODES#R}, BIND_INCLUDE_CAPABILITIES is implicit.
*/
@@ -678,7 +678,7 @@
* </p>
*
* <em>This flag is NOT compatible with {@link BindServiceFlags}. If you need to use
- * {@link BindServiceFlags}, you must use {@link #BIND_EXTERNAL_SERVICE_LONG} instead.
+ * {@link BindServiceFlags}, you must use {@link #BIND_EXTERNAL_SERVICE_LONG} instead.</em>
*/
public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index ac7f2ca..7e42f43 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -179,7 +179,7 @@
* @return unmodifiable map between physical camera ids and their capture result metadata
*
* @deprecated
- * <p>Please use {@link #getPhysicalCameraTotalResults() instead to get the
+ * <p>Please use {@link #getPhysicalCameraTotalResults()} instead to get the
* physical cameras' {@code TotalCaptureResult}.</p>
*/
public Map<String, CaptureResult> getPhysicalCameraResults() {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index fdcb87f..bbaaa47 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -259,6 +259,8 @@
}
private TraceDelegate mTracer;
+ private int mTraceLastState = 0;
+ private int mTraceLastState2 = 0;
/**
* Constructor
@@ -1241,7 +1243,6 @@
*/
private void recordTraceEvents(int code, HistoryTag tag) {
if (code == HistoryItem.EVENT_NONE) return;
- if (!mTracer.tracingEnabled()) return;
final int idx = code & HistoryItem.EVENT_TYPE_MASK;
final String prefix = (code & HistoryItem.EVENT_FLAG_START) != 0 ? "+" :
@@ -1270,8 +1271,6 @@
* Writes changes to a HistoryItem state bitmap to Atrace.
*/
private void recordTraceCounters(int oldval, int newval, BitDescription[] descriptions) {
- if (!mTracer.tracingEnabled()) return;
-
int diff = oldval ^ newval;
if (diff == 0) return;
@@ -1324,6 +1323,16 @@
}
private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ if (mTracer != null && mTracer.tracingEnabled()) {
+ recordTraceEvents(cur.eventCode, cur.eventTag);
+ recordTraceCounters(mTraceLastState, cur.states,
+ BatteryStats.HISTORY_STATE_DESCRIPTIONS);
+ recordTraceCounters(mTraceLastState2, cur.states2,
+ BatteryStats.HISTORY_STATE2_DESCRIPTIONS);
+ mTraceLastState = cur.states;
+ mTraceLastState2 = cur.states2;
+ }
+
if (!mHaveBatteryLevel || !mRecordingHistory) {
return;
}
@@ -1345,12 +1354,6 @@
+ Integer.toHexString(lastDiffStates2));
}
- recordTraceEvents(cur.eventCode, cur.eventTag);
- recordTraceCounters(mHistoryLastWritten.states,
- cur.states, BatteryStats.HISTORY_STATE_DESCRIPTIONS);
- recordTraceCounters(mHistoryLastWritten.states2,
- cur.states2, BatteryStats.HISTORY_STATE2_DESCRIPTIONS);
-
if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
&& timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
&& (diffStates2 & lastDiffStates2) == 0
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 1276dec..65c883a 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1256,7 +1256,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Приложенията се стартират."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Зареждането завършва."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Натиснахте бутона за включване/изключване – това обикновено изключва екрана.\n\nОпитайте да докоснете леко, докато настройвате отпечатъка си."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Изключете екрана за изход от настройката"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"За изход изключете екрана"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Изключване"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Напред с потвърждаването на отпечатъка?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Натиснахте бутона за включване/изключване – това обикновено изключва екрана.\n\nОпитайте да докоснете леко, за да потвърдите отпечатъка си."</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ee3ad19..fe686db 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -638,7 +638,7 @@
<string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"প্রতিবার আপনার আঙুলের অবস্থান সামান্য পরিবর্তন করুন"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
- <string name="fingerprint_error_not_match" msgid="4599441812893438961">"আঙ্গুলের ছাপ শনাক্ত করা যায়নি"</string>
+ <string name="fingerprint_error_not_match" msgid="4599441812893438961">"ফিঙ্গারপ্রিন্ট শনাক্ত করা যায়নি"</string>
<string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"ফিঙ্গারপ্রিন্ট শনাক্ত করা যায়নি"</string>
<string name="fingerprint_authenticated" msgid="2024862866860283100">"আঙ্গুলের ছাপ যাচাই করা হয়েছে"</string>
<string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"ফেস যাচাই করা হয়েছে"</string>
@@ -1365,7 +1365,7 @@
<string name="usb_midi_notification_title" msgid="7404506788950595557">"USB এর মাধ্যমে MIDI চালু করা হয়েছে"</string>
<string name="usb_uvc_notification_title" msgid="2030032862673400008">"ওয়েবক্যাম হিসেবে কানেক্ট করা আছে"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"ইউএসবি অ্যাক্সেসরি কানেক্ট করা হয়েছে"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"আরও বিকল্পের জন্য আলতো চাপুন৷"</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"আরও বিকল্পের জন্য ট্যাপ করুন।"</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"সংযুক্ত ডিভাইস চার্জ করা হচ্ছে। আরও বিকল্প দেখতে ট্যাপ করুন।"</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"অ্যানালগ অডিও অ্যাক্সেসরি শনাক্ত করা হয়েছে"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"সংযুক্ত ডিভাইসটি এই ফোনের সাথে ব্যবহার করা যাবে না। আরও জানতে ট্যাপ করুন।"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 5889f94..b581d49 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -703,7 +703,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"No es pot crear el model facial. Torna-ho a provar."</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"S\'han detectat ulleres fosques. La cara ha de veure\'s sencera."</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"S\'ha detectat una mascareta. La cara ha de veure\'s sencera."</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Tens la cara tapada. La cara ha de veure\'s sencera."</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"No es pot verificar la cara. Maquinari no disponible."</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 9bb14bc..1813fc4 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1263,7 +1263,7 @@
<string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"خاموش کردن صفحه"</string>
<string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ادامه"</string>
<string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> در حال اجرا"</string>
- <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"برای برگشت به بازی، ضربه بزنید"</string>
+ <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"برای برگشتن به بازی، ضربه بزنید"</string>
<string name="heavy_weight_switcher_title" msgid="3861984210040100886">"انتخاب بازی"</string>
<string name="heavy_weight_switcher_text" msgid="6814316627367160126">"برای عملکرد بهتر، هربار فقط یکی از این بازیها را میتوان باز کرد."</string>
<string name="old_app_action" msgid="725331621042848590">"به <xliff:g id="OLD_APP">%1$s</xliff:g> برگردید"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 25addec..c162672 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1257,7 +1257,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Lancement des applications…"</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Finalisation de la mise à jour."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Vous avez appuyé sur le bouton Marche/Arrêt, ce qui éteint généralement l\'écran.\n\nEssayez d\'appuyer doucement pendant la configuration de votre empreinte digitale."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Éteignez l\'écran pour achever la configuration."</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Éteignez l\'écran pour finir la config."</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Éteindre"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuer de valider votre empreinte ?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Vous avez appuyé sur le bouton Marche/Arrêt, ce qui éteint généralement l\'écran.\n\nPour valider votre empreinte digitale, appuyez plus doucement."</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index f20230f..da1b5dc 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1256,7 +1256,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Հավելվածները մեկնարկում են:"</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Բեռնումն ավարտվում է:"</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Դուք սեղմել եք սնուցման կոճակը։ Սովորաբար դրա արդյունքում էկրանն անջատվում է։\n\nՄատնահետքը ավելացնելու համար թեթևակի հպեք կոճակին։"</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Ավարտեք կարգավորումը՝ անջատելով էկրանը"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Անջատեք էկրանը և ավարտեք"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Անջատել"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Շարունակե՞լ մատնահետքի սկանավորումը"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Դուք սեղմել եք սնուցման կոճակը։ Սովորաբար դրա արդյունքում էկրանն անջատվում է։\n\nՄատնահետքը սկանավորելու համար թեթևակի հպեք կոճակին։"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index b4e0255..cc03284 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1258,7 +1258,7 @@
<string name="android_upgrading_complete" msgid="409800058018374746">"תהליך האתחול בשלבי סיום."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות במהלך ההגדרה של טביעת האצבע שלך."</string>
<string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"לסיום ההגדרה, יש לכבות את המסך"</string>
- <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"השבתה"</string>
+ <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"כיבוי"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"להמשיך לאמת את טביעת האצבע שלך?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות כדי לאמת את טביעת האצבע שלך."</string>
<string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"כיבוי המסך"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 1398b35..1f0cbfc 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -645,7 +645,7 @@
<string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"ಮುಖವನ್ನು ದೃಢೀಕರಿಸಲಾಗಿದೆ, ದೃಢೀಕರಣವನ್ನು ಒತ್ತಿ"</string>
<string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಹಾರ್ಡ್ವೇರ್ ಲಭ್ಯವಿಲ್ಲ."</string>
<string name="fingerprint_error_no_space" msgid="7285481581905967580">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ನು ಸೆಟಪ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="fingerprint_error_timeout" msgid="7361192266621252164">"ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಸೆಟಪ್ ಮಾಡುವ ಅವಧಿ ಮುಗಿದಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="fingerprint_error_timeout" msgid="7361192266621252164">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆಟಪ್ ಮಾಡುವ ಅವಧಿ ಮುಗಿದಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="fingerprint_error_canceled" msgid="540026881380070750">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ರದ್ದುಮಾಡಲಾಗಿದೆ."</string>
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"ಬಳಕೆದಾರರು ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ರದ್ದುಪಡಿಸಿದ್ದಾರೆ."</string>
<string name="fingerprint_error_lockout" msgid="6626753679019351368">"ಹಲವು ಬಾರಿ ಪ್ರಯತ್ನಿಸಿದ್ದೀರಿ. ಬದಲಾಗಿ ಸ್ಕ್ರೀನ್ಲಾಕ್ ಬಳಸಿ."</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 7df606b..e6979ae 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -625,11 +625,11 @@
<string name="biometric_error_generic" msgid="6784371929985434439">"인증 오류"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"화면 잠금 사용"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"계속하려면 화면 잠금용 사용자 인증 정보를 입력하세요"</string>
- <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"센서 위에 손가락을 좀 더 오래 올려놓으세요"</string>
+ <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"센서를 세게 누르세요"</string>
<string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"지문을 인식할 수 없습니다. 다시 시도해 주세요."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"지문 센서를 닦은 후 다시 시도해 보세요."</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"센서를 닦은 후 다시 시도해 보세요."</string>
- <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"센서 위에 손가락을 좀 더 오래 올려놓으세요"</string>
+ <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"센서를 세게 누르세요"</string>
<string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"손가락을 너무 느리게 움직였습니다. 다시 시도해 주세요."</string>
<string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"다른 지문으로 시도"</string>
<string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"너무 밝음"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index fe9c02e..c3b86e3 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1009,7 +1009,7 @@
<string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"പാറ്റേൺ മറന്നോ?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"അക്കൗണ്ട് അൺലോക്ക്"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"വളരെയധികം പാറ്റേൺ ശ്രമങ്ങൾ"</string>
- <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"അൺലോക്കുചെയ്യുന്നതിന്, നിങ്ങളുടെ Google അക്കൗണ്ട് ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യുക."</string>
+ <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"അൺലോക്കുചെയ്യുന്നതിന്, നിങ്ങളുടെ Google Account ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യുക."</string>
<string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"ഉപയോക്താവിന്റെ പേര് (ഇമെയിൽ)"</string>
<string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"പാസ്വേഡ്"</string>
<string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"സൈൻ ഇൻ ചെയ്യുക"</string>
@@ -1660,7 +1660,7 @@
<string name="kg_invalid_puk" msgid="4809502818518963344">"ശരിയായ PUK കോഡ് വീണ്ടും നൽകുക. ആവർത്തിച്ചുള്ള ശ്രമങ്ങൾ സിം ശാശ്വതമായി പ്രവർത്തനരഹിതമാക്കും."</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"പിൻ കോഡുകൾ പൊരുത്തപ്പെടുന്നില്ല"</string>
<string name="kg_login_too_many_attempts" msgid="699292728290654121">"വളരെയധികം പാറ്റേൺ ശ്രമങ്ങൾ"</string>
- <string name="kg_login_instructions" msgid="3619844310339066827">"അൺലോക്കുചെയ്യുന്നതിന്, നിങ്ങളുടെ Google അക്കൗണ്ട് ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യുക."</string>
+ <string name="kg_login_instructions" msgid="3619844310339066827">"അൺലോക്കുചെയ്യുന്നതിന്, നിങ്ങളുടെ Google Account ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യുക."</string>
<string name="kg_login_username_hint" msgid="1765453775467133251">"ഉപയോക്താവിന്റെ പേര് (ഇമെയിൽ)"</string>
<string name="kg_login_password_hint" msgid="3330530727273164402">"പാസ്വേഡ്"</string>
<string name="kg_login_submit_button" msgid="893611277617096870">"സൈൻ ഇൻ ചെയ്യുക"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 64150d0..80082c8 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -701,7 +701,7 @@
<!-- no translation found for face_acquired_mouth_covering_detected (8219428572168642593) -->
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"Kan gezichtsmodel niet maken. Probeer het opnieuw."</string>
- <string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Donkere bril waargenomen. Je gezicht moet geheel zichtbaar zijn."</string>
+ <string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"Donkere bril waargenomen. Je gezicht moet helemaal zichtbaar zijn."</string>
<string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Gezichtsbedekking waargenomen. Je hele gezicht moet zichtbaar zijn."</string>
<string-array name="face_acquired_vendor">
</string-array>
@@ -1256,7 +1256,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Apps starten."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Opstarten afronden."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Je hebt op de aan/uit-knop gedrukt. Zo zet je meestal het scherm uit.\n\nRaak de knop voorzichtig aan terwijl je je vingerafdruk instelt."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Zet het scherm uit om het instellen te beëindigen"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Zet scherm uit om instellen te beëindigen"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Uitzetten"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Doorgaan met verificatie van je vingerafdruk?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Je hebt op de aan/uit-knop gedrukt. Zo zet je meestal het scherm uit.\n\nRaak de knop voorzichtig aan om je vingerafdruk te verifiëren."</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 92f7c5b..81a7bb4 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1365,7 +1365,7 @@
<string name="usb_midi_notification_title" msgid="7404506788950595557">"USB ମାଧ୍ୟମରେ MIDIକୁ ଚାଲୁ କରାଗଲା"</string>
<string name="usb_uvc_notification_title" msgid="2030032862673400008">"ୱେବକେମ ଭାବେ ଡିଭାଇସ କନେକ୍ଟ କରାଯାଇଛି"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB ଆକ୍ସେସୋରୀ ଯୋଡ଼ାଗଲା"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"ଅଧିକ ବିକଳ୍ପ ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"ଅଧିକ ବିକଳ୍ପ ପାଇଁ ଟାପ କରନ୍ତୁ।"</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"ଯୋଡ଼ାଯାଇଥିବା ଡିଭାଇସ୍ ଚାର୍ଜ ହେଉଛି। ଅଧିକ ବିକଳ୍ପ ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"ଆନାଲଗ୍ ଅଡିଓ ଆକ୍ସେସରୀ ଚିହ୍ନଟ ହେଲା"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"ଏହି ଫୋନ୍ରେ କନେକ୍ଟ ଥିବା ଡିଭାଇସ୍ କମ୍ପାଟିବଲ୍ ନୁହେଁ। ଅଧିକ ଜାଣିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
@@ -1430,7 +1430,7 @@
<string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"କିଛି କାର୍ଯ୍ୟକ୍ଷମତା ଠିକ୍ ଭାବେ କାମ ନକରିପାରେ। ନୂଆ ଷ୍ଟୋରେଜ୍ ଭର୍ତ୍ତି କରନ୍ତୁ।"</string>
<string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"<xliff:g id="NAME">%s</xliff:g>କୁ ଇଜେକ୍ଟ କରାଯାଉଛି"</string>
<string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"କାଢ଼ନ୍ତୁ ନାହିଁ"</string>
- <string name="ext_media_init_action" msgid="2312974060585056709">"ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string>
+ <string name="ext_media_init_action" msgid="2312974060585056709">"ସେଟ ଅପ କରନ୍ତୁ"</string>
<string name="ext_media_unmount_action" msgid="966992232088442745">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="ext_media_browse_action" msgid="344865351947079139">"ଖୋଜନ୍ତୁ"</string>
<string name="ext_media_seamless_action" msgid="8837030226009268080">"ଆଉଟ୍ପୁଟ୍ ସ୍ୱିଚ୍ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index fac87ff..0a6f769 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -646,7 +646,7 @@
<string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Rosto autenticado, pressione \"Confirmar\""</string>
<string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Hardware de impressão digital não disponível."</string>
<string name="fingerprint_error_no_space" msgid="7285481581905967580">"Não foi possível configurar a impressão digital"</string>
- <string name="fingerprint_error_timeout" msgid="7361192266621252164">"A configuração da impressão digital expirou. Tente de novo."</string>
+ <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Tempo de configuração esgotado. Tente de novo."</string>
<string name="fingerprint_error_canceled" msgid="540026881380070750">"Operação de impressão digital cancelada."</string>
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Operação de impressão digital cancelada pelo usuário."</string>
<string name="fingerprint_error_lockout" msgid="6626753679019351368">"Excesso de tentativas. Use o bloqueio de tela."</string>
@@ -1366,7 +1366,7 @@
<string name="usb_midi_notification_title" msgid="7404506788950595557">"MIDI via USB ativado"</string>
<string name="usb_uvc_notification_title" msgid="2030032862673400008">"Dispositivo conectado como Webcam"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"Acessório USB conectado"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"Toque para ver mais opções."</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"Toque para conferir mais opções."</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"Carregando dispositivo conectado. Toque para ver mais opções."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Acessório de áudio analógico detectado"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"O dispositivo anexo não é compatível com esse smartphone. Toque para saber mais."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 86f2c9b..96242e1 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1257,8 +1257,8 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"A iniciar aplicações"</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"A concluir o arranque."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Premiu o botão ligar/desligar. Geralmente, esta ação desliga o ecrã.\n\nExperimente tocar levemente ao configurar a sua impressão digital."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Termine a configuração ao desligar ecrã"</string>
- <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Desativar"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Para terminar, desligue o ecrã"</string>
+ <string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Desligar"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuar a validar a impressão digital?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Premiu o botão ligar/desligar. Geralmente, esta ação desliga o ecrã.\n\nExperimente tocar levemente para validar a sua impressão digital."</string>
<string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Desligar ecrã"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index fac87ff..0a6f769 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -646,7 +646,7 @@
<string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Rosto autenticado, pressione \"Confirmar\""</string>
<string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Hardware de impressão digital não disponível."</string>
<string name="fingerprint_error_no_space" msgid="7285481581905967580">"Não foi possível configurar a impressão digital"</string>
- <string name="fingerprint_error_timeout" msgid="7361192266621252164">"A configuração da impressão digital expirou. Tente de novo."</string>
+ <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Tempo de configuração esgotado. Tente de novo."</string>
<string name="fingerprint_error_canceled" msgid="540026881380070750">"Operação de impressão digital cancelada."</string>
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Operação de impressão digital cancelada pelo usuário."</string>
<string name="fingerprint_error_lockout" msgid="6626753679019351368">"Excesso de tentativas. Use o bloqueio de tela."</string>
@@ -1366,7 +1366,7 @@
<string name="usb_midi_notification_title" msgid="7404506788950595557">"MIDI via USB ativado"</string>
<string name="usb_uvc_notification_title" msgid="2030032862673400008">"Dispositivo conectado como Webcam"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"Acessório USB conectado"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"Toque para ver mais opções."</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"Toque para conferir mais opções."</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"Carregando dispositivo conectado. Toque para ver mais opções."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Acessório de áudio analógico detectado"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"O dispositivo anexo não é compatível com esse smartphone. Toque para saber mais."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 5a5132c..5edccb4 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1257,7 +1257,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Se pornesc aplicațiile."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Se finalizează pornirea."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Ai apăsat butonul de pornire. De obicei, astfel se dezactivează ecranul.\n\nAtinge ușor când îți configurezi amprenta."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Ca să termini configurarea, dezactivează ecranul"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"Termină configurarea dezactivând ecranul"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Dezactivează"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continui cu verificarea amprentei?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"Ai apăsat butonul de pornire. De obicei, astfel se dezactivează ecranul.\n\nAtinge ușor pentru verificarea amprentei."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 1d9b729..e48c487 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1372,7 +1372,7 @@
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Обнаружено аналоговое аудиоустройство"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Подсоединенное устройство несовместимо с этим телефоном. Нажмите, чтобы узнать подробности."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"Отладка по USB активна"</string>
- <string name="adb_active_notification_message" msgid="5617264033476778211">"Нажмите, чтобы отключить"</string>
+ <string name="adb_active_notification_message" msgid="5617264033476778211">"Нажмите, чтобы отключить."</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Нажмите, чтобы запретить"</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Отладка по Wi-Fi включена"</string>
<string name="adbwifi_active_notification_message" msgid="930987922852867972">"Нажмите, чтобы отключить отладку по Wi-Fi."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index f0c6123..c40b205 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -645,7 +645,7 @@
<string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Ansiktet har autentiserats. Tryck på Bekräfta"</string>
<string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Det finns ingen maskinvara för fingeravtryck."</string>
<string name="fingerprint_error_no_space" msgid="7285481581905967580">"Det gick inte att konfigurera fingeravtryck"</string>
- <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Fingeravtryckskonfigurering nådde tidsgränsen. Försök igen."</string>
+ <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Tiden för fingeravtrycksinställning gick ut. Försök igen."</string>
<string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingeravtrycksåtgärden avbröts."</string>
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingeravtrycksåtgärden avbröts av användaren."</string>
<string name="fingerprint_error_lockout" msgid="6626753679019351368">"För många försök. Använd låsskärmen i stället."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 7d28aec..8946886 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1256,7 +1256,7 @@
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"యాప్లను ప్రారంభిస్తోంది."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"బూట్ను ముగిస్తోంది."</string>
<string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"మీరు పవర్ బటన్ను నొక్కారు — ఇది సాధారణంగా స్క్రీన్ను ఆఫ్ చేస్తుంది.\n\nమీ వేలిముద్రను సెటప్ చేస్తున్నప్పుడు తేలికగా ట్యాప్ చేయడానికి ట్రై చేయండి."</string>
- <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"సెటప్ ముగించడానికి, స్క్రీన్ను ఆఫ్ చేయండి"</string>
+ <string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"సెటప్ ముగించడానికి, స్క్రీన్ ఆఫ్ చేయండి"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"ఆఫ్ చేయండి"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"మీ వేలిముద్ర వెరిఫైను కొనసాగించాలా?"</string>
<string name="fp_power_button_bp_message" msgid="2983163038168903393">"మీరు పవర్ బటన్ను నొక్కారు — ఇది సాధారణంగా స్క్రీన్ను ఆఫ్ చేస్తుంది.\n\nమీ వేలిముద్రను వెరిఫై చేయడానికి తేలికగా ట్యాప్ చేయడం ట్రై చేయండి."</string>
@@ -1365,7 +1365,7 @@
<string name="usb_midi_notification_title" msgid="7404506788950595557">"USB ద్వారా MIDI ఆన్ చేయబడింది"</string>
<string name="usb_uvc_notification_title" msgid="2030032862673400008">"పరికరం వెబ్క్యామ్గా కనెక్ట్ చేయబడింది"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB ఉపకరణం కనెక్ట్ చేయబడింది"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"మరిన్ని ఆప్షన్ల కోసం నొక్కండి."</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"మరిన్ని ఆప్షన్ల కోసం ట్యాప్ చేయండి."</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"కనెక్ట్ చేయబడిన పరికరాన్ని ఛార్జ్ చేస్తోంది. మరిన్ని ఎంపికల కోసం నొక్కండి."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"అనలాగ్ ఆడియో ఉపకరణం కనుగొనబడింది"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"జోడించిన పరికరం ఈ ఫోన్కు అనుకూలంగా లేదు. మరింత తెలుసుకోవడానికి నొక్కండి."</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index eb9b1f3..107a66a 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -702,7 +702,7 @@
<skip />
<string name="face_acquired_recalibrate_alt" msgid="5702674220280332115">"สร้างรูปแบบใบหน้าไม่ได้ โปรดลองอีกครั้ง"</string>
<string name="face_acquired_dark_glasses_detected_alt" msgid="4052123776406041972">"ตรวจพบแว่นตาดำ ต้องมองเห็นใบหน้าของคุณทั้งหมด"</string>
- <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"ตรวจพบหน้ากากอนามัย ต้องมองเห็นใบหน้าของคุณทั้งหมด"</string>
+ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"ตรวจพบหน้ากาก ต้องมองเห็นใบหน้าของคุณทั้งหมด"</string>
<string-array name="face_acquired_vendor">
</string-array>
<string name="face_error_hw_not_available" msgid="5085202213036026288">"ยืนยันใบหน้าไม่ได้ ฮาร์ดแวร์ไม่พร้อมใช้งาน"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index f9f9c3a..33b66d3 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1950,7 +1950,7 @@
<string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> uygulaması şu anda kullanılamıyor. Uygulamanın kullanım durumu <xliff:g id="APP_NAME_1">%2$s</xliff:g> tarafından yönetiliyor."</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"Daha fazla bilgi"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Uygulamanın duraklatmasını kaldır"</string>
- <string name="work_mode_off_title" msgid="6367463960165135829">"İş uygulamaları devam ettirilsin mi?"</string>
+ <string name="work_mode_off_title" msgid="6367463960165135829">"İş uygulamaları açılsın mı?"</string>
<string name="work_mode_turn_on" msgid="5316648862401307800">"Devam ettir"</string>
<string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Acil durum"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"Uygulama kullanılamıyor"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 13a2ea2..edff47a 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -59,7 +59,7 @@
<string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"خروج از «حالت یکدستی»"</string>
<string name="bubbles_settings_button_description" msgid="1301286017420516912">"تنظیمات برای حبابکهای <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="bubble_overflow_button_content_description" msgid="8160974472718594382">"سرریز"</string>
- <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشت به پشته"</string>
+ <string name="bubble_accessibility_action_add_back" msgid="1830101076853540953">"افزودن برگشتن به پشته"</string>
<string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
<string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> مورد بیشتر"</string>
<string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"انتقال به بالا سمت راست"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index cf38990..b14c3c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -447,7 +447,7 @@
}
/**
- * Callback when launcher finishes swipe-pip-to-home operation.
+ * Callback when launcher finishes preparation of swipe-pip-to-home operation.
* Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e04e9f7..b8407c46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -953,7 +953,8 @@
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
// reparent the PIP activity to a new PIP task (in case there are other activities
- // in the original Task), so we should also reparent the overlay to the PIP task.
+ // in the original Task, in other words multi-activity apps), so we should also reparent
+ // the overlay to the final PIP task.
startTransaction.reparent(swipePipToHomeOverlay, leash)
.setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
mPipOrganizer.mSwipePipToHomeOverlay = null;
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index aeaa171..4c9a23d 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -96,17 +96,33 @@
sk_sp<SkColorSpace> decodeColorSpace =
mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
SkBitmap bm;
- HeapAllocator heapAlloc;
- if (!mGainmapBRD->decodeRegion(&bm, &heapAlloc, desiredSubset, sampleSize, decodeColorType,
- requireUnpremul, decodeColorSpace)) {
- ALOGE("Error decoding Gainmap region");
- return false;
- }
- sk_sp<Bitmap> nativeBitmap(heapAlloc.getStorageObjAndReset());
+ // Because we must match the dimensions of the base bitmap, we always use a
+ // recycling allocator even though we are allocating a new bitmap. This is to ensure
+ // that if a recycled bitmap was used for the base image that we match the relative
+ // dimensions of that base image. The behavior of BRD here is:
+ // if inBitmap is specified -> output dimensions are always equal to the inBitmap's
+ // if no bitmap is reused -> output dimensions are the intersect of the desiredSubset &
+ // the image bounds
+ // The handling of the above conditionals are baked into the desiredSubset, so we
+ // simply need to ensure that the resulting bitmap is the exact same width/height as
+ // the specified desiredSubset regardless of the intersection to the image bounds.
+ // kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying
+ // allocation type. RecyclingClippingPixelAllocator will populate this with the
+ // actual alpha type in either allocPixelRef() or copyIfNecessary()
+ sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(
+ SkImageInfo::Make(desiredSubset.width(), desiredSubset.height(), decodeColorType,
+ kPremul_SkAlphaType, decodeColorSpace));
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap for Gainmap");
return false;
}
+ RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false);
+ if (!mGainmapBRD->decodeRegion(&bm, &allocator, desiredSubset, sampleSize, decodeColorType,
+ requireUnpremul, decodeColorSpace)) {
+ ALOGE("Error decoding Gainmap region");
+ return false;
+ }
+ allocator.copyIfNecessary();
auto gainmap = sp<uirenderer::Gainmap>::make();
if (!gainmap) {
ALOGE("OOM allocating Gainmap");
@@ -238,13 +254,11 @@
// Recycle a bitmap if possible.
android::Bitmap* recycledBitmap = nullptr;
- size_t recycledBytes = 0;
if (javaBitmap) {
recycledBitmap = &bitmap::toBitmap(inBitmapHandle);
if (recycledBitmap->isImmutable()) {
ALOGW("Warning: Reusing an immutable bitmap as an image decoder target.");
}
- recycledBytes = recycledBitmap->getAllocationByteCount();
}
auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
@@ -263,7 +277,7 @@
// Set up the pixel allocator
skia::BRDAllocator* allocator = nullptr;
- RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap, recycledBytes);
+ RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap);
HeapAllocator heapAlloc;
if (javaBitmap) {
allocator = &recycleAlloc;
@@ -277,7 +291,7 @@
decodeColorType, colorSpace);
// Decode the region.
- SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
+ const SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
SkBitmap bitmap;
if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize,
decodeColorType, requireUnpremul, decodeColorSpace)) {
@@ -307,10 +321,27 @@
GraphicsJNI::getColorSpace(env, decodeColorSpace.get(), decodeColorType));
}
+ if (javaBitmap) {
+ recycleAlloc.copyIfNecessary();
+ }
+
sp<uirenderer::Gainmap> gainmap;
bool hasGainmap = brd->hasGainmap();
if (hasGainmap) {
- SkIRect gainmapSubset = brd->calculateGainmapRegion(subset);
+ SkIRect adjustedSubset{};
+ if (javaBitmap) {
+ // Clamp to the width/height of the recycled bitmap in case the reused bitmap
+ // was too small for the specified rectangle, in which case we need to clip
+ adjustedSubset = SkIRect::MakeXYWH(inputX, inputY,
+ std::min(subset.width(), recycledBitmap->width()),
+ std::min(subset.height(), recycledBitmap->height()));
+ } else {
+ // We are not recycling, so use the decoded width/height for calculating the gainmap
+ // subset instead to ensure the gainmap region proportionally matches
+ adjustedSubset = SkIRect::MakeXYWH(std::max(0, inputX), std::max(0, inputY),
+ bitmap.width(), bitmap.height());
+ }
+ SkIRect gainmapSubset = brd->calculateGainmapRegion(adjustedSubset);
if (!brd->decodeGainmapRegion(&gainmap, gainmapSubset, sampleSize, requireUnpremul)) {
// If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
hasGainmap = false;
@@ -319,7 +350,6 @@
// If we may have reused a bitmap, we need to indicate that the pixels have changed.
if (javaBitmap) {
- recycleAlloc.copyIfNecessary();
if (hasGainmap) {
recycledBitmap->setGainmap(std::move(gainmap));
}
@@ -331,6 +361,7 @@
if (!requireUnpremul) {
bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
}
+
if (isHardware) {
sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap);
if (hasGainmap) {
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 914266d..78b4f7b 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -620,13 +620,13 @@
////////////////////////////////////////////////////////////////////////////////
-RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(
- android::Bitmap* recycledBitmap, size_t recycledBytes)
- : mRecycledBitmap(recycledBitmap)
- , mRecycledBytes(recycledBytes)
- , mSkiaBitmap(nullptr)
- , mNeedsCopy(false)
-{}
+RecyclingClippingPixelAllocator::RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
+ bool mustMatchColorType)
+ : mRecycledBitmap(recycledBitmap)
+ , mRecycledBytes(recycledBitmap ? recycledBitmap->getAllocationByteCount() : 0)
+ , mSkiaBitmap(nullptr)
+ , mNeedsCopy(false)
+ , mMustMatchColorType(mustMatchColorType) {}
RecyclingClippingPixelAllocator::~RecyclingClippingPixelAllocator() {}
@@ -637,10 +637,16 @@
LOG_ALWAYS_FATAL_IF(!bitmap);
mSkiaBitmap = bitmap;
- // This behaves differently than the RecyclingPixelAllocator. For backwards
- // compatibility, the original color type of the recycled bitmap must be maintained.
- if (mRecycledBitmap->info().colorType() != bitmap->colorType()) {
- return false;
+ if (mMustMatchColorType) {
+ // This behaves differently than the RecyclingPixelAllocator. For backwards
+ // compatibility, the original color type of the recycled bitmap must be maintained.
+ if (mRecycledBitmap->info().colorType() != bitmap->colorType()) {
+ ALOGW("recycled color type %d != bitmap color type %d",
+ mRecycledBitmap->info().colorType(), bitmap->colorType());
+ return false;
+ }
+ } else {
+ mRecycledBitmap->reconfigure(mRecycledBitmap->info().makeColorType(bitmap->colorType()));
}
// The Skia bitmap specifies the width and height needed by the decoder.
@@ -695,7 +701,7 @@
void RecyclingClippingPixelAllocator::copyIfNecessary() {
if (mNeedsCopy) {
mRecycledBitmap->ref();
- SkPixelRef* recycledPixels = mRecycledBitmap;
+ android::Bitmap* recycledPixels = mRecycledBitmap;
void* dst = recycledPixels->pixels();
const size_t dstRowBytes = mRecycledBitmap->rowBytes();
const size_t bytesToCopy = std::min(mRecycledBitmap->info().minRowBytes(),
@@ -708,6 +714,8 @@
dst = reinterpret_cast<void*>(
reinterpret_cast<uint8_t*>(dst) + dstRowBytes);
}
+ recycledPixels->setAlphaType(mSkiaBitmap->alphaType());
+ recycledPixels->setColorSpace(mSkiaBitmap->refColorSpace());
recycledPixels->notifyPixelsChanged();
recycledPixels->unref();
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 24f9e82..23ab5dd 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -222,9 +222,8 @@
*/
class RecyclingClippingPixelAllocator : public android::skia::BRDAllocator {
public:
-
RecyclingClippingPixelAllocator(android::Bitmap* recycledBitmap,
- size_t recycledBytes);
+ bool mustMatchColorType = true);
~RecyclingClippingPixelAllocator();
@@ -252,6 +251,7 @@
const size_t mRecycledBytes;
SkBitmap* mSkiaBitmap;
bool mNeedsCopy;
+ const bool mMustMatchColorType;
};
class AshmemPixelAllocator : public SkBitmap::Allocator {
diff --git a/packages/CarrierDefaultApp/res/values-ur/strings.xml b/packages/CarrierDefaultApp/res/values-ur/strings.xml
index 0c7cdc5..d6225c2 100644
--- a/packages/CarrierDefaultApp/res/values-ur/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ur/strings.xml
@@ -16,9 +16,7 @@
<string name="ssl_error_continue" msgid="1138548463994095584">"براؤزر کے ذریعے بہرحال جاری رکھیں"</string>
<string name="performance_boost_notification_channel" msgid="3475440855635538592">"پرفارمینس بوسٹ"</string>
<string name="performance_boost_notification_title" msgid="3126203390685781861">"آپ کے کیریئر سے 5G کے اختیارات"</string>
- <!-- String.format failed for translation -->
- <!-- no translation found for performance_boost_notification_detail (216569851036236346) -->
- <skip />
+ <string name="performance_boost_notification_detail" msgid="216569851036236346">"اپنی ایپ کے تجربے کے اختیارات دیکھنے کے لیے %s کی ویب سائٹ ملاحظہ کریں"</string>
<string name="performance_boost_notification_button_not_now" msgid="6459755324243683785">"ابھی نہیں"</string>
<string name="performance_boost_notification_button_manage" msgid="4976836444046497973">"نظم کریں"</string>
<string name="slice_purchase_app_label" msgid="7170191659233241166">"پرفارمینس بوسٹ خریدیں۔"</string>
diff --git a/packages/EasterEgg/Android.bp b/packages/EasterEgg/Android.bp
index e88410c..8699f59 100644
--- a/packages/EasterEgg/Android.bp
+++ b/packages/EasterEgg/Android.bp
@@ -26,7 +26,10 @@
android_app {
// the build system in pi-dev can't quite handle R.java in kt
// so we will have a mix of java and kotlin files
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
@@ -36,17 +39,34 @@
certificate: "platform",
optimize: {
+ enabled: true,
+ optimize: true,
+ shrink: true,
+ shrink_resources: true,
+ proguard_compatibility: false,
proguard_flags_files: ["proguard.flags"],
},
- static_libs: [
- "androidx.core_core",
- "androidx.recyclerview_recyclerview",
+ static_libs: [
+ "androidx.core_core",
"androidx.annotation_annotation",
- "kotlinx-coroutines-android",
- "kotlinx-coroutines-core",
- //"kotlinx-coroutines-reactive",
- ],
+ "androidx.recyclerview_recyclerview",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
+
+ "androidx.core_core-ktx",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.activity_activity-compose",
+ "androidx.compose.ui_ui",
+ "androidx.compose.ui_ui-util",
+ "androidx.compose.ui_ui-tooling-preview",
+ "androidx.compose.material_material",
+ "androidx.window_window",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.activity_activity-compose",
+ "androidx.compose.ui_ui",
+ ],
manifest: "AndroidManifest.xml",
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index cc7bb4a..d1db237 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -1,4 +1,19 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.egg"
android:versionCode="12"
@@ -18,8 +33,27 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
- android:icon="@drawable/icon"
+ android:icon="@drawable/android14_patch_adaptive"
android:label="@string/app_name">
+
+ <!-- Android U easter egg -->
+
+ <activity
+ android:name=".landroid.MainActivity"
+ android:exported="true"
+ android:label="@string/u_egg_name"
+ android:icon="@drawable/android14_patch_adaptive"
+ android:configChanges="orientation|screenLayout|screenSize|density"
+ android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="com.android.internal.category.PLATLOGO" />
+ </intent-filter>
+ </activity>
+
+
+ <!-- Android Q easter egg -->
<activity
android:name=".quares.QuaresActivity"
android:exported="true"
@@ -69,7 +103,7 @@
android:exported="true"
android:showOnLockScreen="true"
android:theme="@android:style/Theme.Material.Light.Dialog.NoActionBar" />
- <!-- Used to enable easter egg -->
+ <!-- Used to enable easter egg components for earlier easter eggs. -->
<activity
android:name=".ComponentActivationActivity"
android:excludeFromRecents="true"
@@ -79,7 +113,6 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
- <category android:name="com.android.internal.category.PLATLOGO" />
</intent-filter>
</activity>
diff --git a/packages/EasterEgg/res/drawable/android14_patch_adaptive.xml b/packages/EasterEgg/res/drawable/android14_patch_adaptive.xml
new file mode 100644
index 0000000..423e351
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android14_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/android14_patch_adaptive_background"/>
+ <foreground android:drawable="@drawable/android14_patch_adaptive_foreground"/>
+ <monochrome android:drawable="@drawable/android14_patch_monochrome"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/drawable/android14_patch_adaptive_background.xml b/packages/EasterEgg/res/drawable/android14_patch_adaptive_background.xml
new file mode 100644
index 0000000..c31aa7b
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android14_patch_adaptive_background.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M0,0 L108,0 L108,108 L0,108 z"
+ android:fillColor="#FF073042"/>
+ <path
+ android:pathData="M44.51,43.32L44.86,42.27C47.04,54.48 52.81,86.71 52.81,50.14C52.81,49.99 52.92,49.86 53.06,49.86H55.04C55.18,49.86 55.3,49.98 55.3,50.14C55.27,114.18 44.51,43.32 44.51,43.32Z"
+ android:fillColor="#3DDC84"/>
+ <path
+ android:name="planetary head"
+ android:pathData="M38.81,42.23L33.63,51.21C33.33,51.72 33.51,52.38 34.02,52.68C34.54,52.98 35.2,52.8 35.49,52.28L40.74,43.2C49.22,47 58.92,47 67.4,43.2L72.65,52.28C72.96,52.79 73.62,52.96 74.13,52.65C74.62,52.35 74.79,51.71 74.51,51.21L69.33,42.23C78.23,37.39 84.32,28.38 85.21,17.74H22.93C23.82,28.38 29.91,37.39 38.81,42.23Z"
+ android:fillColor="#ffffff"/>
+ <!-- yes it's an easter egg in a vector drawable -->
+ <path
+ android:name="planetary body"
+ android:pathData="M22.9,0 L85.1,0 L85.1,15.5 L22.9,15.5 z"
+ android:fillColor="#ffffff" />
+ <path
+ android:pathData="M54.96,43.32H53.1C52.92,43.32 52.77,43.47 52.77,43.65V48.04C52.77,48.22 52.92,48.37 53.1,48.37H54.96C55.15,48.37 55.3,48.22 55.3,48.04V43.65C55.3,43.47 55.15,43.32 54.96,43.32Z"
+ android:fillColor="#3DDC84"/>
+ <path
+ android:pathData="M54.99,40.61H53.08C52.91,40.61 52.77,40.75 52.77,40.92V41.56C52.77,41.73 52.91,41.87 53.08,41.87H54.99C55.16,41.87 55.3,41.73 55.3,41.56V40.92C55.3,40.75 55.16,40.61 54.99,40.61Z"
+ android:fillColor="#3DDC84"/>
+ <path
+ android:pathData="M41.49,47.88H40.86V48.51H41.49V47.88Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M44.13,57.08H43.5V57.71H44.13V57.08Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M72.29,66.76H71.66V67.39H72.29V66.76Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M59.31,53.41H58.68V54.04H59.31V53.41Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M64.47,48.19H63.84V48.83H64.47V48.19Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M60.58,59.09H59.95V59.72H60.58V59.09Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M66.95,56.7H65.69V57.97H66.95V56.7Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M44.13,60.71H43.5V61.34H44.13V60.71Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M49.66,51.33H48.4V52.6H49.66V51.33Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M57.78,63.83H56.52V65.09H57.78V63.83Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M61.1,68.57H59.83V69.83H61.1V68.57Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M40.43,53.73H39.16V54.99H40.43V53.73Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M74.47,44H73.21V45.26H74.47V44Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M36.8,64.58H35.54V65.84H36.8V64.58Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android14_patch_adaptive_foreground.xml b/packages/EasterEgg/res/drawable/android14_patch_adaptive_foreground.xml
new file mode 100644
index 0000000..391d515
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android14_patch_adaptive_foreground.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M54.03,33.03C52.99,33.03 52.14,33.86 52.14,34.87V37.14C52.14,37.34 52.3,37.5 52.5,37.5C52.69,37.5 52.85,37.34 52.85,37.14V36.53C52.85,36.14 53.17,35.82 53.56,35.82H54.51C54.9,35.82 55.22,36.14 55.22,36.53V37.14C55.22,37.34 55.38,37.5 55.57,37.5C55.77,37.5 55.93,37.34 55.93,37.14V34.87C55.93,33.86 55.08,33.03 54.03,33.03H54.03Z"
+ android:fillColor="#3DDC84"/>
+ <path
+ android:pathData="M108,0H0V108H108V0ZM54,80.67C68.73,80.67 80.67,68.73 80.67,54C80.67,39.27 68.73,27.33 54,27.33C39.27,27.33 27.33,39.27 27.33,54C27.33,68.73 39.27,80.67 54,80.67Z"
+ android:fillColor="#F86734"
+ android:fillType="evenOdd"/>
+ <group>
+ <!-- the text doesn't look great everywhere but you can remove the clip to try it out. -->
+ <clip-path />
+ <path
+ android:pathData="M28.58,32.18L29.18,31.5L33.82,33.02L33.12,33.81L32.15,33.48L30.92,34.87L31.37,35.8L30.68,36.58L28.58,32.18L28.58,32.18ZM31.25,33.18L29.87,32.71L30.51,34.02L31.25,33.18V33.18Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M38,29.76L34.61,28.79L36.23,31.04L35.42,31.62L32.8,27.99L33.5,27.48L36.88,28.45L35.26,26.21L36.08,25.62L38.7,29.25L38,29.76Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M39.23,23.87L40.63,23.27C41.79,22.77 43.13,23.28 43.62,24.43C44.11,25.57 43.56,26.89 42.4,27.39L40.99,27.99L39.23,23.87ZM42.03,26.54C42.73,26.24 42.96,25.49 42.68,24.83C42.4,24.17 41.69,23.82 41,24.11L40.51,24.32L41.55,26.75L42.03,26.54Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M45.91,21.43L47.64,21.09C48.47,20.93 49.12,21.41 49.27,22.15C49.38,22.72 49.15,23.14 48.63,23.45L50.57,25.08L49.39,25.31L47.57,23.79L47.41,23.82L47.76,25.63L46.78,25.83L45.91,21.43H45.91ZM47.87,22.86C48.16,22.8 48.34,22.59 48.29,22.34C48.24,22.07 48,21.96 47.71,22.02L47.07,22.14L47.24,22.98L47.87,22.86Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M52.17,22.69C52.19,21.41 53.24,20.39 54.52,20.41C55.8,20.43 56.82,21.49 56.8,22.76C56.78,24.04 55.72,25.06 54.45,25.04C53.17,25.02 52.15,23.96 52.17,22.69ZM55.79,22.75C55.8,22.02 55.23,21.39 54.51,21.38C53.78,21.37 53.19,21.98 53.18,22.7C53.17,23.43 53.73,24.06 54.47,24.07C55.19,24.08 55.78,23.47 55.79,22.75H55.79Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M60,21.01L60.98,21.2L60.12,25.6L59.14,25.41L60,21.01Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M64.3,22.03L65.73,22.58C66.91,23.03 67.51,24.32 67.07,25.49C66.62,26.65 65.31,27.22 64.13,26.77L62.71,26.22L64.3,22.03L64.3,22.03ZM64.46,25.9C65.17,26.17 65.86,25.8 66.12,25.12C66.37,24.45 66.11,23.71 65.4,23.44L64.91,23.25L63.97,25.72L64.46,25.9Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M73.59,27.94L72.94,27.44L73.51,26.69L74.92,27.77L72.2,31.34L71.45,30.76L73.59,27.94Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M76.18,33.75L74.69,32.14L75.25,31.62L78.81,31.42L79.4,32.05L77.47,33.85L77.86,34.27L77.22,34.86L76.83,34.44L76.12,35.11L75.47,34.41L76.18,33.75ZM77.72,32.31L76.12,32.4L76.82,33.15L77.72,32.31Z"
+ android:fillColor="#ffffff"/>
+ </group>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android14_patch_monochrome.xml b/packages/EasterEgg/res/drawable/android14_patch_monochrome.xml
new file mode 100644
index 0000000..beef85c
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android14_patch_monochrome.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <group>
+ <clip-path
+ android:pathData="M0,0h108v108h-108z"/>
+ <group>
+ <clip-path
+ android:pathData="M22,22h64v64h-64z"/>
+ <path
+ android:pathData="M54,78C67.25,78 78,67.25 78,54C78,40.75 67.25,30 54,30C40.75,30 30,40.75 30,54C30,67.25 40.75,78 54,78Z"
+ android:strokeWidth="5"
+ android:fillColor="#00000000"
+ android:strokeColor="#000000"/>
+ <group>
+ <clip-path
+ android:pathData="M77.5,54C77.5,66.98 66.98,77.5 54,77.5C41.02,77.5 30.5,66.98 30.5,54C30.5,41.02 41.02,30.5 54,30.5C66.98,30.5 77.5,41.02 77.5,54Z"/>
+ <path
+ android:pathData="M61.5,46.06C56.7,47.89 51.4,47.89 46.61,46.06L44.04,50.51C43.49,51.46 42.28,51.79 41.33,51.24C40.39,50.69 40.06,49.48 40.61,48.53L43.06,44.28C37.97,41.03 34.54,35.56 34,29.19L33.88,27.74H74.22L74.1,29.19C73.57,35.56 70.14,41.03 65.04,44.28L67.51,48.56C68.03,49.49 67.71,50.66 66.8,51.21C65.87,51.77 64.65,51.47 64.08,50.54L64.07,50.51L61.5,46.06Z"
+ android:fillColor="#000000"/>
+ </group>
+ <path
+ android:pathData="M51.33,67.33h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M48.67,62h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M56.67,70h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M56.67,62h2.67v2.67h-2.67z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M67.33,62h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M59.33,51.33h2.67v2.67h-2.67z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M62,59.33h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M70,54h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M35.33,56.67h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M35.33,48.67h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M40.67,59.33h2.67v2.67h-2.67z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M46,51.33h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M43.33,67.33h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M54,54h1.33v1.33h-1.33z"
+ android:fillColor="#000000"/>
+ </group>
+ </group>
+</vector>
diff --git a/packages/EasterEgg/res/values/landroid_strings.xml b/packages/EasterEgg/res/values/landroid_strings.xml
new file mode 100644
index 0000000..1394f2f
--- /dev/null
+++ b/packages/EasterEgg/res/values/landroid_strings.xml
@@ -0,0 +1,371 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string name="u_egg_name" translatable="false">Android 14 Easter Egg</string>
+
+ <string-array name="planet_descriptors" translatable="false">
+ <item>earthy</item>
+ <item>swamp</item>
+ <item>frozen</item>
+ <item>grassy</item>
+ <item>arid</item>
+ <item>crowded</item>
+ <item>ancient</item>
+ <item>lively</item>
+ <item>homey</item>
+ <item>modern</item>
+ <item>boring</item>
+ <item>compact</item>
+ <item>expensive</item>
+ <item>polluted</item>
+ <item>rusty</item>
+ <item>sandy</item>
+ <item>undulating</item>
+ <item>verdant</item>
+ <item>tessellated</item>
+ <item>hollow</item>
+ <item>scalding</item>
+ <item>hemispherical</item>
+ <item>oblong</item>
+ <item>oblate</item>
+ <item>vacuum</item>
+ <item>high-pressure</item>
+ <item>low-pressure</item>
+ <item>plastic</item>
+ <item>metallic</item>
+ <item>burned-out</item>
+ <item>bucolic</item>
+ </string-array>
+
+ <string-array name="life_descriptors" translatable="false">
+ <item>aggressive</item>
+ <item>passive-aggressive</item>
+ <item>shy</item>
+ <item>timid</item>
+ <item>nasty</item>
+ <item>brutish</item>
+ <item>short</item>
+ <item>absent</item>
+ <item>teen-aged</item>
+ <item>confused</item>
+ <item>transparent</item>
+ <item>cubic</item>
+ <item>quadratic</item>
+ <item>higher-order</item>
+ <item>huge</item>
+ <item>tall</item>
+ <item>wary</item>
+ <item>loud</item>
+ <item>yodeling</item>
+ <item>purring</item>
+ <item>slender</item>
+ <item>cats</item>
+ <item>adorable</item>
+ <item>eclectic</item>
+ <item>electric</item>
+ <item>microscopic</item>
+ <item>trunkless</item>
+ <item>myriad</item>
+ <item>cantankerous</item>
+ <item>gargantuan</item>
+ <item>contagious</item>
+ <item>fungal</item>
+ <item>cattywampus</item>
+ <item>spatchcocked</item>
+ <item>rotisserie</item>
+ <item>farm-to-table</item>
+ <item>organic</item>
+ <item>synthetic</item>
+ <item>unfocused</item>
+ <item>focused</item>
+ <item>capitalist</item>
+ <item>communal</item>
+ <item>bossy</item>
+ <item>malicious</item>
+ <item>compliant</item>
+ <item>psychic</item>
+ <item>oblivious</item>
+ <item>passive</item>
+ <item>bonsai</item>
+ </string-array>
+
+ <string-array name="any_descriptors" translatable="false">
+ <item>silly</item>
+ <item>dangerous</item>
+ <item>vast</item>
+ <item>invisible</item>
+ <item>superfluous</item>
+ <item>superconducting</item>
+ <item>superior</item>
+ <item>alien</item>
+ <item>phantom</item>
+ <item>friendly</item>
+ <item>peaceful</item>
+ <item>lonely</item>
+ <item>uncomfortable</item>
+ <item>charming</item>
+ <item>fractal</item>
+ <item>imaginary</item>
+ <item>forgotten</item>
+ <item>tardy</item>
+ <item>gassy</item>
+ <item>fungible</item>
+ <item>bespoke</item>
+ <item>artisanal</item>
+ <item>exceptional</item>
+ <item>puffy</item>
+ <item>rusty</item>
+ <item>fresh</item>
+ <item>crusty</item>
+ <item>glossy</item>
+ <item>lovely</item>
+ <item>processed</item>
+ <item>macabre</item>
+ <item>reticulated</item>
+ <item>shocking</item>
+ <item>void</item>
+ <item>undefined</item>
+ <item>gothic</item>
+ <item>beige</item>
+ <item>mid</item>
+ <item>milquetoast</item>
+ <item>melancholy</item>
+ <item>unnerving</item>
+ <item>cheery</item>
+ <item>vibrant</item>
+ <item>heliotrope</item>
+ <item>psychedelic</item>
+ <item>nondescript</item>
+ <item>indescribable</item>
+ <item>tubular</item>
+ <item>toroidal</item>
+ <item>voxellated</item>
+ <item>low-poly</item>
+ <item>low-carb</item>
+ <item>100% cotton</item>
+ <item>synthetic</item>
+ <item>boot-cut</item>
+ <item>bell-bottom</item>
+ <item>bumpy</item>
+ <item>fluffy</item>
+ <item>sous-vide</item>
+ <item>tepid</item>
+ <item>upcycled</item>
+ <item>sous-vide</item>
+ <item>bedazzled</item>
+ <item>ancient</item>
+ <item>inexplicable</item>
+ <item>sparkling</item>
+ <item>still</item>
+ <item>lemon-scented</item>
+ <item>eccentric</item>
+ <item>tilted</item>
+ <item>pungent</item>
+ <item>pine-scented</item>
+ <item>corduroy</item>
+ <item>overengineered</item>
+ <item>bioengineered</item>
+ <item>impossible</item>
+ </string-array>
+
+ <string-array name="constellations" translatable="false">
+ <item>Aries</item>
+ <item>Taurus</item>
+ <item>Gemini</item>
+ <item>Cancer</item>
+ <item>Leo</item>
+ <item>Virgo</item>
+ <item>Libra</item>
+ <item>Scorpio</item>
+ <item>Sagittarius</item>
+ <item>Capricorn</item>
+ <item>Aquarius</item>
+ <item>Pisces</item>
+ <item>Andromeda</item>
+ <item>Cygnus</item>
+ <item>Draco</item>
+ <item>Alcor</item>
+ <item>Calamari</item>
+ <item>Cuckoo</item>
+ <item>Neko</item>
+ <item>Monoceros</item>
+ <item>Norma</item>
+ <item>Abnorma</item>
+ <item>Morel</item>
+ <item>Redlands</item>
+ <item>Cupcake</item>
+ <item>Donut</item>
+ <item>Eclair</item>
+ <item>Froyo</item>
+ <item>Gingerbread</item>
+ <item>Honeycomb</item>
+ <item>Icecreamsandwich</item>
+ <item>Jellybean</item>
+ <item>Kitkat</item>
+ <item>Lollipop</item>
+ <item>Marshmallow</item>
+ <item>Nougat</item>
+ <item>Oreo</item>
+ <item>Pie</item>
+ <item>Quincetart</item>
+ <item>Redvelvetcake</item>
+ <item>Snowcone</item>
+ <item>Tiramisu</item>
+ <item>Upsidedowncake</item>
+ <item>Vanillaicecream</item>
+ <item>Android</item>
+ <item>Binder</item>
+ <item>Campanile</item>
+ <item>Dread</item>
+ </string-array>
+
+ <!-- prob: 5% -->
+ <string-array name="constellations_rare" translatable="false">
+ <item>Jandycane</item>
+ <item>Zombiegingerbread</item>
+ <item>Astro</item>
+ <item>Bender</item>
+ <item>Flan</item>
+ <item>Untitled-1</item>
+ <item>Expedit</item>
+ <item>Petit Four</item>
+ <item>Worcester</item>
+ <item>Xylophone</item>
+ <item>Yellowpeep</item>
+ <item>Zebraball</item>
+ <item>Hutton</item>
+ <item>Klang</item>
+ <item>Frogblast</item>
+ <item>Exo</item>
+ <item>Keylimepie</item>
+ <item>Nat</item>
+ <item>Nrp</item>
+ </string-array>
+
+ <!-- prob: 75% -->
+ <string-array name="star_suffixes" translatable="false">
+ <item>Alpha</item>
+ <item>Beta</item>
+ <item>Gamma</item>
+ <item>Delta</item>
+ <item>Epsilon</item>
+ <item>Zeta</item>
+ <item>Eta</item>
+ <item>Theta</item>
+ <item>Iota</item>
+ <item>Kappa</item>
+ <item>Lambda</item>
+ <item>Mu</item>
+ <item>Nu</item>
+ <item>Xi</item>
+ <item>Omicron</item>
+ <item>Pi</item>
+ <item>Rho</item>
+ <item>Sigma</item>
+ <item>Tau</item>
+ <item>Upsilon</item>
+ <item>Phi</item>
+ <item>Chi</item>
+ <item>Psi</item>
+ <item>Omega</item>
+
+ <item>Prime</item>
+ <item>Secundo</item>
+ <item>Major</item>
+ <item>Minor</item>
+ <item>Diminished</item>
+ <item>Augmented</item>
+ <item>Ultima</item>
+ <item>Penultima</item>
+ <item>Mid</item>
+
+ <item>Proxima</item>
+ <item>Novis</item>
+
+ <item>Plus</item>
+ </string-array>
+
+ <!-- prob: 5% -->
+ <!-- more than one can be appended, with very low prob -->
+ <string-array name="star_suffixes_rare" translatable="false">
+ <item>Serif</item>
+ <item>Sans</item>
+ <item>Oblique</item>
+ <item>Grotesque</item>
+ <item>Handtooled</item>
+ <item>III “Trey”</item>
+ <item>Alfredo</item>
+ <item>2.0</item>
+ <item>(Final)</item>
+ <item>(Final (Final))</item>
+ <item>(Draft)</item>
+ <item>Con Carne</item>
+ </string-array>
+
+ <string-array name="planet_types" translatable="false">
+ <item>planet</item>
+ <item>planetoid</item>
+ <item>moon</item>
+ <item>moonlet</item>
+ <item>centaur</item>
+ <item>asteroid</item>
+ <item>space garbage</item>
+ <item>detritus</item>
+ <item>satellite</item>
+ <item>core</item>
+ <item>giant</item>
+ <item>body</item>
+ <item>slab</item>
+ <item>rock</item>
+ <item>husk</item>
+ <item>planemo</item>
+ <item>object</item>
+ <item>planetesimal</item>
+ <item>exoplanet</item>
+ <item>ploonet</item>
+ </string-array>
+
+ <string-array name="atmo_descriptors" translatable="false">
+ <item>toxic</item>
+ <item>breathable</item>
+ <item>radioactive</item>
+ <item>clear</item>
+ <item>calm</item>
+ <item>peaceful</item>
+ <item>vacuum</item>
+ <item>stormy</item>
+ <item>freezing</item>
+ <item>burning</item>
+ <item>humid</item>
+ <item>tropical</item>
+ <item>cloudy</item>
+ <item>obscured</item>
+ <item>damp</item>
+ <item>dank</item>
+ <item>clammy</item>
+ <item>frozen</item>
+ <item>contaminated</item>
+ <item>temperate</item>
+ <item>moist</item>
+ <item>minty</item>
+ <item>relaxed</item>
+ <item>skunky</item>
+ <item>breezy</item>
+ <item>soup </item>
+ </string-array>
+
+</resources>
diff --git a/packages/EasterEgg/res/values/strings.xml b/packages/EasterEgg/res/values/strings.xml
index 743947a..79957df 100644
--- a/packages/EasterEgg/res/values/strings.xml
+++ b/packages/EasterEgg/res/values/strings.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <string name="app_name" translatable="false">Android S Easter Egg</string>
+ <string name="app_name" translatable="false">Android Easter Egg</string>
<!-- name of the Q easter egg, a nonogram-style icon puzzle -->
<string name="q_egg_name" translatable="false">Icon Quiz</string>
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Colors.kt b/packages/EasterEgg/src/com/android/egg/landroid/Colors.kt
new file mode 100644
index 0000000..f5657ae
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Colors.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import androidx.compose.ui.graphics.Color
+
+/** Various UI colors. */
+object Colors {
+ val Eigengrau = Color(0xFF16161D)
+ val Eigengrau2 = Color(0xFF292936)
+ val Eigengrau3 = Color(0xFF3C3C4F)
+ val Eigengrau4 = Color(0xFFA7A7CA)
+
+ val Console = Color(0xFFB7B7FF)
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/ComposeTools.kt b/packages/EasterEgg/src/com/android/egg/landroid/ComposeTools.kt
new file mode 100644
index 0000000..d040fba
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/ComposeTools.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import kotlin.random.Random
+
+@Composable fun Dp.toLocalPx() = with(LocalDensity.current) { this@toLocalPx.toPx() }
+
+operator fun Easing.times(next: Easing) = { x: Float -> next.transform(transform(x)) }
+
+fun flickerFadeEasing(rng: Random) = Easing { frac -> if (rng.nextFloat() < frac) 1f else 0f }
+
+val flickerFadeIn =
+ fadeIn(
+ animationSpec =
+ tween(
+ durationMillis = 1000,
+ easing = CubicBezierEasing(0f, 1f, 1f, 0f) * flickerFadeEasing(Random)
+ )
+ )
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
new file mode 100644
index 0000000..5a9b814
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import android.content.res.Resources
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.withInfiniteAnimationFrameNanos
+import androidx.compose.animation.fadeIn
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.border
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.gestures.rememberTransformableState
+import androidx.compose.foundation.gestures.transformable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.AbsoluteAlignment.Left
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Devices
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.core.math.MathUtils.clamp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowInfoTracker
+import java.lang.Float.max
+import java.lang.Float.min
+import java.util.Calendar
+import java.util.GregorianCalendar
+import kotlin.math.absoluteValue
+import kotlin.math.floor
+import kotlin.math.sqrt
+import kotlin.random.Random
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+enum class RandomSeedType {
+ Fixed,
+ Daily,
+ Evergreen
+}
+
+const val TEST_UNIVERSE = false
+
+val RANDOM_SEED_TYPE = RandomSeedType.Daily
+
+const val FIXED_RANDOM_SEED = 5038L
+const val DEFAULT_CAMERA_ZOOM = 0.25f
+const val MIN_CAMERA_ZOOM = 250f / UNIVERSE_RANGE // 0.0025f
+const val MAX_CAMERA_ZOOM = 5f
+const val TOUCH_CAMERA_PAN = false
+const val TOUCH_CAMERA_ZOOM = true
+const val DYNAMIC_ZOOM = false // @@@ FIXME
+
+fun dailySeed(): Long {
+ val today = GregorianCalendar()
+ return today.get(Calendar.YEAR) * 10_000L +
+ today.get(Calendar.MONTH) * 100L +
+ today.get(Calendar.DAY_OF_MONTH)
+}
+
+fun randomSeed(): Long {
+ return when (RANDOM_SEED_TYPE) {
+ RandomSeedType.Fixed -> FIXED_RANDOM_SEED
+ RandomSeedType.Daily -> dailySeed()
+ else -> Random.Default.nextLong().mod(10_000_000).toLong()
+ }.absoluteValue
+}
+
+val DEBUG_TEXT = mutableStateOf("Hello Universe")
+const val SHOW_DEBUG_TEXT = false
+
+@Composable
+fun DebugText(text: MutableState<String>) {
+ if (SHOW_DEBUG_TEXT) {
+ Text(
+ modifier = Modifier.fillMaxWidth().border(0.5.dp, color = Color.Yellow).padding(2.dp),
+ fontFamily = FontFamily.Monospace,
+ fontWeight = FontWeight.Medium,
+ fontSize = 9.sp,
+ color = Color.Yellow,
+ text = text.value
+ )
+ }
+}
+
+@Composable
+fun ColumnScope.ConsoleText(
+ modifier: Modifier = Modifier,
+ visible: Boolean = true,
+ random: Random = Random.Default,
+ text: String
+) {
+ AnimatedVisibility(
+ modifier = modifier,
+ visible = visible,
+ enter =
+ fadeIn(
+ animationSpec =
+ tween(
+ durationMillis = 1000,
+ easing = flickerFadeEasing(random) * CubicBezierEasing(0f, 1f, 1f, 0f)
+ )
+ )
+ ) {
+ Text(
+ fontFamily = FontFamily.Monospace,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ color = Color(0xFFFF8000),
+ text = text
+ )
+ }
+}
+
+@Composable
+fun Telemetry(universe: VisibleUniverse) {
+ var topVisible by remember { mutableStateOf(false) }
+ var bottomVisible by remember { mutableStateOf(false) }
+
+ LaunchedEffect("blah") {
+ delay(1000)
+ bottomVisible = true
+ delay(1000)
+ topVisible = true
+ }
+
+ Column(modifier = Modifier.fillMaxSize().padding(6.dp)) {
+ universe.triggerDraw.value // recompose on every frame
+ val explored = universe.planets.filter { it.explored }
+
+ AnimatedVisibility(modifier = Modifier, visible = topVisible, enter = flickerFadeIn) {
+ Text(
+ fontFamily = FontFamily.Monospace,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ color = Colors.Console,
+ modifier = Modifier.align(Left),
+ text =
+ with(universe.star) {
+ " STAR: $name (UDC-${universe.randomSeed % 100_000})\n" +
+ " CLASS: ${cls.name}\n" +
+ "RADIUS: ${radius.toInt()}\n" +
+ " MASS: %.3g\n".format(mass) +
+ "BODIES: ${explored.size} / ${universe.planets.size}\n" +
+ "\n"
+ } +
+ explored
+ .map {
+ " BODY: ${it.name}\n" +
+ " TYPE: ${it.description.capitalize()}\n" +
+ " ATMO: ${it.atmosphere.capitalize()}\n" +
+ " FAUNA: ${it.fauna.capitalize()}\n" +
+ " FLORA: ${it.flora.capitalize()}\n"
+ }
+ .joinToString("\n")
+
+ // TODO: different colors, highlight latest discovery
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ AnimatedVisibility(modifier = Modifier, visible = bottomVisible, enter = flickerFadeIn) {
+ Text(
+ fontFamily = FontFamily.Monospace,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ color = Colors.Console,
+ modifier = Modifier.align(Left),
+ text =
+ with(universe.ship) {
+ val closest = universe.closestPlanet()
+ val distToClosest = (closest.pos - pos).mag().toInt()
+ listOfNotNull(
+ landing?.let { "LND: ${it.planet.name}" }
+ ?: if (distToClosest < 10_000) {
+ "ALT: $distToClosest"
+ } else null,
+ if (thrust != Vec2.Zero) "THR: %.0f%%".format(thrust.mag() * 100f)
+ else null,
+ "POS: %s".format(pos.str("%+7.0f")),
+ "VEL: %.0f".format(velocity.mag())
+ )
+ .joinToString("\n")
+ }
+ )
+ }
+ }
+}
+
+class MainActivity : ComponentActivity() {
+ private var foldState = mutableStateOf<FoldingFeature?>(null)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ onWindowLayoutInfoChange()
+
+ val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+
+ if (TEST_UNIVERSE) {
+ universe.initTest()
+ } else {
+ universe.initRandom()
+ }
+
+ setContent {
+ Spaaaace(modifier = Modifier.fillMaxSize(), u = universe, foldState = foldState)
+ DebugText(DEBUG_TEXT)
+
+ val minRadius = 50.dp.toLocalPx()
+ val maxRadius = 100.dp.toLocalPx()
+ FlightStick(
+ modifier = Modifier.fillMaxSize(),
+ minRadius = minRadius,
+ maxRadius = maxRadius,
+ color = Color.Green
+ ) { vec ->
+ (universe.follow as? Spacecraft)?.let { ship ->
+ if (vec == Vec2.Zero) {
+ ship.thrust = Vec2.Zero
+ } else {
+ val a = vec.angle()
+ ship.angle = a
+
+ val m = vec.mag()
+ if (m < minRadius) {
+ // within this radius, just reorient
+ ship.thrust = Vec2.Zero
+ } else {
+ ship.thrust =
+ Vec2.makeWithAngleMag(
+ a,
+ lexp(minRadius, maxRadius, m).coerceIn(0f, 1f)
+ )
+ }
+ }
+ }
+ }
+ Telemetry(universe)
+ }
+ }
+
+ private fun onWindowLayoutInfoChange() {
+ val windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
+
+ lifecycleScope.launch(Dispatchers.Main) {
+ lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ windowInfoTracker.windowLayoutInfo(this@MainActivity).collect { layoutInfo ->
+ foldState.value =
+ layoutInfo.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()
+ Log.v("Landroid", "fold updated: $foldState")
+ }
+ }
+ }
+ }
+}
+
+@Preview(name = "phone", device = Devices.PHONE)
+@Preview(name = "fold", device = Devices.FOLDABLE)
+@Preview(name = "tablet", device = Devices.TABLET)
+@Composable
+fun MainActivityPreview() {
+ val universe = VisibleUniverse(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
+
+ universe.initTest()
+
+ Spaaaace(modifier = Modifier.fillMaxSize(), universe)
+ DebugText(DEBUG_TEXT)
+ Telemetry(universe)
+}
+
+@Composable
+fun FlightStick(
+ modifier: Modifier,
+ minRadius: Float = 0f,
+ maxRadius: Float = 1000f,
+ color: Color = Color.Green,
+ onStickChanged: (vector: Vec2) -> Unit
+) {
+ val origin = remember { mutableStateOf(Vec2.Zero) }
+ val target = remember { mutableStateOf(Vec2.Zero) }
+
+ Box(
+ modifier =
+ modifier
+ .pointerInput(Unit) {
+ forEachGesture {
+ awaitPointerEventScope {
+ // ACTION_DOWN
+ val down = awaitFirstDown(requireUnconsumed = false)
+ origin.value = down.position
+ target.value = down.position
+
+ do {
+ // ACTION_MOVE
+ val event: PointerEvent = awaitPointerEvent()
+ target.value = event.changes[0].position
+
+ onStickChanged(target.value - origin.value)
+ } while (
+ !event.changes.any { it.isConsumed } &&
+ event.changes.count { it.pressed } == 1
+ )
+
+ // ACTION_UP / CANCEL
+ target.value = Vec2.Zero
+ origin.value = Vec2.Zero
+
+ onStickChanged(Vec2.Zero)
+ }
+ }
+ }
+ .drawBehind {
+ if (origin.value != Vec2.Zero) {
+ val delta = target.value - origin.value
+ val mag = min(maxRadius, delta.mag())
+ val r = max(minRadius, mag)
+ val a = delta.angle()
+ drawCircle(
+ color = color,
+ center = origin.value,
+ radius = r,
+ style =
+ Stroke(
+ width = 2f,
+ pathEffect =
+ if (mag < minRadius)
+ PathEffect.dashPathEffect(
+ floatArrayOf(this.density * 1f, this.density * 2f)
+ )
+ else null
+ )
+ )
+ drawLine(
+ color = color,
+ start = origin.value,
+ end = origin.value + Vec2.makeWithAngleMag(a, mag),
+ strokeWidth = 2f
+ )
+ }
+ }
+ )
+}
+
+@Composable
+fun Spaaaace(
+ modifier: Modifier,
+ u: VisibleUniverse,
+ foldState: MutableState<FoldingFeature?> = mutableStateOf(null)
+) {
+ LaunchedEffect(u) {
+ while (true) withInfiniteAnimationFrameNanos { frameTimeNanos ->
+ u.simulateAndDrawFrame(frameTimeNanos)
+ }
+ }
+
+ var cameraZoom by remember { mutableStateOf(1f) }
+ var cameraOffset by remember { mutableStateOf(Offset.Zero) }
+
+ val transformableState =
+ rememberTransformableState { zoomChange, offsetChange, rotationChange ->
+ if (TOUCH_CAMERA_PAN) cameraOffset += offsetChange / cameraZoom
+ if (TOUCH_CAMERA_ZOOM)
+ cameraZoom = clamp(cameraZoom * zoomChange, MIN_CAMERA_ZOOM, MAX_CAMERA_ZOOM)
+ }
+
+ var canvasModifier = modifier
+
+ if (TOUCH_CAMERA_PAN || TOUCH_CAMERA_ZOOM) {
+ canvasModifier = canvasModifier.transformable(transformableState)
+ }
+
+ val halfFolded = foldState.value?.let { it.state == FoldingFeature.State.HALF_OPENED } ?: false
+ val horizontalFold =
+ foldState.value?.let { it.orientation == FoldingFeature.Orientation.HORIZONTAL } ?: false
+
+ val centerFracX: Float by
+ animateFloatAsState(if (halfFolded && !horizontalFold) 0.25f else 0.5f, label = "centerX")
+ val centerFracY: Float by
+ animateFloatAsState(if (halfFolded && horizontalFold) 0.25f else 0.5f, label = "centerY")
+
+ Canvas(modifier = canvasModifier) {
+ drawRect(Colors.Eigengrau, Offset.Zero, size)
+
+ val closest = u.closestPlanet()
+ val distToNearestSurf = max(0f, (u.ship.pos - closest.pos).mag() - closest.radius * 1.2f)
+ // val normalizedDist = clamp(distToNearestSurf, 50f, 50_000f) / 50_000f
+ if (DYNAMIC_ZOOM) {
+ // cameraZoom = lerp(0.1f, 5f, smooth(1f-normalizedDist))
+ cameraZoom = clamp(500f / distToNearestSurf, MIN_CAMERA_ZOOM, MAX_CAMERA_ZOOM)
+ } else if (!TOUCH_CAMERA_ZOOM) cameraZoom = DEFAULT_CAMERA_ZOOM
+ if (!TOUCH_CAMERA_PAN) cameraOffset = (u.follow?.pos ?: Vec2.Zero) * -1f
+
+ // cameraZoom: metersToPixels
+ // visibleSpaceSizeMeters: meters
+ // cameraOffset: meters ≈ vector pointing from ship to (0,0) (e.g. -pos)
+ val visibleSpaceSizeMeters = size / cameraZoom // meters x meters
+ val visibleSpaceRectMeters =
+ Rect(
+ -cameraOffset -
+ Offset(
+ visibleSpaceSizeMeters.width * centerFracX,
+ visibleSpaceSizeMeters.height * centerFracY
+ ),
+ visibleSpaceSizeMeters
+ )
+
+ var gridStep = 1000f
+ while (gridStep * cameraZoom < 32.dp.toPx()) gridStep *= 10
+
+ DEBUG_TEXT.value =
+ ("SIMULATION //\n" +
+ // "normalizedDist=${normalizedDist} \n" +
+ "entities: ${u.entities.size} // " +
+ "zoom: ${"%.4f".format(cameraZoom)}x // " +
+ "fps: ${"%3.0f".format(1f / u.dt)} " +
+ "dt: ${u.dt}\n" +
+ ((u.follow as? Spacecraft)?.let {
+ "ship: p=%s v=%7.2f a=%6.3f t=%s\n".format(
+ it.pos.str("%+7.1f"),
+ it.velocity.mag(),
+ it.angle,
+ it.thrust.str("%+5.2f")
+ )
+ }
+ ?: "") +
+ "star: '${u.star.name}' designation=UDC-${u.randomSeed % 100_000} " +
+ "class=${u.star.cls.name} r=${u.star.radius.toInt()} m=${u.star.mass}\n" +
+ "planets: ${u.planets.size}\n" +
+ u.planets.joinToString("\n") {
+ val range = (u.ship.pos - it.pos).mag()
+ val vorbit = sqrt(GRAVITATION * it.mass / range)
+ val vescape = sqrt(2 * GRAVITATION * it.mass / it.radius)
+ " * ${it.name}:\n" +
+ if (it.explored) {
+ " TYPE: ${it.description.capitalize()}\n" +
+ " ATMO: ${it.atmosphere.capitalize()}\n" +
+ " FAUNA: ${it.fauna.capitalize()}\n" +
+ " FLORA: ${it.flora.capitalize()}\n"
+ } else {
+ " (Unexplored)\n"
+ } +
+ " orbit=${(it.pos - it.orbitCenter).mag().toInt()}" +
+ " radius=${it.radius.toInt()}" +
+ " mass=${"%g".format(it.mass)}" +
+ " vel=${(it.speed).toInt()}" +
+ " // range=${"%.0f".format(range)}" +
+ " vorbit=${vorbit.toInt()} vescape=${vescape.toInt()}"
+ })
+
+ zoom(cameraZoom) {
+ // All coordinates are space coordinates now.
+
+ translate(
+ -visibleSpaceRectMeters.center.x + size.width * 0.5f,
+ -visibleSpaceRectMeters.center.y + size.height * 0.5f
+ ) {
+ // debug outer frame
+ // drawRect(
+ // Colors.Eigengrau2,
+ // visibleSpaceRectMeters.topLeft,
+ // visibleSpaceRectMeters.size,
+ // style = Stroke(width = 10f / cameraZoom)
+ // )
+
+ var x = floor(visibleSpaceRectMeters.left / gridStep) * gridStep
+ while (x < visibleSpaceRectMeters.right) {
+ drawLine(
+ color = Colors.Eigengrau2,
+ start = Offset(x, visibleSpaceRectMeters.top),
+ end = Offset(x, visibleSpaceRectMeters.bottom),
+ strokeWidth = (if ((x % (gridStep * 10) == 0f)) 3f else 1.5f) / cameraZoom
+ )
+ x += gridStep
+ }
+
+ var y = floor(visibleSpaceRectMeters.top / gridStep) * gridStep
+ while (y < visibleSpaceRectMeters.bottom) {
+ drawLine(
+ color = Colors.Eigengrau2,
+ start = Offset(visibleSpaceRectMeters.left, y),
+ end = Offset(visibleSpaceRectMeters.right, y),
+ strokeWidth = (if ((y % (gridStep * 10) == 0f)) 3f else 1.5f) / cameraZoom
+ )
+ y += gridStep
+ }
+
+ this@zoom.drawUniverse(u)
+ }
+ }
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Maths.kt b/packages/EasterEgg/src/com/android/egg/landroid/Maths.kt
new file mode 100644
index 0000000..fdf29f7
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Maths.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import kotlin.math.pow
+
+/** smoothstep. Ken Perlin's version */
+fun smooth(x: Float): Float {
+ return x * x * x * (x * (x * 6 - 15) + 10)
+}
+
+/** Kind of like an inverted smoothstep, but */
+fun invsmoothish(x: Float): Float {
+ return 0.25f * ((2f * x - 1f).pow(5f) + 1f) + 0.5f * x
+}
+
+/** Compute the fraction that progress represents between start and end (inverse of lerp). */
+fun lexp(start: Float, end: Float, progress: Float): Float {
+ return (progress - start) / (end - start)
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Namer.kt b/packages/EasterEgg/src/com/android/egg/landroid/Namer.kt
new file mode 100644
index 0000000..67d536e
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Namer.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import android.content.res.Resources
+import kotlin.random.Random
+
+import com.android.egg.R
+
+const val SUFFIX_PROB = 0.75f
+const val LETTER_PROB = 0.3f
+const val NUMBER_PROB = 0.3f
+const val RARE_PROB = 0.05f
+
+class Namer(resources: Resources) {
+ private val planetDescriptors = Bag(resources.getStringArray(R.array.planet_descriptors))
+ private val lifeDescriptors = Bag(resources.getStringArray(R.array.life_descriptors))
+ private val anyDescriptors = Bag(resources.getStringArray(R.array.any_descriptors))
+ private val atmoDescriptors = Bag(resources.getStringArray(R.array.atmo_descriptors))
+
+ private val planetTypes = Bag(resources.getStringArray(R.array.planet_types))
+ private val constellations = Bag(resources.getStringArray(R.array.constellations))
+ private val constellationsRare = Bag(resources.getStringArray(R.array.constellations_rare))
+ private val suffixes = Bag(resources.getStringArray(R.array.star_suffixes))
+ private val suffixesRare = Bag(resources.getStringArray(R.array.star_suffixes_rare))
+
+ private val planetTable = RandomTable(0.75f to planetDescriptors, 0.25f to anyDescriptors)
+
+ private var lifeTable = RandomTable(0.75f to lifeDescriptors, 0.25f to anyDescriptors)
+
+ private var constellationsTable =
+ RandomTable(RARE_PROB to constellationsRare, 1f - RARE_PROB to constellations)
+
+ private var suffixesTable = RandomTable(RARE_PROB to suffixesRare, 1f - RARE_PROB to suffixes)
+
+ private var atmoTable = RandomTable(0.75f to atmoDescriptors, 0.25f to anyDescriptors)
+
+ private var delimiterTable =
+ RandomTable(
+ 15f to " ",
+ 3f to "-",
+ 1f to "_",
+ 1f to "/",
+ 1f to ".",
+ 1f to "*",
+ 1f to "^",
+ 1f to "#",
+ 0.1f to "(^*!%@##!!"
+ )
+
+ fun describePlanet(rng: Random): String {
+ return planetTable.roll(rng).pull(rng) + " " + planetTypes.pull(rng)
+ }
+
+ fun describeLife(rng: Random): String {
+ return lifeTable.roll(rng).pull(rng)
+ }
+
+ fun nameSystem(rng: Random): String {
+ val parts = StringBuilder()
+ parts.append(constellationsTable.roll(rng).pull(rng))
+ if (rng.nextFloat() <= SUFFIX_PROB) {
+ parts.append(delimiterTable.roll(rng))
+ parts.append(suffixesTable.roll(rng).pull(rng))
+ if (rng.nextFloat() <= RARE_PROB) parts.append(' ').append(suffixesRare.pull(rng))
+ }
+ if (rng.nextFloat() <= LETTER_PROB) {
+ parts.append(delimiterTable.roll(rng))
+ parts.append('A' + rng.nextInt(0, 26))
+ if (rng.nextFloat() <= RARE_PROB) parts.append(delimiterTable.roll(rng))
+ }
+ if (rng.nextFloat() <= NUMBER_PROB) {
+ parts.append(delimiterTable.roll(rng))
+ parts.append(rng.nextInt(2, 5039))
+ }
+ return parts.toString()
+ }
+
+ fun describeAtmo(rng: Random): String {
+ return atmoTable.roll(rng).pull(rng)
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/PathTools.kt b/packages/EasterEgg/src/com/android/egg/landroid/PathTools.kt
new file mode 100644
index 0000000..8510640
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/PathTools.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import android.util.Log
+import androidx.compose.ui.graphics.Path
+import kotlin.math.cos
+import kotlin.math.sin
+
+fun createPolygon(radius: Float, sides: Int): Path {
+ return Path().apply {
+ moveTo(radius, 0f)
+ val angleStep = PI2f / sides
+ for (i in 1 until sides) {
+ lineTo(radius * cos(angleStep * i), radius * sin(angleStep * i))
+ }
+ close()
+ }
+}
+
+fun createStar(radius1: Float, radius2: Float, points: Int): Path {
+ return Path().apply {
+ val angleStep = PI2f / points
+ moveTo(radius1, 0f)
+ lineTo(radius2 * cos(angleStep * (0.5f)), radius2 * sin(angleStep * (0.5f)))
+ for (i in 1 until points) {
+ lineTo(radius1 * cos(angleStep * i), radius1 * sin(angleStep * i))
+ lineTo(radius2 * cos(angleStep * (i + 0.5f)), radius2 * sin(angleStep * (i + 0.5f)))
+ }
+ close()
+ }
+}
+
+fun Path.parseSvgPathData(d: String) {
+ Regex("([A-Z])([-.,0-9e ]+)").findAll(d.trim()).forEach {
+ val cmd = it.groups[1]!!.value
+ val args =
+ it.groups[2]?.value?.split(Regex("\\s+"))?.map { v -> v.toFloat() } ?: emptyList()
+ Log.d("Landroid", "cmd = $cmd, args = " + args.joinToString(","))
+ when (cmd) {
+ "M" -> moveTo(args[0], args[1])
+ "C" -> cubicTo(args[0], args[1], args[2], args[3], args[4], args[5])
+ "L" -> lineTo(args[0], args[1])
+ "Z" -> close()
+ else -> Log.v("Landroid", "unsupported SVG command: $cmd")
+ }
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
new file mode 100644
index 0000000..fc66ad6
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import android.util.ArraySet
+import kotlin.random.Random
+
+// artificially speed up or slow down the simulation
+const val TIME_SCALE = 1f
+
+// if it's been over 1 real second since our last timestep, don't simulate that elapsed time.
+// this allows the simulation to "pause" when, for example, the activity pauses
+const val MAX_VALID_DT = 1f
+
+interface Entity {
+ // Integrate.
+ // Compute accelerations from forces, add accelerations to velocity, save old position,
+ // add velocity to position.
+ fun update(sim: Simulator, dt: Float)
+
+ // Post-integration step, after constraints are satisfied.
+ fun postUpdate(sim: Simulator, dt: Float)
+}
+
+open class Body(var name: String = "Unknown") : Entity {
+ var pos = Vec2.Zero
+ var opos = Vec2.Zero
+ var velocity = Vec2.Zero
+
+ var mass = 0f
+ var angle = 0f
+ var radius = 0f
+
+ var collides = true
+
+ var omega: Float
+ get() = angle - oangle
+ set(value) {
+ oangle = angle - value
+ }
+
+ var oangle = 0f
+
+ override fun update(sim: Simulator, dt: Float) {
+ if (dt <= 0) return
+
+ // integrate velocity
+ val vscaled = velocity * dt
+ opos = pos
+ pos += vscaled
+
+ // integrate angular velocity
+ // val wscaled = omega * timescale
+ // oangle = angle
+ // angle = (angle + wscaled) % PI2f
+ }
+
+ override fun postUpdate(sim: Simulator, dt: Float) {
+ if (dt <= 0) return
+ velocity = (pos - opos) / dt
+ }
+}
+
+interface Constraint {
+ // Solve constraints. Pick up objects and put them where they are "supposed" to be.
+ fun solve(sim: Simulator, dt: Float)
+}
+
+open class Container(val radius: Float) : Constraint {
+ private val list = ArraySet<Body>()
+ private val softness = 0.0f
+
+ override fun toString(): String {
+ return "Container($radius)"
+ }
+
+ fun add(p: Body) {
+ list.add(p)
+ }
+
+ fun remove(p: Body) {
+ list.remove(p)
+ }
+
+ override fun solve(sim: Simulator, dt: Float) {
+ for (p in list) {
+ if ((p.pos.mag() + p.radius) > radius) {
+ p.pos =
+ p.pos * (softness) +
+ Vec2.makeWithAngleMag(p.pos.angle(), radius - p.radius) * (1f - softness)
+ }
+ }
+ }
+}
+
+open class Simulator(val randomSeed: Long) {
+ private var wallClockNanos: Long = 0L
+ var now: Float = 0f
+ var dt: Float = 0f
+ val rng = Random(randomSeed)
+ val entities = ArraySet<Entity>(1000)
+ val constraints = ArraySet<Constraint>(100)
+
+ fun add(e: Entity) = entities.add(e)
+ fun remove(e: Entity) = entities.remove(e)
+ fun add(c: Constraint) = constraints.add(c)
+ fun remove(c: Constraint) = constraints.remove(c)
+
+ open fun updateAll(dt: Float, entities: ArraySet<Entity>) {
+ entities.forEach { it.update(this, dt) }
+ }
+
+ open fun solveAll(dt: Float, constraints: ArraySet<Constraint>) {
+ constraints.forEach { it.solve(this, dt) }
+ }
+
+ open fun postUpdateAll(dt: Float, entities: ArraySet<Entity>) {
+ entities.forEach { it.postUpdate(this, dt) }
+ }
+
+ fun step(nanos: Long) {
+ val firstFrame = (wallClockNanos == 0L)
+
+ dt = (nanos - wallClockNanos) / 1_000_000_000f * TIME_SCALE
+ this.wallClockNanos = nanos
+
+ // we start the simulation on the next frame
+ if (firstFrame || dt > MAX_VALID_DT) return
+
+ // simulation is running; we start accumulating simulation time
+ this.now += dt
+
+ val localEntities = ArraySet(entities)
+ val localConstraints = ArraySet(constraints)
+
+ // position-based dynamics approach:
+ // 1. apply acceleration to velocity, save positions, apply velocity to position
+ updateAll(dt, localEntities)
+
+ // 2. solve all constraints
+ solveAll(dt, localConstraints)
+
+ // 3. compute new velocities from updated positions and saved positions
+ postUpdateAll(dt, localEntities)
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Randomness.kt b/packages/EasterEgg/src/com/android/egg/landroid/Randomness.kt
new file mode 100644
index 0000000..ebbb2bd
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Randomness.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import kotlin.random.Random
+
+/**
+ * A bag of stones. Each time you pull one out it is not replaced, preventing duplicates. When the
+ * bag is exhausted, all the stones are replaced and reshuffled.
+ */
+class Bag<T>(items: Array<T>) {
+ private val remaining = items.copyOf()
+ private var next = remaining.size // will cause a shuffle on first pull()
+
+ /** Return the next random item from the bag, without replacing it. */
+ fun pull(rng: Random): T {
+ if (next >= remaining.size) {
+ remaining.shuffle(rng)
+ next = 0
+ }
+ return remaining[next++]
+ }
+}
+
+/**
+ * A loot table. The weight of each possibility is in the first of the pair; the value to be
+ * returned in the second. They need not add up to 1f (we will do that for you, free of charge).
+ */
+class RandomTable<T>(private vararg val pairs: Pair<Float, T>) {
+ private val total = pairs.map { it.first }.sum()
+
+ /** Select a random value from the weighted table. */
+ fun roll(rng: Random): T {
+ var x = rng.nextFloatInRange(0f, total)
+ for ((weight, result) in pairs) {
+ x -= weight
+ if (x < 0f) return result
+ }
+ return pairs.last().second
+ }
+}
+
+/** Return a random float in the range [from, until). */
+fun Random.nextFloatInRange(from: Float, until: Float): Float =
+ from + ((until - from) * nextFloat())
+
+/** Return a random float in the range [start, end). */
+fun Random.nextFloatInRange(fromUntil: ClosedFloatingPointRange<Float>): Float =
+ nextFloatInRange(fromUntil.start, fromUntil.endInclusive)
+/** Return a random float in the range [first, second). */
+fun Random.nextFloatInRange(fromUntil: Pair<Float, Float>): Float =
+ nextFloatInRange(fromUntil.first, fromUntil.second)
+
+/** Choose a random element from an array. */
+fun <T> Random.choose(array: Array<T>) = array[nextInt(array.size)]
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt b/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
new file mode 100644
index 0000000..fec3ab3
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import android.util.ArraySet
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.util.lerp
+import kotlin.math.absoluteValue
+import kotlin.math.pow
+import kotlin.math.sqrt
+
+const val UNIVERSE_RANGE = 200_000f
+
+val NUM_PLANETS_RANGE = 1..10
+val STAR_RADIUS_RANGE = (1_000f..8_000f)
+val PLANET_RADIUS_RANGE = (50f..2_000f)
+val PLANET_ORBIT_RANGE = (STAR_RADIUS_RANGE.endInclusive * 2f)..(UNIVERSE_RANGE * 0.75f)
+
+const val GRAVITATION = 1e-2f
+const val KEPLER_CONSTANT = 50f // * 4f * PIf * PIf / GRAVITATION
+
+// m = d * r
+const val PLANETARY_DENSITY = 2.5f
+const val STELLAR_DENSITY = 0.5f
+
+const val SPACECRAFT_MASS = 10f
+
+const val CRAFT_SPEED_LIMIT = 5_000f
+const val MAIN_ENGINE_ACCEL = 1000f // thrust effect, pixels per second squared
+const val LAUNCH_MECO = 2f // how long to suspend gravity when launching
+
+const val SCALED_THRUST = true
+
+interface Removable {
+ fun canBeRemoved(): Boolean
+}
+
+open class Planet(
+ val orbitCenter: Vec2,
+ radius: Float,
+ pos: Vec2,
+ var speed: Float,
+ var color: Color = Color.White
+) : Body() {
+ var atmosphere = ""
+ var description = ""
+ var flora = ""
+ var fauna = ""
+ var explored = false
+ private val orbitRadius: Float
+ init {
+ this.radius = radius
+ this.pos = pos
+ orbitRadius = pos.distance(orbitCenter)
+ mass = 4 / 3 * PIf * radius.pow(3) * PLANETARY_DENSITY
+ }
+
+ override fun update(sim: Simulator, dt: Float) {
+ val orbitAngle = (pos - orbitCenter).angle()
+ // constant linear velocity
+ velocity = Vec2.makeWithAngleMag(orbitAngle + PIf / 2f, speed)
+
+ super.update(sim, dt)
+ }
+
+ override fun postUpdate(sim: Simulator, dt: Float) {
+ // This is kind of like a constraint, but whatever.
+ val orbitAngle = (pos - orbitCenter).angle()
+ pos = orbitCenter + Vec2.makeWithAngleMag(orbitAngle, orbitRadius)
+ super.postUpdate(sim, dt)
+ }
+}
+
+enum class StarClass {
+ O,
+ B,
+ A,
+ F,
+ G,
+ K,
+ M
+}
+
+fun starColor(cls: StarClass) =
+ when (cls) {
+ StarClass.O -> Color(0xFF6666FF)
+ StarClass.B -> Color(0xFFCCCCFF)
+ StarClass.A -> Color(0xFFEEEEFF)
+ StarClass.F -> Color(0xFFFFFFFF)
+ StarClass.G -> Color(0xFFFFFF66)
+ StarClass.K -> Color(0xFFFFCC33)
+ StarClass.M -> Color(0xFFFF8800)
+ }
+
+class Star(val cls: StarClass, radius: Float) :
+ Planet(orbitCenter = Vec2.Zero, radius = radius, pos = Vec2.Zero, speed = 0f) {
+ init {
+ pos = Vec2.Zero
+ mass = 4 / 3 * PIf * radius.pow(3) * STELLAR_DENSITY
+ color = starColor(cls)
+ collides = false
+ }
+ var anim = 0f
+ override fun update(sim: Simulator, dt: Float) {
+ anim += dt
+ }
+}
+
+open class Universe(val namer: Namer, randomSeed: Long) : Simulator(randomSeed) {
+ var latestDiscovery: Planet? = null
+ lateinit var star: Star
+ lateinit var ship: Spacecraft
+ val planets: MutableList<Planet> = mutableListOf()
+ var follow: Body? = null
+ val ringfence = Container(UNIVERSE_RANGE)
+
+ fun initTest() {
+ val systemName = "TEST SYSTEM"
+ star =
+ Star(
+ cls = StarClass.A,
+ radius = STAR_RADIUS_RANGE.endInclusive,
+ )
+ .apply { name = "TEST SYSTEM" }
+
+ repeat(NUM_PLANETS_RANGE.last) {
+ val thisPlanetFrac = it.toFloat() / (NUM_PLANETS_RANGE.last - 1)
+ val radius =
+ lerp(PLANET_RADIUS_RANGE.start, PLANET_RADIUS_RANGE.endInclusive, thisPlanetFrac)
+ val orbitRadius =
+ lerp(PLANET_ORBIT_RANGE.start, PLANET_ORBIT_RANGE.endInclusive, thisPlanetFrac)
+
+ val period = sqrt(orbitRadius.pow(3f) / star.mass) * KEPLER_CONSTANT
+ val speed = 2f * PIf * orbitRadius / period
+
+ val p =
+ Planet(
+ orbitCenter = star.pos,
+ radius = radius,
+ pos = star.pos + Vec2.makeWithAngleMag(thisPlanetFrac * PI2f, orbitRadius),
+ speed = speed,
+ color = Colors.Eigengrau4
+ )
+ android.util.Log.v(
+ "Landroid",
+ "created planet $p with period $period and vel $speed"
+ )
+ val num = it + 1
+ p.description = "TEST PLANET #$num"
+ p.atmosphere = "radius=$radius"
+ p.flora = "mass=${p.mass}"
+ p.fauna = "speed=$speed"
+ planets.add(p)
+ add(p)
+ }
+
+ planets.sortBy { it.pos.distance(star.pos) }
+ planets.forEachIndexed { idx, planet -> planet.name = "$systemName ${idx + 1}" }
+ add(star)
+
+ ship = Spacecraft()
+
+ ship.pos = star.pos + Vec2.makeWithAngleMag(PIf / 4, PLANET_ORBIT_RANGE.start)
+ ship.angle = 0f
+ add(ship)
+
+ ringfence.add(ship)
+ add(ringfence)
+
+ follow = ship
+ }
+
+ fun initRandom() {
+ val systemName = namer.nameSystem(rng)
+ star =
+ Star(
+ cls = rng.choose(StarClass.values()),
+ radius = rng.nextFloatInRange(STAR_RADIUS_RANGE)
+ )
+ star.name = systemName
+ repeat(rng.nextInt(NUM_PLANETS_RANGE.first, NUM_PLANETS_RANGE.last + 1)) {
+ val radius = rng.nextFloatInRange(PLANET_RADIUS_RANGE)
+ val orbitRadius =
+ lerp(
+ PLANET_ORBIT_RANGE.start,
+ PLANET_ORBIT_RANGE.endInclusive,
+ rng.nextFloat().pow(1f)
+ )
+
+ // Kepler's third law
+ val period = sqrt(orbitRadius.pow(3f) / star.mass) * KEPLER_CONSTANT
+ val speed = 2f * PIf * orbitRadius / period
+
+ val p =
+ Planet(
+ orbitCenter = star.pos,
+ radius = radius,
+ pos = star.pos + Vec2.makeWithAngleMag(rng.nextFloat() * PI2f, orbitRadius),
+ speed = speed,
+ color = Colors.Eigengrau4
+ )
+ android.util.Log.v(
+ "Landroid",
+ "created planet $p with period $period and vel $speed"
+ )
+ p.description = namer.describePlanet(rng)
+ p.atmosphere = namer.describeAtmo(rng)
+ p.flora = namer.describeLife(rng)
+ p.fauna = namer.describeLife(rng)
+ planets.add(p)
+ add(p)
+ }
+ planets.sortBy { it.pos.distance(star.pos) }
+ planets.forEachIndexed { idx, planet -> planet.name = "$systemName ${idx + 1}" }
+ add(star)
+
+ ship = Spacecraft()
+
+ ship.pos =
+ star.pos +
+ Vec2.makeWithAngleMag(
+ rng.nextFloat() * PI2f,
+ rng.nextFloatInRange(PLANET_ORBIT_RANGE.start, PLANET_ORBIT_RANGE.endInclusive)
+ )
+ ship.angle = rng.nextFloat() * PI2f
+ add(ship)
+
+ ringfence.add(ship)
+ add(ringfence)
+
+ follow = ship
+ }
+
+ override fun updateAll(dt: Float, entities: ArraySet<Entity>) {
+ // check for passing in front of the sun
+ ship.transit = false
+
+ (planets + star).forEach { planet ->
+ val vector = planet.pos - ship.pos
+ val d = vector.mag()
+ if (d < planet.radius) {
+ if (planet is Star) ship.transit = true
+ } else if (
+ now > ship.launchClock + LAUNCH_MECO
+ ) { // within MECO sec of launch, no gravity at all
+ // simulate gravity: $ f_g = G * m1 * m2 * 1/d^2 $
+ ship.velocity =
+ ship.velocity +
+ Vec2.makeWithAngleMag(
+ vector.angle(),
+ GRAVITATION * (ship.mass * planet.mass) / d.pow(2)
+ ) * dt
+ }
+ }
+
+ super.updateAll(dt, entities)
+ }
+
+ fun closestPlanet(): Planet {
+ val bodiesByDist =
+ (planets + star)
+ .map { planet -> (planet.pos - ship.pos) to planet }
+ .sortedBy { it.first.mag() }
+
+ return bodiesByDist[0].second
+ }
+
+ override fun solveAll(dt: Float, constraints: ArraySet<Constraint>) {
+ if (ship.landing == null) {
+ val planet = closestPlanet()
+
+ if (planet.collides) {
+ val d = (ship.pos - planet.pos).mag() - ship.radius - planet.radius
+ val a = (ship.pos - planet.pos).angle()
+
+ if (d < 0) {
+ // landing, or impact?
+
+ // 1. relative speed
+ val vDiff = (ship.velocity - planet.velocity).mag()
+ // 2. landing angle
+ val aDiff = (ship.angle - a).absoluteValue
+
+ // landing criteria
+ if (aDiff < PIf / 4
+ // &&
+ // vDiff < 100f
+ ) {
+ val landing = Landing(ship, planet, a)
+ ship.landing = landing
+ ship.velocity = planet.velocity
+ add(landing)
+
+ planet.explored = true
+ latestDiscovery = planet
+ } else {
+ val impact = planet.pos + Vec2.makeWithAngleMag(a, planet.radius)
+ ship.pos =
+ planet.pos + Vec2.makeWithAngleMag(a, planet.radius + ship.radius - d)
+
+ // add(Spark(
+ // lifetime = 1f,
+ // style = Spark.Style.DOT,
+ // color = Color.Yellow,
+ // size = 10f
+ // ).apply {
+ // pos = impact
+ // opos = impact
+ // velocity = Vec2.Zero
+ // })
+ //
+ (1..10).forEach {
+ Spark(
+ lifetime = rng.nextFloatInRange(0.5f, 2f),
+ style = Spark.Style.DOT,
+ color = Color.White,
+ size = 1f
+ )
+ .apply {
+ pos =
+ impact +
+ Vec2.makeWithAngleMag(
+ rng.nextFloatInRange(0f, 2 * PIf),
+ rng.nextFloatInRange(0.1f, 0.5f)
+ )
+ opos = pos
+ velocity =
+ ship.velocity * 0.8f +
+ Vec2.makeWithAngleMag(
+ // a +
+ // rng.nextFloatInRange(-PIf, PIf),
+ rng.nextFloatInRange(0f, 2 * PIf),
+ rng.nextFloatInRange(0.1f, 0.5f)
+ )
+ add(this)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ super.solveAll(dt, constraints)
+ }
+
+ override fun postUpdateAll(dt: Float, entities: ArraySet<Entity>) {
+ super.postUpdateAll(dt, entities)
+
+ entities
+ .filterIsInstance<Removable>()
+ .filter(predicate = Removable::canBeRemoved)
+ .filterIsInstance<Entity>()
+ .forEach { remove(it) }
+ }
+}
+
+class Landing(val ship: Spacecraft, val planet: Planet, val angle: Float) : Constraint {
+ private val landingVector = Vec2.makeWithAngleMag(angle, ship.radius + planet.radius)
+ override fun solve(sim: Simulator, dt: Float) {
+ val desiredPos = planet.pos + landingVector
+ ship.pos = (ship.pos * 0.5f) + (desiredPos * 0.5f) // @@@ FIXME
+ ship.angle = angle
+ }
+}
+
+class Spark(
+ var lifetime: Float,
+ collides: Boolean = false,
+ mass: Float = 0f,
+ val style: Style = Style.LINE,
+ val color: Color = Color.Gray,
+ val size: Float = 2f
+) : Removable, Body() {
+ enum class Style {
+ LINE,
+ LINE_ABSOLUTE,
+ DOT,
+ DOT_ABSOLUTE,
+ RING
+ }
+
+ init {
+ this.collides = collides
+ this.mass = mass
+ }
+ override fun update(sim: Simulator, dt: Float) {
+ super.update(sim, dt)
+ lifetime -= dt
+ }
+ override fun canBeRemoved(): Boolean {
+ return lifetime < 0
+ }
+}
+
+const val TRACK_LENGTH = 10_000
+const val SIMPLE_TRACK_DRAWING = true
+
+class Track {
+ val positions = ArrayDeque<Vec2>(TRACK_LENGTH)
+ private val angles = ArrayDeque<Float>(TRACK_LENGTH)
+ fun add(x: Float, y: Float, a: Float) {
+ if (positions.size >= (TRACK_LENGTH - 1)) {
+ positions.removeFirst()
+ angles.removeFirst()
+ positions.removeFirst()
+ angles.removeFirst()
+ }
+ positions.addLast(Vec2(x, y))
+ angles.addLast(a)
+ }
+}
+
+class Spacecraft : Body() {
+ var thrust = Vec2.Zero
+ var launchClock = 0f
+
+ var transit = false
+
+ val track = Track()
+
+ var landing: Landing? = null
+
+ init {
+ mass = SPACECRAFT_MASS
+ radius = 12f
+ }
+
+ override fun update(sim: Simulator, dt: Float) {
+ // check for thrusters
+ val thrustMag = thrust.mag()
+ if (thrustMag > 0) {
+ var deltaV = MAIN_ENGINE_ACCEL * dt
+ if (SCALED_THRUST) deltaV *= thrustMag.coerceIn(0f, 1f)
+
+ if (landing == null) {
+ // we are free in space, so we attempt to pivot toward the desired direction
+ // NOTE: no longer required thanks to FlightStick
+ // angle = thrust.angle()
+ } else
+ landing?.let { landing ->
+ if (launchClock == 0f) launchClock = sim.now + 1f /* @@@ TODO extract */
+
+ if (sim.now > launchClock) {
+ // first-stage to orbit has 1000x power
+ // deltaV *= 1000f
+ sim.remove(landing)
+ this.landing = null
+ } else {
+ deltaV = 0f
+ }
+ }
+
+ // this is it. impart thrust to the ship.
+ // note that we always thrust in the forward direction
+ velocity += Vec2.makeWithAngleMag(angle, deltaV)
+ } else {
+ if (launchClock != 0f) launchClock = 0f
+ }
+
+ // apply global speed limit
+ if (velocity.mag() > CRAFT_SPEED_LIMIT)
+ velocity = Vec2.makeWithAngleMag(velocity.angle(), CRAFT_SPEED_LIMIT)
+
+ super.update(sim, dt)
+ }
+
+ override fun postUpdate(sim: Simulator, dt: Float) {
+ super.postUpdate(sim, dt)
+
+ // special effects all need to be added after the simulation step so they have
+ // the correct position of the ship.
+ track.add(pos.x, pos.y, angle)
+
+ val mag = thrust.mag()
+ if (sim.rng.nextFloat() < mag) {
+ // exhaust
+ sim.add(
+ Spark(
+ lifetime = sim.rng.nextFloatInRange(0.5f, 1f),
+ collides = true,
+ mass = 1f,
+ style = Spark.Style.RING,
+ size = 3f,
+ color = Color(0x40FFFFFF)
+ )
+ .also { spark ->
+ spark.pos = pos
+ spark.opos = pos
+ spark.velocity =
+ velocity +
+ Vec2.makeWithAngleMag(
+ angle + sim.rng.nextFloatInRange(-0.2f, 0.2f),
+ -MAIN_ENGINE_ACCEL * mag * 10f * dt
+ )
+ }
+ )
+ }
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Vec2.kt b/packages/EasterEgg/src/com/android/egg/landroid/Vec2.kt
new file mode 100644
index 0000000..82bae75
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Vec2.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import androidx.compose.ui.geometry.Offset
+import kotlin.math.PI
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.sin
+
+const val PIf = PI.toFloat()
+const val PI2f = (2 * PI).toFloat()
+
+typealias Vec2 = Offset
+
+fun Vec2.str(fmt: String = "%+.2f"): String = "<$fmt,$fmt>".format(x, y)
+
+fun Vec2(x: Float, y: Float): Vec2 = Offset(x, y)
+
+fun Vec2.mag(): Float {
+ return getDistance()
+}
+
+fun Vec2.distance(other: Vec2): Float {
+ return (this - other).mag()
+}
+
+fun Vec2.angle(): Float {
+ return atan2(y, x)
+}
+
+fun Vec2.dot(o: Vec2): Float {
+ return x * o.x + y * o.y
+}
+
+fun Vec2.product(f: Float): Vec2 {
+ return Vec2(x * f, y * f)
+}
+
+fun Offset.Companion.makeWithAngleMag(a: Float, m: Float): Vec2 {
+ return Vec2(m * cos(a), m * sin(a))
+}
+
+fun Vec2.rotate(angle: Float, origin: Vec2 = Vec2.Zero): Offset {
+ val translated = this - origin
+ return origin +
+ Offset(
+ (translated.x * cos(angle) - translated.y * sin(angle)),
+ (translated.x * sin(angle) + translated.y * cos(angle))
+ )
+}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
new file mode 100644
index 0000000..24b9c6a
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.egg.landroid
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.PointMode
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.rotateRad
+import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.util.lerp
+import androidx.core.math.MathUtils.clamp
+import java.lang.Float.max
+import kotlin.math.sqrt
+
+const val DRAW_ORBITS = true
+const val DRAW_GRAVITATIONAL_FIELDS = true
+const val DRAW_STAR_GRAVITATIONAL_FIELDS = true
+
+val STAR_POINTS = android.os.Build.VERSION.SDK_INT.takeIf { it in 1..99 } ?: 31
+
+/**
+ * A zoomedDrawScope is one that is scaled, but remembers its zoom level, so you can correct for it
+ * if you want to draw single-pixel lines. Which we do.
+ */
+interface ZoomedDrawScope : DrawScope {
+ val zoom: Float
+}
+
+fun DrawScope.zoom(zoom: Float, block: ZoomedDrawScope.() -> Unit) {
+ val ds =
+ object : ZoomedDrawScope, DrawScope by this {
+ override var zoom = zoom
+ }
+ ds.scale(zoom) { block(ds) }
+}
+
+class VisibleUniverse(namer: Namer, randomSeed: Long) : Universe(namer, randomSeed) {
+ // Magic variable. Every time we update it, Compose will notice and redraw the universe.
+ val triggerDraw = mutableStateOf(0L)
+
+ fun simulateAndDrawFrame(nanos: Long) {
+ // By writing this value, Compose will look for functions that read it (like drawZoomed).
+ triggerDraw.value = nanos
+
+ step(nanos)
+ }
+}
+
+fun ZoomedDrawScope.drawUniverse(universe: VisibleUniverse) {
+ with(universe) {
+ triggerDraw.value // Please recompose when this value changes.
+
+ // star.drawZoomed(ds, zoom)
+ // planets.forEach { p ->
+ // p.drawZoomed(ds, zoom)
+ // if (p == follow) {
+ // drawCircle(Color.Red, 20f / zoom, p.pos)
+ // }
+ // }
+ //
+ // ship.drawZoomed(ds, zoom)
+
+ constraints.forEach {
+ when (it) {
+ is Landing -> drawLanding(it)
+ is Container -> drawContainer(it)
+ }
+ }
+ drawStar(star)
+ entities.forEach {
+ if (it === ship || it === star) return@forEach // draw the ship last
+ when (it) {
+ is Spacecraft -> drawSpacecraft(it)
+ is Spark -> drawSpark(it)
+ is Planet -> drawPlanet(it)
+ }
+ }
+ drawSpacecraft(ship)
+ }
+}
+
+fun ZoomedDrawScope.drawContainer(container: Container) {
+ drawCircle(
+ color = Color(0xFF800000),
+ radius = container.radius,
+ center = Vec2.Zero,
+ style =
+ Stroke(
+ width = 1f / zoom,
+ pathEffect = PathEffect.dashPathEffect(floatArrayOf(8f / zoom, 8f / zoom), 0f)
+ )
+ )
+ // val path = Path().apply {
+ // fillType = PathFillType.EvenOdd
+ // addOval(Rect(center = Vec2.Zero, radius = container.radius))
+ // addOval(Rect(center = Vec2.Zero, radius = container.radius + 10_000))
+ // }
+ // drawPath(
+ // path = path,
+ //
+ // )
+}
+
+fun ZoomedDrawScope.drawGravitationalField(planet: Planet) {
+ val rings = 8
+ for (i in 0 until rings) {
+ val force =
+ lerp(
+ 200f,
+ 0.01f,
+ i.toFloat() / rings
+ ) // first rings at force = 1N, dropping off after that
+ val r = sqrt(GRAVITATION * planet.mass * SPACECRAFT_MASS / force)
+ drawCircle(
+ color = Color(1f, 0f, 0f, lerp(0.5f, 0.1f, i.toFloat() / rings)),
+ center = planet.pos,
+ style = Stroke(2f / zoom),
+ radius = r
+ )
+ }
+}
+
+fun ZoomedDrawScope.drawPlanet(planet: Planet) {
+ with(planet) {
+ if (DRAW_ORBITS)
+ drawCircle(
+ color = Color(0x8000FFFF),
+ radius = pos.distance(orbitCenter),
+ center = orbitCenter,
+ style =
+ Stroke(
+ width = 1f / zoom,
+ )
+ )
+
+ if (DRAW_GRAVITATIONAL_FIELDS) {
+ drawGravitationalField(this)
+ }
+
+ drawCircle(color = Colors.Eigengrau, radius = radius, center = pos)
+ drawCircle(color = color, radius = radius, center = pos, style = Stroke(2f / zoom))
+ }
+}
+
+fun ZoomedDrawScope.drawStar(star: Star) {
+ translate(star.pos.x, star.pos.y) {
+ drawCircle(color = star.color, radius = star.radius, center = Vec2.Zero)
+
+ if (DRAW_STAR_GRAVITATIONAL_FIELDS) this@drawStar.drawGravitationalField(star)
+
+ rotateRad(radians = star.anim / 23f * PI2f, pivot = Vec2.Zero) {
+ drawPath(
+ path =
+ createStar(
+ radius1 = star.radius + 80,
+ radius2 = star.radius + 250,
+ points = STAR_POINTS
+ ),
+ color = star.color,
+ style =
+ Stroke(
+ width = 3f / this@drawStar.zoom,
+ pathEffect = PathEffect.cornerPathEffect(radius = 200f)
+ )
+ )
+ }
+ rotateRad(radians = star.anim / -19f * PI2f, pivot = Vec2.Zero) {
+ drawPath(
+ path =
+ createStar(
+ radius1 = star.radius + 20,
+ radius2 = star.radius + 200,
+ points = STAR_POINTS + 1
+ ),
+ color = star.color,
+ style =
+ Stroke(
+ width = 3f / this@drawStar.zoom,
+ pathEffect = PathEffect.cornerPathEffect(radius = 200f)
+ )
+ )
+ }
+ }
+}
+
+val spaceshipPath =
+ Path().apply {
+ parseSvgPathData(
+ """
+M11.853 0
+C11.853 -4.418 8.374 -8 4.083 -8
+L-5.5 -8
+C-6.328 -8 -7 -7.328 -7 -6.5
+C-7 -5.672 -6.328 -5 -5.5 -5
+L-2.917 -5
+C-1.26 -5 0.083 -3.657 0.083 -2
+L0.083 2
+C0.083 3.657 -1.26 5 -2.917 5
+L-5.5 5
+C-6.328 5 -7 5.672 -7 6.5
+C-7 7.328 -6.328 8 -5.5 8
+L4.083 8
+C8.374 8 11.853 4.418 11.853 0
+Z
+"""
+ )
+ }
+val thrustPath = createPolygon(-3f, 3).also { it.translate(Vec2(-4f, 0f)) }
+
+fun ZoomedDrawScope.drawSpacecraft(ship: Spacecraft) {
+ with(ship) {
+ rotateRad(angle, pivot = pos) {
+ translate(pos.x, pos.y) {
+ // drawPath(
+ // path = createStar(200f, 100f, 3),
+ // color = Color.White,
+ // style = Stroke(width = 2f / zoom)
+ // )
+ drawPath(path = spaceshipPath, color = Colors.Eigengrau) // fauxpaque
+ drawPath(
+ path = spaceshipPath,
+ color = if (transit) Color.Black else Color.White,
+ style = Stroke(width = 2f / this@drawSpacecraft.zoom)
+ )
+ if (thrust != Vec2.Zero) {
+ drawPath(
+ path = thrustPath,
+ color = Color(0xFFFF8800),
+ style =
+ Stroke(
+ width = 2f / this@drawSpacecraft.zoom,
+ pathEffect = PathEffect.cornerPathEffect(radius = 1f)
+ )
+ )
+ }
+ // drawRect(
+ // topLeft = Offset(-1f, -1f),
+ // size = Size(2f, 2f),
+ // color = Color.Cyan,
+ // style = Stroke(width = 2f / zoom)
+ // )
+ // drawLine(
+ // start = Vec2.Zero,
+ // end = Vec2(20f, 0f),
+ // color = Color.Cyan,
+ // strokeWidth = 2f / zoom
+ // )
+ }
+ }
+ // // DEBUG: draw velocity vector
+ // drawLine(
+ // start = pos,
+ // end = pos + velocity,
+ // color = Color.Red,
+ // strokeWidth = 3f / zoom
+ // )
+ drawTrack(track)
+ }
+}
+
+fun ZoomedDrawScope.drawLanding(landing: Landing) {
+ val v = landing.planet.pos + Vec2.makeWithAngleMag(landing.angle, landing.planet.radius)
+ drawLine(Color.Red, v + Vec2(-5f, -5f), v + Vec2(5f, 5f), strokeWidth = 1f / zoom)
+ drawLine(Color.Red, v + Vec2(5f, -5f), v + Vec2(-5f, 5f), strokeWidth = 1f / zoom)
+}
+
+fun ZoomedDrawScope.drawSpark(spark: Spark) {
+ with(spark) {
+ if (lifetime < 0) return
+ when (style) {
+ Spark.Style.LINE ->
+ if (opos != Vec2.Zero) drawLine(color, opos, pos, strokeWidth = size)
+ Spark.Style.LINE_ABSOLUTE ->
+ if (opos != Vec2.Zero) drawLine(color, opos, pos, strokeWidth = size / zoom)
+ Spark.Style.DOT -> drawCircle(color, size, pos)
+ Spark.Style.DOT_ABSOLUTE -> drawCircle(color, size, pos / zoom)
+ Spark.Style.RING -> drawCircle(color, size, pos, style = Stroke(width = 1f / zoom))
+ // drawPoints(listOf(pos), PointMode.Points, color, strokeWidth = 2f/zoom)
+ // drawCircle(color, 2f/zoom, pos)
+ }
+ // drawCircle(Color.Gray, center = pos, radius = 1.5f / zoom)
+ }
+}
+
+fun ZoomedDrawScope.drawTrack(track: Track) {
+ with(track) {
+ if (SIMPLE_TRACK_DRAWING) {
+ drawPoints(
+ positions,
+ pointMode = PointMode.Lines,
+ color = Color.Green,
+ strokeWidth = 1f / zoom
+ )
+ // if (positions.size < 2) return
+ // drawPath(Path()
+ // .apply {
+ // val p = positions[positions.size - 1]
+ // moveTo(p.x, p.y)
+ // positions.reversed().subList(1, positions.size).forEach { p ->
+ // lineTo(p.x, p.y)
+ // }
+ // },
+ // color = Color.Green, style = Stroke(1f/zoom))
+ } else {
+ if (positions.size < 2) return
+ var prev: Vec2 = positions[positions.size - 1]
+ var a = 0.5f
+ positions.reversed().subList(1, positions.size).forEach { pos ->
+ drawLine(Color(0f, 1f, 0f, a), prev, pos, strokeWidth = max(1f, 1f / zoom))
+ prev = pos
+ a = clamp((a - 1f / TRACK_LENGTH), 0f, 1f)
+ }
+ }
+ }
+}
diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml
index 14f9513..ef39eef 100644
--- a/packages/PackageInstaller/res/values-ko/strings.xml
+++ b/packages/PackageInstaller/res/values-ko/strings.xml
@@ -70,7 +70,7 @@
<string name="uninstalling_app" msgid="8866082646836981397">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 제거 중…"</string>
<string name="uninstall_done" msgid="439354138387969269">"제거를 완료했습니다."</string>
<string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>를 제거했습니다."</string>
- <string name="uninstall_done_clone_app" msgid="5578308154544195413">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 클론 삭제됨"</string>
+ <string name="uninstall_done_clone_app" msgid="5578308154544195413">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 복제 삭제됨"</string>
<string name="uninstall_failed" msgid="1847750968168364332">"제거하지 못했습니다."</string>
<string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>을(를) 제거하지 못했습니다."</string>
<string name="uninstalling_cloned_app" msgid="1826380164974984870">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 클론 삭제 중…"</string>
@@ -94,7 +94,7 @@
<string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"휴대전화와 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 휴대전화 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
<string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"태블릿과 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 태블릿 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
<string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"TV와 개인 데이터는 알 수 없는 앱의 공격에 더욱 취약합니다. 이 앱을 설치하면 앱 사용으로 인해 발생할 수 있는 모든 TV 손상이나 데이터 손실에 사용자가 책임을 진다는 것에 동의하게 됩니다."</string>
- <string name="cloned_app_label" msgid="7503612829833756160">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 클론"</string>
+ <string name="cloned_app_label" msgid="7503612829833756160">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 복제"</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"계속"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"설정"</string>
<string name="wear_app_channel" msgid="1960809674709107850">"Wear 앱 설치/제거"</string>
diff --git a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
index ef4ee0d..3e1b837 100644
--- a/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/res/values-ko/strings.xml
@@ -23,5 +23,5 @@
<string name="app_permission_summary_allowed" msgid="6115213465364138103">"허용됨"</string>
<string name="app_permission_summary_not_allowed" msgid="58396132188553920">"허용되지 않음"</string>
<string name="version_text" msgid="4001669804596458577">"버전 <xliff:g id="VERSION_NUM">%1$s</xliff:g>"</string>
- <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 클론"</string>
+ <string name="cloned_app_info_label" msgid="1765651167024478391">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> 복제"</string>
</resources>
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 23e3a01..1a03ede 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -34,7 +34,6 @@
import android.view.ViewGroupOverlay
import android.widget.FrameLayout
import com.android.internal.jank.InteractionJankMonitor
-import java.lang.IllegalArgumentException
import java.util.LinkedList
import kotlin.math.min
import kotlin.math.roundToInt
@@ -240,7 +239,7 @@
val ghostView = this.ghostView ?: return
val backgroundView = this.backgroundView!!
- if (!state.visible) {
+ if (!state.visible || !ghostedView.isAttachedToWindow) {
if (ghostView.visibility == View.VISIBLE) {
// Making the ghost view invisible will make the ghosted view visible, so order is
// important here.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 1e9a466..70b5e75 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -21,6 +21,8 @@
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
+import android.database.ContentObserver
+import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.util.Log
import android.util.MathUtils
@@ -64,6 +66,7 @@
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.animation.requiresRemeasuring
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.traceSection
import java.io.PrintWriter
@@ -105,6 +108,7 @@
private val mediaFlags: MediaFlags,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val globalSettings: GlobalSettings,
) : Dumpable {
/** The current width of the carousel */
var currentCarouselWidth: Int = 0
@@ -169,6 +173,13 @@
private var carouselLocale: Locale? = null
+ private val animationScaleObserver: ContentObserver =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
+ }
+ }
+
/** Whether the media card currently has the "expanded" layout */
@VisibleForTesting
var currentlyExpanded = true
@@ -529,6 +540,12 @@
listenForAnyStateToGoneKeyguardTransition(this)
}
}
+
+ // Notifies all active players about animation scale changes.
+ globalSettings.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ animationScaleObserver
+ )
}
private fun inflateSettingsButton() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 0819d0d..35082fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -34,7 +34,6 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
@@ -252,13 +251,6 @@
private boolean mWasPlaying = false;
private boolean mButtonClicked = false;
- private ContentObserver mAnimationScaleObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- updateAnimatorDurationScale();
- }
- };
-
/**
* Initialize a new control panel
*
@@ -318,10 +310,6 @@
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
- mGlobalSettings.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
- mAnimationScaleObserver
- );
updateAnimatorDurationScale();
}
@@ -405,7 +393,10 @@
updateSeekBarVisibility();
}
- private void updateAnimatorDurationScale() {
+ /**
+ * Reloads animator duration scale.
+ */
+ void updateAnimatorDurationScale() {
if (mSeekBarObserver != null) {
mSeekBarObserver.setAnimationEnabled(
mGlobalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 0a5b4b3..7712690 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -105,6 +105,7 @@
private WallpaperColors mWallpaperColors;
private boolean mShouldLaunchLeBroadcastDialog;
private boolean mIsLeBroadcastCallbackRegistered;
+ private boolean mDismissing;
MediaOutputBaseAdapter mAdapter;
@@ -265,13 +266,22 @@
mDevicesRecyclerView.setHasFixedSize(false);
// Init bottom buttons
mDoneButton.setOnClickListener(v -> dismiss());
- mStopButton.setOnClickListener(v -> {
- mMediaOutputController.releaseSession();
- dismiss();
- });
+ mStopButton.setOnClickListener(v -> onStopButtonClick());
mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication);
mMediaMetadataSectionLayout.setOnClickListener(
mMediaOutputController::tryToLaunchMediaApplication);
+
+ mDismissing = false;
+ }
+
+ @Override
+ public void dismiss() {
+ // TODO(287191450): remove this once expensive binder calls are removed from refresh().
+ // Due to these binder calls on the UI thread, calling refresh() during dismissal causes
+ // significant frame drops for the dismissal animation. Since the dialog is going away
+ // anyway, we use this state to turn refresh() into a no-op.
+ mDismissing = true;
+ super.dismiss();
}
@Override
@@ -299,7 +309,9 @@
}
void refresh(boolean deviceSetChanged) {
- if (mMediaOutputController.isRefreshing()) {
+ // TODO(287191450): remove binder calls in this method from the UI thread.
+ // If the dialog is going away or is already refreshing, do nothing.
+ if (mDismissing || mMediaOutputController.isRefreshing()) {
return;
}
mMediaOutputController.setRefreshing(true);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 19b32e9..f3865f5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -28,6 +28,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
@@ -36,11 +37,14 @@
*/
@SysUISingleton
public class MediaOutputDialog extends MediaOutputBaseDialog {
- final UiEventLogger mUiEventLogger;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
+ private final UiEventLogger mUiEventLogger;
MediaOutputDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController, UiEventLogger uiEventLogger) {
+ MediaOutputController mediaOutputController, DialogLaunchAnimator dialogLaunchAnimator,
+ UiEventLogger uiEventLogger) {
super(context, broadcastSender, mediaOutputController);
+ mDialogLaunchAnimator = dialogLaunchAnimator;
mUiEventLogger = uiEventLogger;
mAdapter = new MediaOutputAdapter(mMediaOutputController);
if (!aboveStatusbar) {
@@ -138,6 +142,7 @@
}
} else {
mMediaOutputController.releaseSession();
+ mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
dismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 8024886..4c168ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -28,11 +28,11 @@
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import java.util.Optional
import javax.inject.Inject
@@ -71,7 +71,8 @@
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
powerExemptionManager, keyGuardManager, featureFlags, userTracker)
val dialog =
- MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
+ MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller,
+ dialogLaunchAnimator, uiEventLogger)
mediaOutputDialog = dialog
// Show the dialog.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 2aff90c..5b8272b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -19,7 +19,9 @@
import android.app.PendingIntent
import android.content.res.ColorStateList
import android.content.res.Configuration
+import android.database.ContentObserver
import android.os.LocaleList
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
@@ -56,6 +58,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import java.util.Locale
import javax.inject.Provider
@@ -113,6 +116,7 @@
@Mock lateinit var mediaFlags: MediaFlags
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock lateinit var globalSettings: GlobalSettings
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
@@ -120,6 +124,7 @@
@Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
@Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@Captor lateinit var hostStateCallback: ArgumentCaptor<MediaHostStatesManager.Callback>
+ @Captor lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
private val clock = FakeSystemClock()
private lateinit var mediaCarouselController: MediaCarouselController
@@ -148,6 +153,7 @@
mediaFlags,
keyguardUpdateMonitor,
KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+ globalSettings
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
@@ -160,6 +166,11 @@
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
MediaPlayerData.clear()
+ verify(globalSettings)
+ .registerContentObserver(
+ eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
+ settingsObserverCaptor.capture()
+ )
}
@Test
@@ -873,6 +884,15 @@
assertTrue(stateUpdated)
}
+ @Test
+ fun testAnimationScaleChanged_mediaControlPanelsNotified() {
+ MediaPlayerData.addMediaPlayer("key", DATA, panel, clock, isSsReactivated = false)
+
+ globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
+ settingsObserverCaptor.value!!.onChange(false)
+ verify(panel).updateAnimatorDurationScale()
+ }
+
/**
* Helper method when a configuration change occurs.
*
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f6075ad..f902be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -25,7 +25,6 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
-import android.database.ContentObserver
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
@@ -113,7 +112,6 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
@@ -239,7 +237,6 @@
this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@Mock private lateinit var globalSettings: GlobalSettings
- @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -281,7 +278,7 @@
lockscreenUserManager,
broadcastDialogController,
fakeFeatureFlag,
- globalSettings,
+ globalSettings
) {
override fun loadAnimator(
animId: Int,
@@ -292,12 +289,6 @@
}
}
- verify(globalSettings)
- .registerContentObserver(
- eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
- settingsObserverCaptor.capture()
- )
-
initGutsViewHolderMocks()
initMediaViewHolderMocks()
@@ -986,7 +977,7 @@
// When the setting changes,
globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
- settingsObserverCaptor.value!!.onChange(false)
+ player.updateAnimatorDurationScale()
// Then the seekbar is set to not animate
assertThat(seekBarObserver.animationEnabled).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index f3aee48..a14ff2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -356,6 +356,7 @@
});
verify(mockMediaOutputController).releaseSession();
+ verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations();
}
@Test
@@ -371,7 +372,7 @@
@NonNull
private MediaOutputDialog makeTestDialog(MediaOutputController controller) {
return new MediaOutputDialog(mContext, false, mBroadcastSender,
- controller, mUiEventLogger);
+ controller, mDialogLaunchAnimator, mUiEventLogger);
}
private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a1ccade..611541f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -64,6 +64,7 @@
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
+import android.companion.utils.FeatureUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -746,6 +747,11 @@
@Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
+ if (!FeatureUtils.isPermSyncEnabled()) {
+ throw new UnsupportedOperationException("Calling"
+ + " buildPermissionTransferUserConsentIntent, but this API is disabled by"
+ + " the system.");
+ }
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
packageName, userId, associationId);
}
@@ -753,6 +759,10 @@
@Override
public void startSystemDataTransfer(String packageName, int userId, int associationId,
ISystemDataTransferCallback callback) {
+ if (!FeatureUtils.isPermSyncEnabled()) {
+ throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this"
+ + " API is disabled by the system.");
+ }
mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId,
associationId, callback);
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 5a3db4b..3cb9ac8 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -130,6 +130,7 @@
if (DEBUG) {
Slog.d(TAG, "Starting secure channel.");
}
+ mStopped = false;
new Thread(() -> {
try {
// 1. Wait for the next handshake message and process it.
@@ -185,6 +186,17 @@
}
/**
+ * Return true if the channel is currently inactive.
+ * The channel could have been stopped by either {@link SecureChannel#stop()} or by
+ * encountering a fatal error.
+ *
+ * @return true if the channel is currently inactive.
+ */
+ public boolean isStopped() {
+ return mStopped;
+ }
+
+ /**
* Start exchanging handshakes to create a secure layer asynchronously. When the handshake is
* completed successfully, then the {@link Callback#onSecureConnection()} will trigger. Any
* error that occurs during the handshake will be passed by {@link Callback#onError(Throwable)}.
@@ -290,6 +302,7 @@
try {
data = new byte[length];
} catch (OutOfMemoryError error) {
+ Streams.skipByReading(mInput, Long.MAX_VALUE);
throw new SecureChannelException("Payload is too large.", error);
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 0f00f5f..a49021a 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -22,14 +22,10 @@
import android.annotation.NonNull;
import android.annotation.SuppressLint;
-import android.app.ActivityManagerInternal;
import android.companion.AssociationInfo;
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Binder;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
@@ -38,7 +34,6 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
import com.android.server.companion.AssociationStore;
import java.io.FileDescriptor;
@@ -137,44 +132,15 @@
synchronized (mTransports) {
for (int i = 0; i < associationIds.length; i++) {
if (mTransports.contains(associationIds[i])) {
- try {
- mTransports.get(associationIds[i]).sendMessage(message, data);
- } catch (IOException e) {
- Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message)
- + " data length " + data.length + " to association "
- + associationIds[i]);
- }
+ mTransports.get(associationIds[i]).requestForResponse(message, data);
}
}
}
}
- /**
- * For the moment, we only offer transporting of system data to built-in
- * companion apps; future work will improve the security model to support
- * third-party companion apps.
- */
- private void enforceCallerCanTransportSystemData(String packageName, int userId) {
- mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
-
- try {
- final ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
- packageName, 0, userId);
- final int instrumentationUid = LocalServices.getService(ActivityManagerInternal.class)
- .getInstrumentationSourceUid(Binder.getCallingUid());
- if (!Build.isDebuggable() && !info.isSystemApp()
- && instrumentationUid == android.os.Process.INVALID_UID) {
- throw new SecurityException("Transporting of system data currently only available "
- + "to built-in companion apps or tests");
- }
- } catch (NameNotFoundException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
public void attachSystemDataTransport(String packageName, int userId, int associationId,
ParcelFileDescriptor fd) {
- enforceCallerCanTransportSystemData(packageName, userId);
+ mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
detachSystemDataTransport(packageName, userId, associationId);
@@ -188,7 +154,7 @@
}
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
- enforceCallerCanTransportSystemData(packageName, userId);
+ mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
synchronized (mTransports) {
final Transport transport = mTransports.get(associationId);
if (transport != null) {
@@ -244,6 +210,7 @@
}
addMessageListenersToTransport(transport);
+ transport.setOnTransportClosedListener(this::detachSystemDataTransport);
transport.start();
synchronized (mTransports) {
mTransports.put(associationId, transport);
@@ -321,4 +288,14 @@
transport.addListener(mMessageListeners.keyAt(i), mMessageListeners.valueAt(i));
}
}
+
+ void detachSystemDataTransport(Transport transport) {
+ int associationId = transport.mAssociationId;
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (association != null) {
+ detachSystemDataTransport(association.getPackageName(),
+ association.getUserId(),
+ association.getId());
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
index e64509f..ca169aac 100644
--- a/services/companion/java/com/android/server/companion/transport/RawTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -70,6 +70,8 @@
}
IoUtils.closeQuietly(mRemoteIn);
IoUtils.closeQuietly(mRemoteOut);
+
+ super.close();
}
@Override
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 2d856b9..a0301a9 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -21,7 +21,6 @@
import android.os.ParcelFileDescriptor;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.securechannel.AttestationVerifier;
import com.android.server.companion.securechannel.SecureChannel;
@@ -35,7 +34,6 @@
private volatile boolean mShouldProcessRequests = false;
- @GuardedBy("mRequestQueue")
private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
@@ -64,6 +62,8 @@
void close() {
mSecureChannel.close();
mShouldProcessRequests = false;
+
+ super.close();
}
@Override
@@ -81,13 +81,19 @@
}
// Queue up a message to send
- synchronized (mRequestQueue) {
+ try {
mRequestQueue.add(ByteBuffer.allocate(HEADER_LENGTH + data.length)
.putInt(message)
.putInt(sequence)
.putInt(data.length)
.put(data)
.array());
+ } catch (IllegalStateException e) {
+ // Request buffer can only be full if too many requests are being added or
+ // the request processing thread is dead. Assume latter and detach the transport.
+ Slog.w(TAG, "Failed to queue message 0x" + Integer.toHexString(message)
+ + " . Request buffer is full; detaching transport.", e);
+ close();
}
}
@@ -96,8 +102,8 @@
try {
mSecureChannel.establishSecureConnection();
} catch (Exception e) {
- Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
- onError(e);
+ Slog.e(TAG, "Failed to initiate secure channel handshake.", e);
+ close();
}
}
@@ -108,17 +114,14 @@
// TODO: find a better way to handle incoming requests than a dedicated thread.
new Thread(() -> {
- try {
- while (mShouldProcessRequests) {
- synchronized (mRequestQueue) {
- byte[] request = mRequestQueue.poll();
- if (request != null) {
- mSecureChannel.sendSecureMessage(request);
- }
- }
+ while (mShouldProcessRequests) {
+ try {
+ byte[] request = mRequestQueue.take();
+ mSecureChannel.sendSecureMessage(request);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to send secure message.", e);
+ close();
}
- } catch (IOException e) {
- onError(e);
}
}).start();
}
@@ -135,13 +138,18 @@
try {
handleMessage(message, sequence, content);
} catch (IOException error) {
- onError(error);
+ // IOException won't be thrown here because a separate thread is handling
+ // the write operations inside onSecureConnection().
}
}
@Override
public void onError(Throwable error) {
- mShouldProcessRequests = false;
- Slog.e(TAG, error.getMessage(), error);
+ Slog.e(TAG, "Secure transport encountered an error.", error);
+
+ // If the channel was stopped as a result of the error, then detach itself.
+ if (mSecureChannel.isStopped()) {
+ close();
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 6ad6d3a..5af3b98 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.companion.IOnMessageReceivedListener;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -70,6 +69,8 @@
*/
private final Map<Integer, IOnMessageReceivedListener> mListeners;
+ private OnTransportClosedListener mOnTransportClosed;
+
private static boolean isRequest(int message) {
return (message & 0xFF000000) == 0x63000000;
}
@@ -120,20 +121,18 @@
abstract void stop();
/**
- * Stop listening to the incoming data and close the streams.
+ * Stop listening to the incoming data and close the streams. If a listener for closed event
+ * is set, then trigger it to assist with its clean-up.
*/
- abstract void close();
+ void close() {
+ if (mOnTransportClosed != null) {
+ mOnTransportClosed.onClosed(this);
+ }
+ }
protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException;
- /**
- * Send a message.
- */
- public void sendMessage(int message, @NonNull byte[] data) throws IOException {
- sendMessage(message, mNextSequence.incrementAndGet(), data);
- }
-
public Future<byte[]> requestForResponse(int message, byte[] data) {
if (DEBUG) Slog.d(TAG, "Requesting for response");
final int sequence = mNextSequence.incrementAndGet();
@@ -188,12 +187,6 @@
break;
}
case MESSAGE_REQUEST_PERMISSION_RESTORE: {
- if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
- && !Build.isDebuggable()) {
- Slog.w(TAG, "Restoring permissions only supported on watches");
- sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
- break;
- }
try {
callback(message, data);
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
@@ -247,4 +240,14 @@
}
}
}
+
+ void setOnTransportClosedListener(OnTransportClosedListener callback) {
+ this.mOnTransportClosed = callback;
+ }
+
+ // Interface to pass transport to the transport manager to assist with detachment.
+ @FunctionalInterface
+ interface OnTransportClosedListener {
+ void onClosed(Transport transport);
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 47f6485..9185a00 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
import static android.media.VolumeProvider.VOLUME_CONTROL_FIXED;
import static android.media.VolumeProvider.VOLUME_CONTROL_RELATIVE;
@@ -44,7 +45,9 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaMetadata;
+import android.media.MediaRouter2Manager;
import android.media.Rating;
+import android.media.RoutingSessionInfo;
import android.media.VolumeProvider;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -510,7 +513,33 @@
@Override
public boolean canHandleVolumeKey() {
- return mVolumeControlType != VOLUME_CONTROL_FIXED;
+ if (isPlaybackTypeLocal()) {
+ return true;
+ }
+ if (mVolumeControlType == VOLUME_CONTROL_FIXED) {
+ return false;
+ }
+ if (mVolumeAdjustmentForRemoteGroupSessions) {
+ return true;
+ }
+ // See b/228021646 for details.
+ MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext);
+ List<RoutingSessionInfo> sessions = mRouter2Manager.getRoutingSessions(mPackageName);
+ boolean foundNonSystemSession = false;
+ boolean remoteSessionAllowVolumeAdjustment = true;
+ for (RoutingSessionInfo session : sessions) {
+ if (!session.isSystemSession()) {
+ foundNonSystemSession = true;
+ if (session.getVolumeHandling() == PLAYBACK_VOLUME_FIXED) {
+ remoteSessionAllowVolumeAdjustment = false;
+ }
+ }
+ }
+ if (!foundNonSystemSession) {
+ Log.d(TAG, "Package " + mPackageName
+ + " has a remote media session but no associated routing session");
+ }
+ return foundNonSystemSession && remoteSessionAllowVolumeAdjustment;
}
@Override
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3d9edca..5f2d3ca8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -979,7 +979,7 @@
}
if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
- if (didCommitTransientLaunch()) {
+ if (!ar.getTask().isVisibleRequested() || didCommitTransientLaunch()) {
// force enable pip-on-task-switch now that we've committed to actually launching
// to the transient activity.
ar.supportsEnterPipOnTaskSwitch = true;
@@ -1008,7 +1008,8 @@
}
// Legacy pip-entry (not via isAutoEnterEnabled).
- if (didCommitTransientLaunch() && ar.supportsPictureInPicture()) {
+ if ((!ar.getTask().isVisibleRequested() || didCommitTransientLaunch())
+ && ar.supportsPictureInPicture()) {
// force enable pip-on-task-switch now that we've committed to actually launching to the
// transient activity, and then recalculate whether we can attempt pip.
ar.supportsEnterPipOnTaskSwitch = true;
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index aee4f58..c9e691e 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -28,6 +28,7 @@
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
@@ -98,8 +99,9 @@
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
- cancelExistingPendingIntent();
+ Binder.withCleanCallingIdentity(()-> {
try {
+ cancelExistingPendingIntent();
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
@@ -112,9 +114,9 @@
mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
String exception = GetCredentialException.TYPE_UNKNOWN;
mRequestSessionMetric.collectFrameworkException(exception);
- respondToClientWithErrorAndFinish(
- exception, "Unable to instantiate selector");
- }
+ respondToClientWithErrorAndFinish(exception, "Unable to instantiate selector");
+ }
+ });
}
@Override
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index b0b72bc..46c90b4 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -38,11 +38,13 @@
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -116,7 +118,7 @@
@NonNull String servicePackageName,
@NonNull CredentialOption requestOption) {
super(context, requestOption, session,
- new ComponentName(servicePackageName, servicePackageName),
+ new ComponentName(servicePackageName, UUID.randomUUID().toString()),
userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
@@ -133,7 +135,7 @@
@NonNull String servicePackageName,
@NonNull CredentialOption requestOption) {
super(context, requestOption, session,
- new ComponentName(servicePackageName, servicePackageName),
+ new ComponentName(servicePackageName, UUID.randomUUID().toString()),
userId, null);
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
@@ -179,7 +181,9 @@
return null;
}
return new GetCredentialProviderData.Builder(
- mComponentName.flattenToString()).setActionChips(null)
+ mComponentName.flattenToString())
+ .setActionChips(Collections.EMPTY_LIST)
+ .setAuthenticationEntries(Collections.EMPTY_LIST)
.setCredentialEntries(prepareUiCredentialEntries(
mProviderResponse.stream().flatMap((Function<CredentialDescriptionRegistry
.FilterResult,
@@ -261,12 +265,12 @@
.getFilteredResultForProvider(mCredentialProviderPackageName,
mElementKeys);
mCredentialEntries = mProviderResponse.stream().flatMap(
- (Function<CredentialDescriptionRegistry.FilterResult,
- Stream<CredentialEntry>>) filterResult
- -> filterResult.mCredentialEntries.stream())
- .collect(Collectors.toList());
+ (Function<CredentialDescriptionRegistry.FilterResult,
+ Stream<CredentialEntry>>)
+ filterResult -> filterResult.mCredentialEntries.stream())
+ .collect(Collectors.toList());
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
- /*source=*/ CredentialsSource.REGISTRY);
+ /*source=*/ CredentialsSource.REGISTRY);
mProviderSessionMetric.collectCandidateEntryMetrics(mCredentialEntries);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index e65229f..1ba1462 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -93,8 +93,6 @@
@Test
public void testAtraceBinaryState1() {
- mHistory.forceRecordAllHistory();
-
InOrder inOrder = Mockito.inOrder(mTracer);
Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
@@ -112,8 +110,6 @@
@Test
public void testAtraceBinaryState2() {
- mHistory.forceRecordAllHistory();
-
InOrder inOrder = Mockito.inOrder(mTracer);
Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
@@ -131,8 +127,6 @@
@Test
public void testAtraceNumericalState() {
- mHistory.forceRecordAllHistory();
-
InOrder inOrder = Mockito.inOrder(mTracer);
Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
@@ -150,8 +144,6 @@
@Test
public void testAtraceInstantEvent() {
- mHistory.forceRecordAllHistory();
-
InOrder inOrder = Mockito.inOrder(mTracer);
Mockito.when(mTracer.tracingEnabled()).thenReturn(true);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 26590c4..a72f780 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2097,7 +2097,10 @@
* For a self-managed {@link ConnectionService}, a {@link SecurityException} will be thrown if
* the {@link PhoneAccount} has {@link PhoneAccount#CAPABILITY_SELF_MANAGED} and the calling app
* does not have {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
- *
+ * <p>
+ * <p>
+ * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
+ * the call is added to Telecom in order for the notification to be non-dismissible.
* @param phoneAccount A {@link PhoneAccountHandle} registered with
* {@link #registerPhoneAccount}.
* @param extras A bundle that will be passed through to
@@ -2345,7 +2348,10 @@
* {@link PhoneAccount} with the {@link PhoneAccount#CAPABILITY_PLACE_EMERGENCY_CALLS}
* capability, depending on external factors, such as network conditions and Modem/SIM status.
* </p>
- *
+ * <p>
+ * <p>
+ * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
+ * the call is placed in order for the notification to be non-dismissible.
* @param address The address to make the call to.
* @param extras Bundle of extras to use with the call.
*/
@@ -2679,9 +2685,11 @@
/**
* Add a call to the Android system service Telecom. This allows the system to start tracking an
- * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
- * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
- * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
+ * incoming or outgoing call with the specified {@link CallAttributes}. Once a call is added,
+ * a {@link android.app.Notification.CallStyle} notification should be posted and when the
+ * call is ready to be disconnected, use {@link CallControl#disconnect(DisconnectCause,
+ * Executor, OutcomeReceiver)} which is provided by the
+ * {@code pendingControl#onResult(CallControl)}.
* <p>
* <p>
* <p>