diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cd6df6f05d..5c4613e2873 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,34 +6,35 @@ -First and foremost, welcome to the **Lawnchair** Contributing Guidelines! +Welcome to the **Lawnchair** Project, we appreciate your interest in contributing. +Feel free to reach out to us on [Telegram](https://t.me/lccommunity) or [Discord](https://discord.com/invite/3x8qNWxgGZ) for any further questions. ### 💫 Bug reports & feature requests > [!TIP] -> We recommend that the Lawnchair [Nightly](https://github.com/LawnchairLauncher/lawnchair/releases/tag/nightly) -> build be used in your report as they contain the latest changes from Lawnchair. +> Using Lawnchair [Nightly](https://github.com/LawnchairLauncher/lawnchair/releases/tag/nightly) is recommended for bug reports and feature requests +> as it contains the latest changes from Lawnchair. -For [bug reports](https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=bug&projects=&template=bug_report.yaml&title=%5BBUG%5D+), please describe the bug in detail to the best of your ability +For [bug reports](https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=bug&projects=&template=bug_report.yaml&title=%5BBUG%5D+), describe the bug in detail to the best of your ability with steps on how to reproduce it. If applicable, attach log files. -For [feature requests](https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=feature%2Cenhancement&projects=&template=feature_request.yaml&title=%5BFEATURE%5D+), please describe the feature you'd like to see added to Lawnchair. +For [feature requests](https://github.com/LawnchairLauncher/lawnchair/issues/new?assignees=&labels=feature%2Cenhancement&projects=&template=feature_request.yaml&title=%5BFEATURE%5D+), describe the feature you'd like to see added to Lawnchair. If applicable, attach log files. -Please make sure to be civil during the discussion of your contribution as outlined by the [Code of Conduct](CODE_OF_CONDUCT.md). +Make sure to be civil during the discussion of your contribution as outlined by the [Code of Conduct](CODE_OF_CONDUCT.md). ### 🌐 Translation For translation, visit [Lawnchair on Crowdin](https://lawnchair.crowdin.com) and follow these tips: -- When using quotation marks, insert the symbols specific to the target language, as listed in this [this summary table](https://en.wikipedia.org/wiki/Quotation_mark#Summary_table); +- When using quotation marks, insert the symbols specific to the target language, as listed in this [summary table](https://en.wikipedia.org/wiki/Quotation_mark#Summary_table); - Some English terminology may have not have commonly-used equivalents in other languages. In such cases, use short descriptive phrases—for example, the equivalent of _bottom row_ for _dock_; - Some languages (e.g. French) have variations of words depending on if it's masculine or feminine (gender-specific); we recommend opting for gender-neutral words instead. ### 🧑‍💻 Code -> [!NOTE] -> For Lawnchair 9 to 13, see the branches with the `9-` to `13-` prefixes respectively. +> [!TIP] +> For Lawnchair 9 to 14, see the branches with the `9-` to `14-` prefixes respectively. For code, it's highly recommended that you use [Android Studio](https://developer.android.com/studio), know [Java](https://www.java.com) or preferably [Kotlin](https://kotlinlang.org/), and [Git](https://git-scm.com/). @@ -51,19 +52,20 @@ run `git submodule update --init --recursive`. Here are some contribution tips to help you get started: -- Always make sure that you're up-to-date with **Lawnchair** by setting your base branch to `14-dev`. +- Always make sure that you're up-to-date with **Lawnchair** by setting your base branch to `15-dev`. - Make sure your code is logical and well-formatted. If using Kotlin, see [“Coding conventions” in the Kotlin documentation](https://kotlinlang.org/docs/coding-conventions.html); -- [The `lawnchair` package](https://github.com/LawnchairLauncher/lawnchair/tree/14-dev/lawnchair) houses Lawnchair’s own code, whereas [the `src` package](https://github.com/LawnchairLauncher/lawnchair/tree/14-dev/src) includes a clone of the Launcher3 codebase with modifications. Generally, place new files in the former, keeping changes to the latter to a minimum. +- [The `lawnchair` package](https://github.com/LawnchairLauncher/lawnchair/tree/15-dev/lawnchair) houses Lawnchair’s own code, whereas [the `src` package](https://github.com/LawnchairLauncher/lawnchair/tree/15-dev/src) includes a clone of the Launcher3 codebase with modifications. Generally, place new files in the former, keeping changes to the latter to a minimum. #### Additional documentation -- The Lawnchair Wiki ([at Github](https://github.com/LawnchairLauncher/lawnchair/wiki)) -- Lawnchair Visual Guidelines ([README.md](/docs/assets/README.md)) -- Lawnchair Quickstep Compat Lib ([README.md](compatLib/README.md)) -- Lawnchair Preferences Components ([README.md](lawnchair/src/app/lawnchair/ui/preferences/components/README.md)) -- SystemUI ViewCapture ([README.md](systemUIViewCapture/README.md)) -- SystemUI Common ([README.md](systemUICommon/README.md)) -- Prebuilt Library ([README.md](prebuilts/libs/README.md)) +- [The Lawnchair Wiki](https://github.com/LawnchairLauncher/lawnchair/wiki) +- [Lawnchair Visual Guidelines](/docs/assets/README.md) +- [Lawnchair Quickstep Compat Library](compatLib/README.md) +- [Lawnchair Preferences Components](lawnchair/src/app/lawnchair/ui/preferences/components/README.md) +- [SystemUI Module](systemUI/README.md) + - [ViewCapture](systemUI/viewcapture/README.md) + - [Common](systemUI/common/README.md) +- [Prebuilt Library](prebuilts/libs/README.md) #### Versioning scheme @@ -105,6 +107,7 @@ The table below shows release phase used by Lawnchair: | Status | Stage | | ----------------- | ----- | +| Development | 00 | | Alpha | 01 | | Beta | 02 | | Release Candidate | 03 | @@ -125,3 +128,12 @@ Strings `names` in `strings.xml` should follow this format: | Preference choice | $1_choice | `off_choice` | Off | | | Feature string | (feature_name)_$1 | `colorpicker_hsb` | HSB | Feature strings are strings that are confined to a specific feature. Examples include the gesture and color picker. | | Launcher string | $1_launcher | `device_contacts_launcher` | Contacts from device | Strings that are specific to the Launcher area | + +#### Updating locally stored font listing + +Lawnchair uses a locally stored JSON file (`google_fonts.json`) to list available fonts from Google Fonts. This file should be updated periodically or before release to include the latest fonts. + +To update Lawnchair's font listing, follow these steps: +1. Acquire a [Google Fonts Developer API key](https://developers.google.com/fonts/docs/developer_api#APIKey). +2. Download the JSON file from `https://www.googleapis.com/webfonts/v1/webfonts?key=API_KEY`, replacing `API_KEY` with the API key from step 1. +3. Replace the content of [`google_fonts.json`](lawnchair/assets/google_fonts.json) with the API response. diff --git a/SECURITY.md b/SECURITY.md index 430694c29c8..0e9c46f1719 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,7 +3,7 @@ ## Supported Versions > [!WARNING] -> Lawnchair v2 (Play Store version) is **unsupported**. Use the newer versions instead. +> [Lawnchair Legacy](https://play.google.com/store/apps/details?id=ch.deletescape.lawnchair.plah) is **unsupported**. Use the newer versions instead. > See [this FAQ page](https://lawnchair.app/faq#do-you-still-support-the-play-store-version) for additional information. The latest version of Lawnchair is the only supported version. @@ -11,12 +11,13 @@ The latest version of Lawnchair is the only supported version. | Version | Supported | | -------------- | ------------------ | | Nightly build | :white_check_mark: | -| 14 | :white_check_mark: | +| 15 | :white_check_mark: | +| 14 | :x: | | 13 | :x: | | 12.1 | :x: | | 12 | :x: | | 11 | :x: | -| Older (legacy) | :x: | +| Older (Legacy) | :x: | ## Reporting Security issues @@ -24,7 +25,7 @@ We appreciate your efforts to responsibly disclose your findings and will make e acknowledge your contributions. To report an issue, please file a [security advisory](https://github.com/LawnchairLauncher/lawnchair/security/advisories/new) -or contact a developer (can be found in the about page of the app) in Telegram or Discord and -state your security vulnerability starting with the words "**SECURITY**". +or contact a developer (can be found in the about page of the app) in [Telegram](https://t.me/lccommunity) or [Discord](https://discord.com/invite/3x8qNWxgGZ) and +state your security vulnerability starting with the words "**SECURITY**" -We'll endeavour to respond quickly, and will keep you updated throughout the process. +We'll endeavor to respond quickly, and will keep you updated throughout the process. diff --git a/build.gradle b/build.gradle index e75fd115749..af48364c9e1 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ allprojects { } plugins.withId('com.google.protobuf') { - def protocVersion = '4.29.1' + def protocVersion = '4.29.2' protobuf { // Configure the protoc executable protoc { @@ -78,7 +78,7 @@ allprojects { ext { FRAMEWORK_PREBUILTS_DIR = "$rootDir/prebuilts/libs" - daggerVersion = '2.53.1' + daggerVersion = '2.54' addFrameworkJar = { String name -> def frameworkJar = new File(FRAMEWORK_PREBUILTS_DIR, name) @@ -335,7 +335,7 @@ dependencies { implementation fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'WindowManager-Shell-15.jar') withQuickstepCompileOnly fileTree(dir: FRAMEWORK_PREBUILTS_DIR, include: 'framework-15.jar') - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' implementation 'androidx.profileinstaller:profileinstaller:1.4.1' baselineProfile projects.baselineProfile @@ -343,7 +343,7 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.3.2" implementation "androidx.preference:preference-ktx:1.2.1" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3' implementation 'com.github.ChickenHook:RestrictionBypass:2.2' implementation 'dev.rikka.tools.refine:runtime:4.4.0' @@ -394,7 +394,7 @@ dependencies { // Persian Date implementation 'com.github.samanzamani:PersianDate:1.7.1' - implementation 'com.airbnb.android:lottie:6.6.1' + implementation 'com.airbnb.android:lottie:6.6.2' // Compose drag and drop library implementation 'sh.calvin.reorderable:reorderable:2.4.2' @@ -404,7 +404,7 @@ dependencies { exclude group: "com.github.skydoves", module: "balloon" } - implementation("com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3") + implementation 'io.github.hokofly:hoko-blur:1.5.3' } ksp { diff --git a/ci.py b/ci.py index 8668b6775cb..8a2d6c19177 100644 --- a/ci.py +++ b/ci.py @@ -38,14 +38,34 @@ def send_document_to_telegram_chat(chat_id, document): def send_artifact_to_telegram_chat(chat_id): subdirectories = os.listdir(artifact_directory) if "beta" in github_ref: - with open(f"{artifact_directory}/{os.listdir(artifact_directory)[0]}", "rb") as artifact: - send_document_to_telegram_chat(chat_id=chat_id, document=artifact) - return - + beta_path = f"{artifact_directory}/{os.listdir(artifact_directory)[0]}" + if os.path.exists(beta_path) and os.path.isfile(beta_path): + with open(beta_path, "rb") as artifact: + send_document_to_telegram_chat(chat_id=chat_id, document=artifact) + else: + print(f"Error: Beta artifact not found at {beta_path}") + return + for each_directory in subdirectories: full_path = f"{artifact_directory}/{each_directory}/debug" - with open(f"{full_path}/{os.listdir(full_path)[0]}", "rb") as artifact: - send_document_to_telegram_chat(chat_id=chat_id, document=artifact) + if not os.path.exists(full_path) or not os.path.isdir(full_path): + print(f"Warning: Directory does not exist or is not valid: {full_path}") + continue + + try: + files = os.listdir(full_path) + if not files: + print(f"Warning: No files found in {full_path}") + continue + + file_path = f"{full_path}/{files[0]}" + if os.path.isfile(file_path): + with open(file_path, "rb") as artifact: + send_document_to_telegram_chat(chat_id=chat_id, document=artifact) + else: + print(f"Warning: Not a file: {file_path}") + except Exception as e: + print(f"Error processing directory {full_path}: {e}") def send_internal_notifications(): repository = git.Repo(".") diff --git a/compatLib/README.md b/compatLib/README.md index d7e55b3a3b9..2698ccba017 100644 --- a/compatLib/README.md +++ b/compatLib/README.md @@ -12,3 +12,4 @@ refers to the compatibility code for that specific Android version. | compatLibVS | 12 | | compatLibVT | 13 | | compatLibVU | 14 | +| compatLibVV | 15 | diff --git a/docs/assets/badge-github.png b/docs/assets/badge-github.png new file mode 100644 index 00000000000..5129ff38b71 Binary files /dev/null and b/docs/assets/badge-github.png differ diff --git a/docs/assets/badge-google-play.png b/docs/assets/badge-google-play.png new file mode 100644 index 00000000000..1d17b6aa53a Binary files /dev/null and b/docs/assets/badge-google-play.png differ diff --git a/docs/assets/badge-izzyondroid.png b/docs/assets/badge-izzyondroid.png new file mode 100644 index 00000000000..c97854de0b0 Binary files /dev/null and b/docs/assets/badge-izzyondroid.png differ diff --git a/docs/assets/badge-obtainium.png b/docs/assets/badge-obtainium.png new file mode 100644 index 00000000000..c5cee26fca8 Binary files /dev/null and b/docs/assets/badge-obtainium.png differ diff --git a/docs/assets/device-frame.png b/docs/assets/device-frame.png index ad5ec6dcea5..46a4d3fa126 100644 Binary files a/docs/assets/device-frame.png and b/docs/assets/device-frame.png differ diff --git a/flags/src/com/android/launcher3/FeatureFlagsImpl.java b/flags/src/com/android/launcher3/FeatureFlagsImpl.java index af84a73063a..2ce8b5ad2dc 100644 --- a/flags/src/com/android/launcher3/FeatureFlagsImpl.java +++ b/flags/src/com/android/launcher3/FeatureFlagsImpl.java @@ -107,7 +107,7 @@ private void load_overrides_launcher() { enableSmartspaceAsAWidget = properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_AS_A_WIDGET, false); enableSmartspaceRemovalToggle = - properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, false); + properties.getBoolean(Flags.FLAG_ENABLE_SMARTSPACE_REMOVAL_TOGGLE, true); enableSupportForArchiving = properties.getBoolean(Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, true); enableTabletTwoPanePickerV2 = diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index eb1a55be0e1..e1b837a19c2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6b1..f3b75f3b0d4 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/lawnchair/res/layout/search_container_all_apps.xml b/lawnchair/res/layout/search_container_all_apps.xml index 9966792c6dc..b42573bd2e7 100644 --- a/lawnchair/res/layout/search_container_all_apps.xml +++ b/lawnchair/res/layout/search_container_all_apps.xml @@ -26,7 +26,7 @@ android:id="@+id/search_wrapper" android:layout_width="match_parent" android:layout_height="@dimen/search_box_height" - android:layout_gravity="bottom" + android:layout_gravity="center" android:background="@drawable/search_input_fg" android:clickable="false" android:focusable="true" @@ -72,7 +72,7 @@ android:id="@+id/search_icon" android:layout_width="@dimen/search_box_height" android:layout_height="@dimen/search_box_height" - android:layout_gravity="bottom|center|start" + android:layout_gravity="center|start" android:background="@drawable/pill_ripple" android:layout_marginEnd="6dp" android:clickable="true" @@ -84,30 +84,33 @@ diff --git a/lawnchair/res/values-el-rGR/strings.xml b/lawnchair/res/values-el-rGR/strings.xml index 81a05f3e87d..ad10bd6cb0b 100644 --- a/lawnchair/res/values-el-rGR/strings.xml +++ b/lawnchair/res/values-el-rGR/strings.xml @@ -67,7 +67,7 @@ Αδιαφάνεια φόντου Αντιγράφηκε στο πρόχειρο - Result copied to clipboard + Το αποτέλεσμα αντιγράφηκε στο πρόχειρο Το στοιχείο αφαιρέθηκε Τι να εμφανίζεται @@ -187,7 +187,7 @@ Πακέτο εικονιδίων Πηγή θεματοποιημένων εικονιδίων - Tint with accent color + Απόχρωση με χρώμα έμφασης Εικονίδια συστήματος Θεματοποιημένα εικονίδια Ανενεργό diff --git a/lawnchair/res/values-en-rCA/strings.xml b/lawnchair/res/values-en-rCA/strings.xml index 1581d2877d7..41eac2e41f9 100644 --- a/lawnchair/res/values-en-rCA/strings.xml +++ b/lawnchair/res/values-en-rCA/strings.xml @@ -21,9 +21,9 @@ Actions and Verbs --> - Preview + Solanvip1 Create - Backup + Restore Delete Reset diff --git a/lawnchair/res/values-pl-rPL/strings.xml b/lawnchair/res/values-pl-rPL/strings.xml index 946a040662b..4f8f6f7907b 100644 --- a/lawnchair/res/values-pl-rPL/strings.xml +++ b/lawnchair/res/values-pl-rPL/strings.xml @@ -67,7 +67,7 @@ Przezroczystość tła Skopiowano do schowka - Result copied to clipboard + Wynik został skopiowany do schowka Element usunięty Co wyświetlić diff --git a/lawnchair/res/values-pt-rBR/strings.xml b/lawnchair/res/values-pt-rBR/strings.xml index e5bb37058d1..fee8bfc6e4d 100644 --- a/lawnchair/res/values-pt-rBR/strings.xml +++ b/lawnchair/res/values-pt-rBR/strings.xml @@ -67,7 +67,7 @@ Opacidade do fundo Copiado para a área de transferência - Result copied to clipboard + Resultado copiado para a área de transferência Item removido O que mostrar @@ -143,7 +143,7 @@ Usar fundo transparente nos ícones temáticos Ícones adaptativos automáticos Para todos os ícones não-adaptativos - Show shadow behind icons + Mostrar sombra atrás dos ícones Luminosidade do fundo Use 100% de luz de fundo para fundo branco Resetar ícones customizados @@ -162,7 +162,7 @@ Ovo iOS Octógono - Hexagon + Hexágono Interface do Usuário Quadrado Arredondado Quadrado Afiado @@ -187,7 +187,7 @@ Pacotes de ícones Fonte dos Ícones Temáticos - Tint with accent color + Colorir com a cor de destaque Ícones do Sistema Ícones Temáticos Desligado @@ -280,12 +280,12 @@ Estado da bateria Tocando Agora - Maximum number of targets - Open Smartspacer settings - Smartspacer settings + Número máximo de alvos + Abrir as configurações do Smartspacer + Configurações do Smartspacer Toque para configurar - To use %1$s, turn on Notification Dots. + Para usar %1$s, ative os pontos de notificação. Mostrar na tela inicial O widget Resumo pode ser adicionado manualmente à tela inicial ao colocar o widget do Lawnchair @@ -312,7 +312,7 @@ Suporte e RP Agradecimentos Traduzir - Donate + Doar Rotação da tela inicial Permitir a rotação da tela inicial quando o dispositivo for girado - Blur wallpaper (experimental) - Blur intensity - Factor threshold + Desfocar papel de parede (experimental) + Intensidade do desfoque + Limite de fator Adicionar novos aplicativos à tela inicial Mostrar o feed Nenhum aplicativo de feed instalado @@ -401,8 +401,8 @@ Usar o estilo de pop-up animado e ligeiramente consolidado do Material You Menu flutuante Mostrar o botão de bloqueio - Show system settings button - Show edit home screen button + Mostrar botão de configurações do sistema + Mostrar botão de editar tela inicial Barra de status Mostrar barra de status Barra de status escura @@ -422,8 +422,8 @@ Desativado Barra de Pesquisa do Google Opacidade do fundo - Outline width - Outline color + Largura do contorno + Cor do contorno Raio do canto Aplicar cor de destaque Provedor de pesquisa @@ -457,8 +457,8 @@ %1$d aplicativos - Icon preview background opacity - Folder background opacity + Opacidade do fundo da pré-visualização do ícone + Opacidade do fundo das pastas Cor de fundo do ícone Máximo de colunas nas pastas Máximo de linhas nas pastas @@ -528,12 +528,12 @@ Contatos e mais Via %1$s - Maximum number of apps - Maximum number of people - Maximum number of files - Maximum number of settings - Maximum items for search history - Maximum number of suggestions + Número máximo de aplicativos + Número máximo de contatos + Número máximo de arquivos + Número máximo de configurações + Máximo de itens para o histórico de pesquisa + Número máximo de sugestões Atraso máximo de sugestões da Web Para pesquisar por contatos, permita as permissões de Contatos e Telefone ao Lawnchair @@ -541,6 +541,6 @@ Permitir permissões Provedor de sugestões da web Mostrar o ícone do provedor de sugestões da web na barra de pesquisa - Match dock search bar actions - Clicking the dock search bar will now open the app drawer search UI + Combinar com as ações da barra de pesquisa no dock + Clicar na barra de pesquisa do dock agora irá abrir a IU de pesquisa da gaveta de aplicativos diff --git a/lawnchair/res/values-zh-rTW/strings.xml b/lawnchair/res/values-zh-rTW/strings.xml index b2c2c72c3a6..ea2da0c8526 100644 --- a/lawnchair/res/values-zh-rTW/strings.xml +++ b/lawnchair/res/values-zh-rTW/strings.xml @@ -67,13 +67,13 @@ 背景不透明度 已複製至剪貼板 - Result copied to clipboard + 結果已複製至剪貼簿 已移除物件 選擇需要顯示的項目 To lock your phone when performing a gesture, and to open Recents via gesture, Lawnchair requires accessibility access.\n\nLawnchair doesn\'t watch any user action, though the privilege to do so is required for all accessibility services. Lawnchair discards any event sent by the system.\n\nIn order to lock your phone, or to open Recents, Lawnchair uses the performGlobalAction Accessibility service. - %1$d x %2$d + %1$d × %2$d %1$s & %2$s To access shortcuts and additional features, set Lawnchair as your default launcher - Notification dots + 通知圓點 顯示通知圖示數字 - Notification dot color - Notification counter color - Warning: Notification dot and counter colors don\'t have enough contrast with each other - Warning: Notification dot and counter colors might not always have enough contrast with each other + 通知圓點顏色 + 通知數量顏色 + 警告:通知圓點與通知數量的顏色對比不夠明顯 + 警告:通知圓點與通知數量的顏色對比在某些情況下可能不夠明顯 需要通知存取權 To show Notification Dots, turn on app notifications for %1$s Transparent themed icons 在套用主題色的圖示上使用透明背景 @@ -162,9 +162,9 @@ 蛋形 iOS 八角形 - Hexagon + 六邊形 One UI - Rounded square + 圓角方形 Sharp square 方形 方圓形 diff --git a/lawnchair/res/values/config.xml b/lawnchair/res/values/config.xml index 1649908d55a..de6f60229d5 100644 --- a/lawnchair/res/values/config.xml +++ b/lawnchair/res/values/config.xml @@ -102,6 +102,7 @@ false true false + false true true false diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml index 67ea81e33bc..5566eca6566 100644 --- a/lawnchair/res/values/strings.xml +++ b/lawnchair/res/values/strings.xml @@ -520,6 +520,8 @@ Status bar Show status bar Dark status bar + Status bar clock + Dynamically hide status bar clock in first screen Text color Light diff --git a/lawnchair/src/app/lawnchair/FeedBridge.kt b/lawnchair/src/app/lawnchair/FeedBridge.kt index c35bb5ed1b8..f86a75479c1 100644 --- a/lawnchair/src/app/lawnchair/FeedBridge.kt +++ b/lawnchair/src/app/lawnchair/FeedBridge.kt @@ -27,6 +27,7 @@ import android.util.Log import app.lawnchair.preferences.PreferenceManager import app.lawnchair.util.SingletonHolder import app.lawnchair.util.ensureOnMainThread +import app.lawnchair.util.getSignatureHash import app.lawnchair.util.useApplicationContext import com.android.launcher3.BuildConfig import com.android.launcher3.R @@ -153,20 +154,17 @@ class FeedBridge(private val context: Context) { private const val TAG = "FeedBridge" private const val OVERLAY_ACTION = "com.android.launcher3.WINDOW_OVERLAY" - private val whitelist = mapOf( - // HomeFeeder, t.me/homefeeder - "ua.itaysonlab.homefeeder" to 0x887456ed, - // Librechair, t.me/librechair - "launcher.libre.dev" to 0x2e9dbab5, - // Smartspacer - SmartspacerConstants.SMARTSPACER_PACKAGE_NAME to 0x15c6e36f, - // AIDL Bridge - "amirz.aidlbridge" to 0xb662cc2f, - // Google - "com.google.android.googlequicksearchbox" to 0xe3ca78d8, - // Pixel Bridge (or launcher) - "com.google.android.apps.nexuslauncher" to 0xb662cc2f, - ) + private val whitelist = mutableMapOf() + + fun initializeWhitelist(context: Context) { + whitelist["com.saulhdev.neofeed"] = getSignatureHash(context, "com.saulhdev.neofeed") + whitelist["ua.itaysonlab.homefeeder"] = 0x887456ed + whitelist["launcher.libre.dev"] = 0x2e9dbab5 + whitelist[SmartspacerConstants.SMARTSPACER_PACKAGE_NAME] = 0x15c6e36f + whitelist["amirz.aidlbridge"] = 0xb662cc2f + whitelist["com.google.android.googlequicksearchbox"] = 0xe3ca78d8 + whitelist["com.google.android.apps.nexuslauncher"] = 0xb662cc2f + } fun getAvailableProviders(context: Context) = context.packageManager .queryIntentServices( @@ -181,4 +179,8 @@ class FeedBridge(private val context: Context) { @JvmStatic fun useBridge(context: Context) = getInstance(context).let { it.shouldUseFeed || it.customBridgeAvailable() } } + + init { + initializeWhitelist(context) + } } diff --git a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt index 59e1152e6d4..2ce11525afe 100644 --- a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt +++ b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt @@ -176,8 +176,17 @@ class LawnchairLauncher : QuickstepLauncher() { } }.launchIn(scope = lifecycleScope) - launcher.stateManager.addStateListener(statusBarClockListener) - + preferenceManager2.statusBarClock.get().onEach { + with(launcher.stateManager) { + if (it) { + addStateListener(statusBarClockListener) + } else { + removeStateListener(statusBarClockListener) + // Make sure status bar clock is restored when the preference is toggled off + LawnchairApp.instance.restoreClockInStatusBar() + } + } + } preferenceManager2.rememberPosition.get().onEach { with(launcher.stateManager) { if (it) { diff --git a/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt b/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt index 1d7079d7e77..32030be816d 100644 --- a/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt +++ b/lawnchair/src/app/lawnchair/allapps/AllAppsSearchInput.kt @@ -30,6 +30,7 @@ import app.lawnchair.preferences2.subscribeBlocking import app.lawnchair.qsb.AssistantIconView import app.lawnchair.qsb.LawnQsbLayout.Companion.getLensIntent import app.lawnchair.qsb.LawnQsbLayout.Companion.getSearchProvider +import app.lawnchair.qsb.ThemingMethod import app.lawnchair.qsb.providers.Google import app.lawnchair.qsb.providers.GoogleGo import app.lawnchair.qsb.providers.PixelSearch @@ -152,6 +153,13 @@ class AllAppsSearchInput(context: Context, attrs: AttributeSet?) : themed = themed || iconRes == R.drawable.ic_qsb_search, method = searchProvider.themingMethod, ) + } else { + // Always theme default search icon + setThemedIconResource( + resId = R.drawable.ic_qsb_search, + themed = true, + method = ThemingMethod.TINT, + ) } setOnClickListener { diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt index 55c70fa2178..0658adf6b36 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultIconRow.kt @@ -18,7 +18,6 @@ import app.lawnchair.search.model.SearchResultActionCallBack import app.lawnchair.util.copyToClipboard import com.android.app.search.LayoutType import com.android.launcher3.R -import com.android.launcher3.touch.ItemClickHandler import com.android.launcher3.views.BubbleTextHolder class SearchResultIconRow(context: Context, attrs: AttributeSet?) : @@ -69,11 +68,11 @@ class SearchResultIconRow(context: Context, attrs: AttributeSet?) : } } - override val isQuickLaunch get() = icon.isQuickLaunch - override val titleText get() = icon.titleText + override val isQuickLaunch get() = icon.isQuickLaunch || hasFlag(flags, SearchResultView.FLAG_QUICK_LAUNCH) + override val titleText get() = if (icon.titleText != "") icon.titleText else title.text override fun launch(): Boolean { - ItemClickHandler.INSTANCE.onClick(this) + performClick() return true } diff --git a/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt b/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt index ab30fd1ad29..ffdd2bb46a9 100644 --- a/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt +++ b/lawnchair/src/app/lawnchair/allapps/views/SearchResultRightLeftIcon.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.util.AttributeSet +import android.util.Log import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView @@ -34,6 +35,8 @@ class SearchResultRightLeftIcon(context: Context, attrs: AttributeSet?) : private var defSmsAppInfo: AppInfo? = null private var isSmall = false + private var flags = 0 + override fun onFinishInflate() { super.onFinishInflate() isSmall = id == R.id.search_result_small_icon_row_left_right @@ -79,11 +82,16 @@ class SearchResultRightLeftIcon(context: Context, attrs: AttributeSet?) : this.layoutParams = layoutParams } - override val isQuickLaunch: Boolean get() = false + override val isQuickLaunch: Boolean get() = hasFlag(flags, SearchResultView.FLAG_QUICK_LAUNCH) override val titleText: CharSequence? get() = title.text - override fun launch(): Boolean = false + override fun launch(): Boolean { + val logTag = "Contact or files" + Log.d(logTag, "in launch") + Log.d(logTag, performClick().toString()) + return true + } override fun bind( target: SearchTargetCompat, diff --git a/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt b/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt index c79636f0073..bd0ae9ec859 100644 --- a/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt +++ b/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt @@ -103,7 +103,7 @@ class PreferenceManager private constructor(private val context: Context) : Base val hotseatQsbStrokeWidth = FloatPref("pref_searchStrokeWidth", 0F, recreate) val enableWallpaperBlur = BoolPref("pref_enableWallpaperBlur", false, recreate) val wallpaperBlur = IntPref("pref_wallpaperBlur", 25, recreate) - val wallpaperBlurFactorThreshold = IntPref("pref_wallpaperBlurFactor", 25, recreate) + val wallpaperBlurFactorThreshold = FloatPref("pref_wallpaperBlurFactor", 3.0F, recreate) val recentsActionScreenshot = BoolPref("pref_recentsActionScreenshot", !isOnePlusStock) val recentsActionShare = BoolPref("pref_recentsActionShare", isOnePlusStock) diff --git a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt index a3dc70b371c..3e45d322dd9 100644 --- a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt +++ b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt @@ -228,6 +228,12 @@ class PreferenceManager2 private constructor(private val context: Context) : Pre defaultValue = context.resources.getBoolean(R.bool.config_default_show_status_bar), ) + val statusBarClock = preference( + key = booleanPreferencesKey(name = "status_bar_clock"), + defaultValue = context.resources.getBoolean(R.bool.config_default_dynamic_hide_status_bar_clock), + onSet = { reloadHelper.reloadGrid() }, + ) + val rememberPosition = preference( key = booleanPreferencesKey(name = "all_apps_remember_position"), defaultValue = context.resources.getBoolean(R.bool.config_default_remember_position), diff --git a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt index 1bf993640b6..eeab662f543 100644 --- a/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt +++ b/lawnchair/src/app/lawnchair/search/adapter/SearchTargetFactory.kt @@ -273,7 +273,6 @@ class SearchTargetFactory( LayoutType.PEOPLE_TILE, SearchTargetCompat.RESULT_TYPE_CONTACT_TILE, CONTACT, - Bundle(), ) } @@ -307,7 +306,6 @@ class SearchTargetFactory( LayoutType.THUMBNAIL, SearchTargetCompat.RESULT_TYPE_FILE_TILE, FILES, - Bundle(), ) } diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt index ad7747778ee..ca49fa98dde 100644 --- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt +++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairLocalSearchAlgorithm.kt @@ -47,9 +47,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull @@ -124,15 +121,21 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm } override fun doSearch(query: String, callback: SearchCallback) { - appState.model.enqueueModelUpdateTask(object : LauncherModel.ModelUpdateTask { - override fun execute(app: ModelTaskController, dataModel: BgDataModel, apps: AllAppsList) { - coroutineScope.launch(Dispatchers.Main) { - getAllSearchResults(apps.data, query, prefs).collect { allResults -> - callback.onSearchResult(query, ArrayList(allResults)) + appState.model.enqueueModelUpdateTask( + object : LauncherModel.ModelUpdateTask { + override fun execute( + app: ModelTaskController, + dataModel: BgDataModel, + apps: AllAppsList, + ) { + coroutineScope.launch(Dispatchers.Main) { + getAllSearchResults(apps.data, query.trimEnd(), prefs).let { allResults -> + callback.onSearchResult(query, ArrayList(allResults)) + } } } - } - }) + }, + ) } override fun cancel(interruptActiveRequests: Boolean) { @@ -141,67 +144,70 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm } } - private fun getAllSearchResults( + private suspend fun getAllSearchResults( apps: MutableList, query: String, prefs: PreferenceManager, - ): Flow> = channelFlow { - val allResults = mutableListOf() - - launch { - if (searchApps) { - getAppSearchResults(apps, query).collect { appResults -> - allResults.addAll(appResults) - send(allResults.toList()) - } - } - getLocalSearchResults(query, prefs).collect { localResults -> - allResults.addAll(localResults) - send(allResults.toList()) - } - getSearchLinks(query).collect { otherResults -> - allResults.addAll(otherResults) - send(allResults.toList()) + ): List { + val allResults = mutableListOf() + + if (searchApps) { + getAppSearchResults(apps, query).let { appResults -> + allResults.addAll(appResults) } } + getLocalSearchResults(query, prefs).let { localResults -> + allResults.addAll(localResults) + } + getSearchLinks(query).let { otherResults -> + allResults.addAll(otherResults) + } + + setFirstItemQuickLaunch(allResults) + val finalResults = transformSearchResults(allResults).toList() + + return finalResults } private fun getAppSearchResults( apps: MutableList, query: String, - ): Flow> = flow { + ): List { val searchTargets = mutableListOf() val appResults = performAppSearch(apps, query) parseAppSearchResults(appResults, searchTargets) - setFirstItemQuickLaunch(searchTargets) - emit(transformSearchResults(searchTargets)) + return searchTargets } - private fun getLocalSearchResults( + private suspend fun getLocalSearchResults( query: String, prefs: PreferenceManager, - ): Flow> = flow { + ): List { val searchTargets = mutableListOf() val localSearchResults = performDeviceLocalSearch(query, prefs) parseLocalSearchResults(localSearchResults, searchTargets) - emit(transformSearchResults(searchTargets)) + return searchTargets } private fun getSearchLinks( query: String, - ): Flow> = flow { + ): List { val searchTargets = mutableListOf() searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) if (useWebSuggestions) { - withContext(Dispatchers.IO) { - searchTargets.add(searchTargetFactory.createWebSearchTarget(query, webSuggestionsProvider)) - } + searchTargets.add( + searchTargetFactory.createWebSearchTarget( + query, + webSuggestionsProvider, + ), + ) } searchTargetFactory.createMarketSearchTarget(query)?.let { searchTargets.add(it) } - emit(transformSearchResults(searchTargets)) + + return searchTargets } private fun parseAppSearchResults( @@ -217,7 +223,14 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm if (shortcuts != null) { if (shortcuts.isNotEmpty()) { searchTargets.add(searchTargetFactory.createHeaderTarget(SPACE)) - singleAppResult.let { searchTargets.add(searchTargetFactory.createAppSearchTarget(it, true)) } + singleAppResult.let { + searchTargets.add( + searchTargetFactory.createAppSearchTarget( + it, + true, + ), + ) + } searchTargets.addAll(shortcuts.map(searchTargetFactory::createShortcutTarget)) } } @@ -238,7 +251,10 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm searchTargets.add(suggestionsHeader) searchTargets.addAll( suggestions.map { - searchTargetFactory.createWebSuggestionsTarget(it.resultData as String, suggestionProvider) + searchTargetFactory.createWebSuggestionsTarget( + it.resultData as String, + suggestionProvider, + ) }, ) } @@ -278,7 +294,14 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm HEADER_JUSTIFY, ) searchTargets.add(recentKeywordHeader) - searchTargets.addAll(recentKeyword.map { searchTargetFactory.createSearchHistoryTarget(it.resultData as RecentKeyword, suggestionProvider) }) + searchTargets.addAll( + recentKeyword.map { + searchTargetFactory.createSearchHistoryTarget( + it.resultData as RecentKeyword, + suggestionProvider, + ) + }, + ) } val files = filterByType(localSearchResults, FILES) @@ -299,7 +322,10 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm SearchUtils.normalSearch(apps, query, maxAppResultsCount, hiddenApps, hiddenAppsInSearch) } - private suspend fun performDeviceLocalSearch(query: String, prefs: PreferenceManager): MutableList = + private suspend fun performDeviceLocalSearch( + query: String, + prefs: PreferenceManager, + ): MutableList = withContext(Dispatchers.IO) { val results = ArrayList() @@ -349,12 +375,13 @@ class LawnchairLocalSearchAlgorithm(context: Context) : LawnchairSearchAlgorithm val timeout = maxWebSuggestionDelay.toLong() val result = withTimeoutOrNull(timeout) { if (prefs.searchResultStartPageSuggestion.get()) { - WebSearchProvider.fromString(webSuggestionsProvider).getSuggestions(query, maxWebSuggestionsCount).map { - SearchResult( - WEB_SUGGESTION, - it, - ) - } + WebSearchProvider.fromString(webSuggestionsProvider) + .getSuggestions(query, maxWebSuggestionsCount).map { + SearchResult( + WEB_SUGGESTION, + it, + ) + } } else { emptyList() } diff --git a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt index d9c214ecfc5..9ce375e561d 100644 --- a/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt +++ b/lawnchair/src/app/lawnchair/search/algorithms/LawnchairSearchAlgorithm.kt @@ -115,7 +115,12 @@ sealed class LawnchairSearchAlgorithm( protected fun setFirstItemQuickLaunch(searchTargets: List) { val hasQuickLaunch = searchTargets.any { it.extras.getBoolean(EXTRA_QUICK_LAUNCH, false) } if (!hasQuickLaunch) { - searchTargets.firstOrNull()?.extras?.apply { + // check if we have a header or spacer item. if so, we skip as there isn't any relevant + // action to be applied + val target = searchTargets.getOrNull( + searchTargets.indexOfFirst { it.layoutType != TEXT_HEADER }, + ) + target?.extras?.apply { putBoolean(EXTRA_QUICK_LAUNCH, true) } } diff --git a/lawnchair/src/app/lawnchair/theme/color/tokens/ColorTokens.kt b/lawnchair/src/app/lawnchair/theme/color/tokens/ColorTokens.kt index c0ce897a8fd..00d3e36b352 100644 --- a/lawnchair/src/app/lawnchair/theme/color/tokens/ColorTokens.kt +++ b/lawnchair/src/app/lawnchair/theme/color/tokens/ColorTokens.kt @@ -144,6 +144,10 @@ object ColorTokens { @JvmField val SurfaceBrightColor = DayNightColorToken(Neutral2_600.setLStar(98.0), Neutral2_600.setLStar(24.0)) + @JvmField val PrimaryButton = Accent1_600 + + @JvmField val WidgetAddButtonBackgroundColor = PrimaryButton + val SwitchThumbOn = Accent1_100 val SwitchThumbOff = DayNightColorToken(Neutral2_300, Neutral1_400) val SwitchThumbDisabled = DayNightColorToken(Neutral2_100, Neutral1_700) diff --git a/lawnchair/src/app/lawnchair/theme/drawable/DrawableTokens.kt b/lawnchair/src/app/lawnchair/theme/drawable/DrawableTokens.kt index 473ce365d5e..743214fa906 100644 --- a/lawnchair/src/app/lawnchair/theme/drawable/DrawableTokens.kt +++ b/lawnchair/src/app/lawnchair/theme/drawable/DrawableTokens.kt @@ -3,6 +3,7 @@ package app.lawnchair.theme.drawable import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.StateListDrawable @@ -186,4 +187,8 @@ object DrawableTokens { @JvmField val WorkCard = ResourceDrawableToken(R.drawable.work_card) .setColor(ColorTokens.Surface) + + @JvmField + val WidgetAddButtonBackground = ResourceDrawableToken(R.drawable.widget_cell_add_button_background) + .setTint(ColorTokens.WidgetAddButtonBackgroundColor) } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/SearchSuggestionPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/SearchSuggestionPreference.kt index 29417f4f941..b8b84d9a701 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/components/SearchSuggestionPreference.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/components/SearchSuggestionPreference.kt @@ -1,5 +1,6 @@ package app.lawnchair.ui.preferences.components +import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -159,8 +160,29 @@ private fun BottomSheetContent( ) { ModalBottomSheetContent( buttons = { - OutlinedButton(onClick = { onHide() }) { - Text(text = stringResource(id = R.string.action_apply)) + Crossfade( + !isPermissionGranted && (onRequestPermission != null && permissionRationale != null), + label = "transition", + ) { + if (it) { + Row { + OutlinedButton(onClick = { onHide() }) { + Text(text = stringResource(id = android.R.string.cancel)) + } + Spacer(Modifier.width(8.dp)) + Button( + onClick = { + onRequestPermission?.invoke() + }, + ) { + Text(text = stringResource(id = R.string.grant_requested_permissions)) + } + } + } else { + OutlinedButton(onClick = { onHide() }) { + Text(text = stringResource(id = R.string.action_apply)) + } + } } }, ) { @@ -183,6 +205,9 @@ private fun BottomSheetContent( } if (!isPermissionGranted) { if (onRequestPermission != null && permissionRationale != null) { + Spacer( + modifier = Modifier.height(8.dp), + ) Card( modifier = Modifier .fillMaxWidth() @@ -195,17 +220,6 @@ private fun BottomSheetContent( Text( text = permissionRationale, ) - Spacer(Modifier.height(8.dp)) - Row { - Spacer(Modifier.weight(1f)) - Button( - onClick = { - onRequestPermission() - }, - ) { - Text(text = stringResource(id = R.string.grant_requested_permissions)) - } - } } } } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/ExperimentalFeaturesPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/ExperimentalFeaturesPreferences.kt index 88157ef910d..0e09d5d790b 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/ExperimentalFeaturesPreferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/ExperimentalFeaturesPreferences.kt @@ -66,9 +66,8 @@ fun ExperimentalFeaturesPreferences( SliderPreference( label = stringResource(id = R.string.wallpaper_background_blur_factor), adapter = prefs.wallpaperBlurFactorThreshold.getAdapter(), - step = 5, - valueRange = 0..100, - showUnit = "%", + step = 1F, + valueRange = 0F..10F, ) } } diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt index 9c7b3a68020..475c420f33f 100644 --- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt +++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/HomeScreenPreferences.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import app.lawnchair.LawnchairApp import app.lawnchair.data.iconoverride.IconOverrideRepository import app.lawnchair.nexuslauncher.OverlayCallbackImpl import app.lawnchair.preferences.getAdapter @@ -161,6 +162,13 @@ fun HomeScreenPreferences( label = stringResource(id = R.string.dark_status_bar_label), ) } + ExpandAndShrink(visible = showStatusBarAdapter.state.value && LawnchairApp.isRecentsEnabled) { + SwitchPreference( + adapter = prefs2.statusBarClock.getAdapter(), + label = stringResource(id = R.string.status_bar_clock_label), + description = stringResource(id = R.string.status_bar_clock_description), + ) + } } PreferenceGroup(heading = stringResource(id = R.string.icons)) { SliderPreference( diff --git a/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt b/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt index f19a03ca381..8cde0de970c 100644 --- a/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt +++ b/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt @@ -35,9 +35,9 @@ import android.graphics.RectF import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Build import android.os.Looper import android.provider.OpenableColumns -import android.util.Log import android.util.Size import android.view.View import android.widget.TextView @@ -51,7 +51,6 @@ import com.android.launcher3.Utilities import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.util.Themes import com.android.systemui.shared.system.QuickStepContract -import com.google.android.renderscript.Toolkit import com.patrykmichalik.opto.core.firstBlocking import java.util.concurrent.Callable import java.util.concurrent.ExecutionException @@ -286,12 +285,21 @@ fun createRoundedBitmap(color: Int, cornerRadius: Float): Bitmap { return bitmap } -fun blurBitmap(source: Bitmap, percent: Int, factorThreshold: Int = 25): Bitmap { - try { - val factor = percent.toFloat().div(100f) * factorThreshold - return Toolkit.blur(source, factor.toInt()) - } catch (e: Exception) { - Log.e("LawnchairUtil", "Error bluring bitmap: $e") - return source +fun getSignatureHash(context: Context, packageName: String): Long? { + return try { + val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) + } else { + context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) + } + + val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + packageInfo.signingInfo?.apkContentsSigners + } else { + packageInfo.signatures + } + signatures?.firstOrNull()?.hashCode()?.toLong() + } catch (_: PackageManager.NameNotFoundException) { + null } } diff --git a/platform_frameworks_libs_systemui b/platform_frameworks_libs_systemui index c343e8f06f3..039cb6fcff1 160000 --- a/platform_frameworks_libs_systemui +++ b/platform_frameworks_libs_systemui @@ -1 +1 @@ -Subproject commit c343e8f06f3f82d67670fe9531bcd5998d70d67a +Subproject commit 039cb6fcff196e7e0101b1081ce2d546af7fb918 diff --git a/proguard.pro b/proguard.pro index dd472614af0..92c14595989 100644 --- a/proguard.pro +++ b/proguard.pro @@ -21,6 +21,16 @@ -dontwarn dalvik.system.CloseGuard -dontwarn lineageos.providers.LineageSettings$System -dontwarn androidx.compose.runtime.PrimitiveSnapshotStateKt +-dontwarn androidx.renderscript.Allocation +-dontwarn androidx.renderscript.BaseObj +-dontwarn androidx.renderscript.Element +-dontwarn androidx.renderscript.FieldPacker +-dontwarn androidx.renderscript.RSRuntimeException +-dontwarn androidx.renderscript.RenderScript +-dontwarn androidx.renderscript.Script$LaunchOptions +-dontwarn androidx.renderscript.ScriptC +-dontwarn androidx.renderscript.ScriptIntrinsicBlur +-dontwarn androidx.renderscript.Type # Common rules. diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 9ab9ea2c3d7..d31c8b7ac49 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -55,7 +55,6 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH; import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; -import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE; import static com.android.launcher3.util.DisplayController.isTransientTaskbar; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; @@ -139,6 +138,7 @@ import com.android.launcher3.util.DynamicResource; import com.android.launcher3.util.ObjectWrapper; import com.android.launcher3.util.RunnableList; +import com.android.launcher3.util.StableViewInfo; import com.android.launcher3.util.Themes; import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.views.ScrimView; @@ -1360,16 +1360,8 @@ private static int getRotationChange(RemoteAnimationTarget[] appTargets) { ? new ArrayList<>() : runningTaskTarget.taskInfo.launchCookies; - int launchCookieItemId = NO_MATCHING_ID; - for (IBinder cookie : launchCookies) { - Integer itemId = ObjectWrapper.unwrap(cookie); - if (itemId != null) { - launchCookieItemId = itemId; - break; - } - } - - return mLauncher.getFirstMatchForAppClose(launchCookieItemId, packageName, + return mLauncher.getFirstMatchForAppClose( + StableViewInfo.fromLaunchCookies(launchCookies), packageName, UserHandle.of(runningTaskTarget.taskInfo.userId), true /* supportsAllAppsState */); } diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 943c08c7864..a9f7c2a4410 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -143,10 +143,12 @@ private void parseIntentExtras() { if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) { mUiSurface = uiSurfaceParam; } - ArrayList addedWidgets = getIntent().getParcelableArrayListExtra( - EXTRA_ADDED_APP_WIDGETS, AppWidgetProviderInfo.class); - if (addedWidgets != null) { - mAddedWidgets = addedWidgets; + if (Utilities.ATLEAST_T) { + ArrayList addedWidgets = getIntent().getParcelableArrayListExtra( + EXTRA_ADDED_APP_WIDGETS, AppWidgetProviderInfo.class); + if (addedWidgets != null) { + mAddedWidgets = addedWidgets; + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index d332e5e62cd..4e8034da631 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -43,6 +43,8 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import static com.android.wm.shell.Flags.enableTinyTaskbar; +import static java.util.stream.Collectors.toList; + import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.ActivityOptions; @@ -1258,7 +1260,7 @@ private void launchFromOverviewTaskbar(@Nullable RecentsView recents, boolean isLaunchingAppPair = itemInfos.size() == 2; // Convert the list of ItemInfo instances to a list of ComponentKeys - List componentKeys = itemInfos.stream().map(ItemInfo::getComponentKey).toList(); + List componentKeys = itemInfos.stream().map(ItemInfo::getComponentKey).collect(toList()); recents.getSplitSelectController().findLastActiveTasksAndRunCallback( componentKeys, isLaunchingAppPair, diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 707ab8f1d60..87b99ff955d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -31,6 +31,8 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; +import static java.util.stream.Collectors.toList; + import android.annotation.BinderThread; import android.annotation.Nullable; import android.app.Notification; @@ -398,7 +400,7 @@ private void applyViewChanges(BubbleBarViewUpdate update) { if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) { // Create the new list List newOrder = update.bubbleKeysInOrder.stream() - .map(mBubbles::get).filter(Objects::nonNull).toList(); + .map(mBubbles::get).filter(Objects::nonNull).collect(toList()); if (!newOrder.isEmpty()) { mBubbleBarViewController.reorderBubbles(newOrder); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 393f58a043b..8e25c5c5d14 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -18,6 +18,8 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; +import static java.util.stream.Collectors.toList; + import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; @@ -463,7 +465,7 @@ public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpandin */ public void reorderBubbles(List newOrder) { List viewList = newOrder.stream().filter(Objects::nonNull) - .map(BubbleBarBubble::getView).toList(); + .map(BubbleBarBubble::getView).collect(toList()); mBarView.reorder(viewList); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java index 63d2ff24d36..b46bfc74185 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java @@ -57,7 +57,7 @@ public QuickstepInteractionHandler(QuickstepLauncher launcher) { @SuppressWarnings("NewApi") @Override public boolean onInteraction(View view, PendingIntent pendingIntent, - RemoteViews.RemoteResponse remoteResponse) { + RemoteViews.RemoteResponse remoteResponse) { LauncherAppWidgetHostView hostView = findHostViewAncestor(view); if (hostView == null) { Log.e(TAG, "View did not have a LauncherAppWidgetHostView ancestor."); @@ -74,15 +74,8 @@ public boolean onInteraction(View view, PendingIntent pendingIntent, Pair options = remoteResponse.getLaunchOptions(view); ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager() .getActivityLaunchOptions(hostView); - Object itemInfo = hostView.getTag(); - IBinder launchCookie = null; - if (itemInfo instanceof ItemInfo) { - launchCookie = mLauncher.getLaunchCookie((ItemInfo) itemInfo); - activityOptions.options.setLaunchCookie(launchCookie); - } - if (Utilities.ATLEAST_S && !pendingIntent.isActivity() && LawnchairApp.isRecentsEnabled()) { - // In the event this pending intent eventually launches an activity, i.e. a - // trampoline, + if (!pendingIntent.isActivity()) { + // In the event this pending intent eventually launches an activity, i.e. a trampoline, // use the Quickstep transition animation. try { IActivityTaskManagerHidden atm = Refine.unsafeCast(ActivityTaskManager.getService()); @@ -90,7 +83,7 @@ public boolean onInteraction(View view, PendingIntent pendingIntent, atm.registerRemoteAnimationForNextActivityStart( pendingIntent.getCreatorPackage(), activityOptions.options.getRemoteAnimationAdapter(), - launchCookie); + activityOptions.options.getLaunchCookie()); } catch (NoSuchMethodError e) { atm.registerRemoteAnimationForNextActivityStart( pendingIntent.getCreatorPackage(), @@ -99,16 +92,18 @@ public boolean onInteraction(View view, PendingIntent pendingIntent, } catch (RemoteException e) { // Do nothing. } + } + try { activityOptions.options.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (Utilities.ATLEAST_T) { - activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); - } - Utilities.allowBGLaunch(activityOptions.options); - + activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR); + activityOptions.options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + } catch (Throwable t) { + // ignore } options = Pair.create(options.first, activityOptions.options); if (pendingIntent.isActivity()) { - logAppLaunch(itemInfo); + logAppLaunch(hostView.getTag()); } return RemoteViews.startPendingIntent(hostView, pendingIntent, options); } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 3334b32b45a..03aa1006082 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -40,7 +40,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED; -import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition; import static com.android.launcher3.popup.SystemShortcut.APP_INFO; import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP; @@ -1192,7 +1191,6 @@ public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInf (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() : Display.DEFAULT_DISPLAY); Utilities.allowBGLaunch(activityOptions.options); - addLaunchCookie(item, activityOptions.options); return activityOptions; } @@ -1214,60 +1212,6 @@ public void enterStageSplitFromRunningApp(boolean leftOrTop) { mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop); } - /** - * Adds a new launch cookie for the activity launch if supported. - * - * @param info the item info for the launch - * @param opts the options to set the launchCookie on. - */ - public void addLaunchCookie(ItemInfo info, ActivityOptions opts) { - IBinder launchCookie = getLaunchCookie(info); - if (launchCookie != null) { - opts.setLaunchCookie(launchCookie); - } - } - - /** - * Return a new launch cookie for the activity launch if supported. - * - * @param info the item info for the launch - */ - public IBinder getLaunchCookie(ItemInfo info) { - if (info == null) { - return null; - } - switch (info.container) { - case Favorites.CONTAINER_DESKTOP: - case Favorites.CONTAINER_HOTSEAT: - case Favorites.CONTAINER_PRIVATESPACE: - // Fall through and continue it's on the workspace (we don't support swiping back - // to other containers like all apps or the hotseat predictions (which can change) - break; - default: - if (info.container >= 0) { - // Also allow swiping to folders - break; - } - // Reset any existing launch cookies associated with the cookie - return ObjectWrapper.wrap(NO_MATCHING_ID); - } - switch (info.itemType) { - case Favorites.ITEM_TYPE_APPLICATION: - case Favorites.ITEM_TYPE_DEEP_SHORTCUT: - case Favorites.ITEM_TYPE_APPWIDGET: - // Fall through and continue if it's an app, shortcut, or widget - break; - default: - // Reset any existing launch cookies associated with the cookie - return ObjectWrapper.wrap(NO_MATCHING_ID); - } - return ObjectWrapper.wrap(new Integer(info.id)); - } - - public void setHintUserWillBeActive() { - addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); - } - @Override public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) { super.onDisplayInfoChanged(context, info, flags); diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt index 27bd03d4dd8..9216e57579d 100644 --- a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt +++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt @@ -7,6 +7,7 @@ import android.app.backup.BackupRestoreEventLogger.BackupRestoreError import android.content.Context import com.android.launcher3.Flags.enableLauncherBrMetricsFixed import com.android.launcher3.LauncherSettings.Favorites +import com.android.launcher3.Utilities import com.android.launcher3.backuprestore.LauncherRestoreEventLogger /** @@ -29,8 +30,11 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven @BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair" } - private val restoreEventLogger: BackupRestoreEventLogger = + private val restoreEventLogger: BackupRestoreEventLogger? = if (Utilities.ATLEAST_S) { BackupManager(context).delayedRestoreLogger + } else { + null + } /** * For logging when multiple items of a given data type failed to restore. @@ -45,7 +49,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven @BackupRestoreError error: String? ) { if (enableLauncherBrMetricsFixed()) { - restoreEventLogger.logItemsRestoreFailed(dataType, count, error) + restoreEventLogger?.logItemsRestoreFailed(dataType, count, error) } } @@ -57,7 +61,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven */ override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) { if (enableLauncherBrMetricsFixed()) { - restoreEventLogger.logItemsRestored(dataType, count) + restoreEventLogger?.logItemsRestored(dataType, count) } } @@ -68,7 +72,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven */ override fun logSingleFavoritesItemRestored(favoritesId: Int) { if (enableLauncherBrMetricsFixed()) { - restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1) + restoreEventLogger?.logItemsRestored(favoritesIdToDataType(favoritesId), 1) } } @@ -80,7 +84,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven */ override fun logFavoritesItemsRestored(favoritesId: Int, count: Int) { if (enableLauncherBrMetricsFixed()) { - restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), count) + restoreEventLogger?.logItemsRestored(favoritesIdToDataType(favoritesId), count) } } @@ -95,7 +99,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven @BackupRestoreError error: String? ) { if (enableLauncherBrMetricsFixed()) { - restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error) + restoreEventLogger?.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error) } } @@ -112,7 +116,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven @BackupRestoreError error: String? ) { if (enableLauncherBrMetricsFixed()) { - restoreEventLogger.logItemsRestoreFailed( + restoreEventLogger?.logItemsRestoreFailed( favoritesIdToDataType(favoritesId), count, error @@ -125,7 +129,7 @@ class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEven * done restoring items for Launcher. */ override fun reportLauncherRestoreResults() { - if (enableLauncherBrMetricsFixed()) { + if (enableLauncherBrMetricsFixed() && restoreEventLogger != null) { BackupManager(context).reportDelayedRestoreResult(restoreEventLogger) } } diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java index 3c665906b57..413ddbc9f8c 100644 --- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -20,7 +20,6 @@ import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.Utilities.mapBoundToRange; -import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; @@ -42,7 +41,7 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.uioverrides.QuickstepLauncher; -import com.android.launcher3.util.ObjectWrapper; +import com.android.launcher3.util.StableViewInfo; import com.android.launcher3.views.ClipIconView; import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.views.FloatingView; @@ -62,12 +61,12 @@ /** * Temporary class to allow easier refactoring */ -public class LauncherSwipeHandlerV2 extends - AbsSwipeUpHandler { +public class LauncherSwipeHandlerV2 extends AbsSwipeUpHandler< + QuickstepLauncher, RecentsView, LauncherState> { public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState, - TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, - boolean continuingLastGesture, InputConsumerController inputConsumer) { + TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, + boolean continuingLastGesture, InputConsumerController inputConsumer) { super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs, continuingLastGesture, inputConsumer); } @@ -95,8 +94,8 @@ public AnimatorPlaybackController createActivityAnimationToHome() { TaskView sourceTaskView = mRecentsView == null && targetTaskView == null ? null : targetTaskView == null - ? mRecentsView.getRunningTaskView() - : targetTaskView; + ? mRecentsView.getRunningTaskView() + : targetTaskView; final View workspaceView = findWorkspaceView( targetTaskView == null ? launchCookies : Collections.emptyList(), sourceTaskView); @@ -107,9 +106,6 @@ public AnimatorPlaybackController createActivityAnimationToHome() { || !mContainer.getDesktopVisibilityController().areDesktopTasksVisible()); mContainer.getRootView().setForceHideBackArrow(true); - if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - mContainer.setHintUserWillBeActive(); - } if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) { return new LauncherHomeAnimationFactory() { @@ -144,8 +140,6 @@ private HomeAnimationFactory createIconHomeAnimationFactory( return new FloatingViewHomeAnimationFactory(floatingIconView) { @Nullable private RectF mTargetRect; - @Nullable - private RectFSpringAnim mSiblingAnimation; @Nullable @Override @@ -172,14 +166,6 @@ public RectF getWindowTargetRect() { } } - @Override - protected void playScalingRevealAnimation() { - if (mContainer != null) { - new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation, - getWindowTargetRect()).start(); - } - } - @Override public void setAnimation(RectFSpringAnim anim) { super.setAnimation(anim); @@ -245,6 +231,8 @@ private HomeAnimationFactory createWidgetHomeAnimationFactory( isTargetTranslucent, fallbackBackgroundColor); return new FloatingViewHomeAnimationFactory(floatingWidgetView) { + @Nullable + private RectF mTargetRect; @Override @Nullable @@ -254,8 +242,14 @@ protected View getViewIgnoredInWorkspaceRevealAnimation() { @Override public RectF getWindowTargetRect() { - super.getWindowTargetRect(); - return backgroundLocation; + if (enableScalingRevealHomeAnimation()) { + if (mTargetRect == null) { + mTargetRect = new RectF(backgroundLocation); + } + return mTargetRect; + } else { + return backgroundLocation; + } } @Override @@ -266,10 +260,11 @@ public float getEndRadius(RectF cropRectF) { @Override public void setAnimation(RectFSpringAnim anim) { super.setAnimation(anim); - - anim.addAnimatorListener(floatingWidgetView); - floatingWidgetView.setOnTargetChangeListener(anim::onTargetPositionChanged); - floatingWidgetView.setFastFinishRunnable(anim::end); + mSiblingAnimation = anim; + mSiblingAnimation.addAnimatorListener(floatingWidgetView); + floatingWidgetView.setOnTargetChangeListener( + mSiblingAnimation::onTargetPositionChanged); + floatingWidgetView.setFastFinishRunnable(mSiblingAnimation::end); } @Override @@ -305,18 +300,7 @@ private View findWorkspaceView(List launchCookies, TaskView sourceTaskV return null; } - // Find the associated item info for the launch cookie (if available), note that predicted - // apps actually have an id of -1, so use another default id here - int launchCookieItemId = NO_MATCHING_ID; - for (IBinder cookie : launchCookies) { - Integer itemId = ObjectWrapper.unwrap(cookie); - if (itemId != null) { - launchCookieItemId = itemId; - break; - } - } - - return mContainer.getFirstMatchForAppClose(launchCookieItemId, + return mContainer.getFirstMatchForAppClose(StableViewInfo.fromLaunchCookies(launchCookies), sourceTaskView.getFirstTask().key.getComponent().getPackageName(), UserHandle.of(sourceTaskView.getFirstTask().key.userId), false /* supportsAllAppsState */); @@ -330,13 +314,22 @@ protected void finishRecentsControllerToHome(Runnable callback) { } private class FloatingViewHomeAnimationFactory extends LauncherHomeAnimationFactory { - private final FloatingView mFloatingView; + @Nullable + protected RectFSpringAnim mSiblingAnimation; FloatingViewHomeAnimationFactory(FloatingView floatingView) { mFloatingView = floatingView; } + @Override + protected void playScalingRevealAnimation() { + if (mContainer != null) { + new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation, + getWindowTargetRect()).start(); + } + } + @Override public void onCancel() { mFloatingView.fastFinish(); @@ -387,4 +380,4 @@ protected void playScalingRevealAnimation() { } } } -} +} \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java index 3dec381b04d..a9da63f4809 100644 --- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java +++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java @@ -19,6 +19,8 @@ import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher; import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; +import static java.util.stream.Collectors.toList; + import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; @@ -175,7 +177,7 @@ public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets t mSplitBounds.rightBottomTaskId); List overlayTargets = Arrays.stream(targets.apps).filter( target -> target.windowConfiguration.getWindowingMode() - != WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW).toList(); + != WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW).collect(toList()); // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude, // vice versa @@ -223,7 +225,7 @@ public RemoteTargetHandle[] assignTargetsForDesktop(RemoteAnimationTargets targe for (int i = 0; i < mRemoteTargetHandles.length; i++) { RemoteAnimationTarget primaryTaskTarget = targets.apps[i]; List excludeTargets = Arrays.stream(targets.apps) - .filter(target -> target.taskId != primaryTaskTarget.taskId).toList(); + .filter(target -> target.taskId != primaryTaskTarget.taskId).collect(toList()); mRemoteTargetHandles[i].mTransformParams.setTargetSet( createRemoteAnimationTargetsForTarget(targets, excludeTargets)); mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null); diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java index ba33c62d9d1..79df19f2597 100644 --- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -26,6 +26,7 @@ import android.graphics.Matrix.ScaleToFit; import android.graphics.Rect; import android.graphics.RectF; +import android.util.Log; import android.view.RemoteAnimationTarget; import androidx.annotation.NonNull; @@ -85,7 +86,7 @@ public abstract class SwipeUpAnimationLogic implements protected boolean mIsSwipeForSplit; public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState) { + GestureState gestureState) { mContext = context; mDeviceState = deviceState; mGestureState = gestureState; @@ -267,7 +268,7 @@ protected RectF[] updateProgressForStartRect(Matrix[] outMatrix, float startProg mCurrentShift.updateValue(startProgress); RectF[] startRects = new RectF[mRemoteTargetHandles.length]; for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length; - i < mRemoteTargetHandlesLength; i++) { + i < mRemoteTargetHandlesLength; i++) { RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i]; TaskViewSimulator tvs = remoteHandle.getTaskViewSimulator(); tvs.apply(remoteHandle.getTransformParams().setProgress(startProgress)); @@ -300,14 +301,14 @@ protected TaskViewSimulator[] getRemoteTaskViewSimulators() { * @param homeAnimationFactory The home animation factory. */ protected RectFSpringAnim[] createWindowAnimationToHome(float startProgress, - HomeAnimationFactory homeAnimationFactory) { + HomeAnimationFactory homeAnimationFactory) { // TODO(b/195473584) compute separate end targets for different staged split final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); RectFSpringAnim[] out = new RectFSpringAnim[mRemoteTargetHandles.length]; Matrix[] homeToWindowPositionMap = new Matrix[mRemoteTargetHandles.length]; RectF[] startRects = updateProgressForStartRect(homeToWindowPositionMap, startProgress); for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length; - i < mRemoteTargetHandlesLength; i++) { + i < mRemoteTargetHandlesLength; i++) { RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i]; out[i] = getWindowAnimationToHomeInternal( homeAnimationFactory, @@ -381,6 +382,8 @@ private RectFSpringAnim getWindowAnimationToHomeInternal( protected class SpringAnimationRunner extends AnimationSuccessListener implements RectFSpringAnim.OnUpdateListener, BuilderProxy { + private static final String TAG = "SpringAnimationRunner"; + final Rect mCropRect = new Rect(); final Matrix mMatrix = new Matrix(); @@ -481,10 +484,26 @@ public void onUpdate(RectF currentRect, float progress) { return; } mTargetTaskView.setAlpha(mAnimationFactory.isAnimatingIntoIcon() ? 1f : alpha); - float width = mThumbnailStartBounds.width(); - float height = mThumbnailStartBounds.height(); - float scale = Math.min(currentRect.width(), currentRect.height()) - / Math.min(width, height); + float startWidth = mThumbnailStartBounds.width(); + float startHeight = mThumbnailStartBounds.height(); + float currentWidth = currentRect.width(); + float currentHeight = currentRect.height(); + float scale; + + boolean isStartWidthValid = Float.compare(startWidth, 0f) > 0; + boolean isStartHeightValid = Float.compare(startHeight, 0f) > 0; + if (isStartWidthValid && isStartHeightValid) { + scale = Math.min(currentWidth, currentHeight) / Math.min(startWidth, startHeight); + } else { + Log.e(TAG, "TaskView starting bounds are invalid: " + mThumbnailStartBounds); + if (isStartWidthValid) { + scale = currentWidth / startWidth; + } else if (isStartHeightValid) { + scale = currentHeight / startHeight; + } else { + scale = 1f; + } + } mTargetTaskView.setScaleX(scale); mTargetTaskView.setScaleY(scale); @@ -496,7 +515,7 @@ public void onUpdate(RectF currentRect, float progress) { @Override public void onBuildTargetParams(SurfaceProperties builder, RemoteAnimationTarget app, - TransformParams params) { + TransformParams params) { builder.setMatrix(mMatrix) .setWindowCrop(mCropRect) .setCornerRadius(params.getCornerRadius()); @@ -606,4 +625,4 @@ public void cancel() { }; } } -} +} \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java index 6f9cbfd74a7..e8782101641 100644 --- a/quickstep/src/com/android/quickstep/util/AppPairsController.java +++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java @@ -33,6 +33,8 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition; +import static java.util.stream.Collectors.toList; + import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -336,7 +338,7 @@ public void handleAppPairLaunchInApp(AppPairIcon launchingIconView, List itemInfos) { TaskbarActivityContext context = (TaskbarActivityContext) launchingIconView.getContext(); List componentKeys = - itemInfos.stream().map(ItemInfo::getComponentKey).toList(); + itemInfos.stream().map(ItemInfo::getComponentKey).collect(toList()); // Use TopTaskTracker to find the currently running app (or apps) TopTaskTracker topTaskTracker = getTopTaskTracker(); @@ -362,7 +364,7 @@ public void handleAppPairLaunchInApp(AppPairIcon launchingIconView, } else { return INVALID_TASK_ID; } - }).toList(); + }).collect(toList()); if (lastActiveTasksOfAppPair.contains(runningTaskId1) && lastActiveTasksOfAppPair.contains(runningTaskId2)) { diff --git a/res/values/styles.xml b/res/values/styles.xml index 128b98c11ef..7f29bd8c21f 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -328,7 +328,9 @@ no - diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 851b4709b35..6a682b2dd38 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -106,7 +106,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_PIN_IME_POPUP = 1 << 22; // Custom compose popups - public static final int TYPE_COMPOSE_VIEW = 1 << 22; + public static final int TYPE_COMPOSE_VIEW = 1 << 23; public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET @@ -115,14 +115,14 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_ADD_TO_HOME_CONFIRMATION | TYPE_TASKBAR_OVERLAY_PROXY - | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP; + | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP | TYPE_COMPOSE_VIEW; // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_TASKBAR_OVERLAY_PROXY - | TYPE_PIN_IME_POPUP; + | TYPE_PIN_IME_POPUP | TYPE_COMPOSE_VIEW; /** Type of popups that should get exclusive accessibility focus. */ public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 97ce00c41d8..541e27b58bb 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -48,6 +48,7 @@ import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; +import android.util.Log; import android.util.Property; import android.util.Size; import android.util.TypedValue; @@ -85,12 +86,13 @@ import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.IconLabelDotView; +import com.android.launcher3.views.FloatingIconViewCompanion; import java.text.NumberFormat; import java.util.HashMap; import java.util.Locale; +import app.lawnchair.LawnchairApp; import app.lawnchair.font.FontManager; import app.lawnchair.preferences.PreferenceManager; import app.lawnchair.util.LawnchairUtilsKt; @@ -103,7 +105,7 @@ * too aggressive. */ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, - IconLabelDotView, DraggableView, Reorderable { + FloatingIconViewCompanion, DraggableView, Reorderable { public static final int DISPLAY_WORKSPACE = 0; public static final int DISPLAY_ALL_APPS = 1; @@ -231,7 +233,7 @@ public BubbleTextView(Context context, AttributeSet attrs) { public BubbleTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mActivity = ActivityContext.lookupContext(context); - FastBitmapDrawable.setFlagHoverEnabled(enableCursorHoverStates()); + FastBitmapDrawable.setFlagHoverEnabled(LawnchairApp.isRecentsEnabled() && enableCursorHoverStates()); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BubbleTextView, defStyle, 0); @@ -1275,6 +1277,13 @@ public SafeCloseable prepareDrawDragView() { }; } + @Override + public void resetIconScale(boolean shouldReset) { + if (shouldReset) { + mIcon.resetScale(); + } + } + private void resetIconScale() { if (mIcon != null) { mIcon.resetScale(); diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index ad586c95f80..848bf42e3e2 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -77,6 +77,7 @@ import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.LauncherAppWidgetHostView; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -84,6 +85,10 @@ import java.util.Arrays; import java.util.Stack; +import app.lawnchair.preferences2.PreferenceManager2; +import app.lawnchair.theme.color.tokens.ColorTokens; +import app.lawnchair.theme.drawable.DrawableTokens; + public class CellLayout extends ViewGroup { private static final String TAG = "CellLayout"; private static final boolean LOGD = false; @@ -208,6 +213,8 @@ public class CellLayout extends ViewGroup { CellLayoutContainer mCellLayoutContainer; + public final PreferenceManager2 pref; + public static final FloatProperty SPRING_LOADED_PROGRESS = new FloatProperty("spring_loaded_progress") { @Override @@ -244,6 +251,7 @@ public CellLayout(Context context, AttributeSet attrs, int defStyle) { mActivity = ActivityContext.lookupContext(context); DeviceProfile deviceProfile = mActivity.getDeviceProfile(); + pref = PreferenceManager2.getInstance(context); resetCellSizeInternal(deviceProfile); mCountX = deviceProfile.inv.numColumns; @@ -258,11 +266,11 @@ public CellLayout(Context context, AttributeSet attrs, int defStyle) { Resources res = getResources(); - mBackground = getContext().getDrawable(R.drawable.bg_celllayout); + mBackground = DrawableTokens.BgCellLayout.resolve(getContext()); mBackground.setCallback(this); mBackground.setAlpha(0); - mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor); + mGridColor = ColorTokens.WorkspaceAccentColor.resolveColor(getContext()); mGridVisualizationRoundingRadius = res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius); mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx); @@ -274,7 +282,7 @@ public CellLayout(Context context, AttributeSet attrs, int defStyle) { for (int i = 0; i < mDragOutlines.length; i++) { mDragOutlines[i] = new CellLayoutLayoutParams(0, 0, 0, 0); } - mDragOutlinePaint.setColor(Themes.getAttrColor(context, R.attr.workspaceTextColor)); + mDragOutlinePaint.setColor(ColorTokens.WorkspaceAccentColor.resolveColor(getContext())); // When dragging things around the home screens, we show a green outline of // where the item will land. The outlines gradually fade out, leaving a trail @@ -732,7 +740,7 @@ public int getCountY() { } public boolean acceptsWidget() { - return mContainerType == WORKSPACE; + return mContainerType == WORKSPACE || mContainerType == HOTSEAT; } /** @@ -1853,7 +1861,7 @@ public int getDesiredHeight() { public boolean isOccupied(int x, int y) { if (x >= 0 && x < mCountX && y >= 0 && y < mCountY) { - return mOccupied.cells[x][y]; + return mOccupied.cells[x][y] && !PreferenceExtensionsKt.firstBlocking(pref.getAllowWidgetOverlap()); } if (BuildConfigs.IS_STUDIO_BUILD) { throw new RuntimeException("Position exceeds the bound of this CellLayout"); @@ -1936,7 +1944,7 @@ public GridOccupancy cloneGridOccupancy() { } public boolean isRegionVacant(int x, int y, int spanX, int spanY) { - return mOccupied.isRegionVacant(x, y, spanX, spanY); + return mOccupied.isRegionVacant(x, y, spanX, spanY) || PreferenceExtensionsKt.firstBlocking(pref.getAllowWidgetOverlap()); } public void setSpaceBetweenCellLayoutsPx(@Px int spaceBetweenCellLayoutsPx) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 0dd0fa6cad8..03c30ccfe64 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -72,6 +72,7 @@ import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE; import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE; +import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW; import static com.android.launcher3.logging.StatsLogManager.EventEnum; @@ -242,6 +243,7 @@ import com.android.launcher3.util.ScreenOnTracker; import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener; import com.android.launcher3.util.SettingsCache; +import com.android.launcher3.util.StableViewInfo; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; @@ -827,7 +829,7 @@ public void invalidateParent(ItemInfo info) { View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container); if (collectionIcon instanceof FolderIcon folderIcon && collectionIcon.getTag() instanceof FolderInfo) { - if (new FolderGridOrganizer(getDeviceProfile()) + if (createFolderGridOrganizer(getDeviceProfile()) .setFolderInfo((FolderInfo) folderIcon.getTag()) .isItemInPreview(info.rank)) { folderIcon.invalidate(); @@ -2494,28 +2496,25 @@ private boolean canAnimatePageChange() { } /** - * Similar to {@link #getFirstMatch} but optimized to finding a suitable view - * for the app close + * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close * animation. * - * @param preferredItemId The id of the preferred item to match to if it - * exists, - * or ItemInfo#NO_MATCHING_ID if you want to not - * match by item id - * @param packageName The package name of the app to match. - * @param user The user of the app to match. - * @param supportsAllAppsState If true and we are in All Apps state, looks for - * view in All Apps. + * @param svi The StableViewInfo of the preferred item to match to if it exists or null + * @param packageName The package name of the app to match. + * @param user The user of the app to match. + * @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps. * Else we only looks on the workspace. */ - public @Nullable View getFirstMatchForAppClose(int preferredItemId, String packageName, + public @Nullable View getFirstMatchForAppClose( + @Nullable StableViewInfo svi, String packageName, UserHandle user, boolean supportsAllAppsState) { - final Predicate preferredItem = info -> info != null && info.id == preferredItemId; - final Predicate packageAndUserAndApp = info -> info != null - && info.itemType == ITEM_TYPE_APPLICATION - && info.user.equals(user) - && info.getTargetComponent() != null - && TextUtils.equals(info.getTargetComponent().getPackageName(), + final Predicate preferredItem = svi == null ? i -> false : svi::matches; + final Predicate packageAndUserAndApp = info -> + info != null + && info.itemType == ITEM_TYPE_APPLICATION + && info.user.equals(user) + && info.getTargetComponent() != null + && TextUtils.equals(info.getTargetComponent().getPackageName(), packageName); if (supportsAllAppsState && isInState(LauncherState.ALL_APPS)) { @@ -2540,7 +2539,7 @@ private boolean canAnimatePageChange() { Folder folder = Folder.getOpen(this); if (folder != null) { View v = getFirstMatch(Collections.singletonList( - folder.getContent().getCurrentCellLayout().getShortcutsAndWidgets()), + folder.getContent().getCurrentCellLayout().getShortcutsAndWidgets()), preferredItem, packageAndUserAndApp); if (v == null) { @@ -2552,7 +2551,8 @@ private boolean canAnimatePageChange() { List containers = new ArrayList<>(mWorkspace.getPanelCount() + 1); containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets()); - mWorkspace.forEachVisiblePage(page -> containers.add(((CellLayout) page).getShortcutsAndWidgets())); + mWorkspace.forEachVisiblePage(page + -> containers.add(((CellLayout) page).getShortcutsAndWidgets())); // Order: Preferred item by itself or in folder, then by matching package/user return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem), diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java index fdff36a62f0..27e7626d4af 100644 --- a/src/com/android/launcher3/LauncherRootView.java +++ b/src/com/android/launcher3/LauncherRootView.java @@ -2,23 +2,31 @@ import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; +import static app.lawnchair.util.PackagePermissionManagerKt.checkAndRequestFilesPermission; + +import android.app.WallpaperManager; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ViewDebug; import android.view.WindowInsets; import com.android.launcher3.graphics.SysUiScrim; import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.uioverrides.ApiWrapper; +import com.hoko.blur.HokoBlur; import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import com.android.launcher3.util.window.WindowManagerProxy; import java.util.Collections; import java.util.List; -import app.lawnchair.LawnchairApp; +import app.lawnchair.preferences.PreferenceManager; import app.lawnchair.preferences2.PreferenceManager2; public class LauncherRootView extends InsettableFrameLayout { @@ -39,12 +47,71 @@ public class LauncherRootView extends InsettableFrameLayout { private final SysUiScrim mSysUiScrim; private final boolean mEnableTaskbarOnPhone; + private final PreferenceManager pref; + public LauncherRootView(Context context, AttributeSet attrs) { super(context, attrs); mActivity = StatefulActivity.fromContext(context); mSysUiScrim = new SysUiScrim(this); PreferenceManager2 prefs2 = PreferenceManager2.getInstance(getContext()); mEnableTaskbarOnPhone = PreferenceExtensionsKt.firstBlocking(prefs2.getEnableTaskbarOnPhone()); + + pref = PreferenceManager.getInstance(getContext()); + + if (pref.getEnableWallpaperBlur().get()){ + if (checkAndRequestFilesPermission(context, pref)){ + setUpBlur(context); + } + } + } + + private void setUpBlur(Context context) { + var display = mActivity.getDeviceProfile(); + int width = display.widthPx; + int height = display.heightPx; + + var wallpaper = getScaledWallpaperDrawable(width, height); + if (wallpaper == null) { + return; + } + + Bitmap originalBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(originalBitmap); + + wallpaper.setBounds(0, 0, width, height); + wallpaper.draw(canvas); + + Paint paint = new Paint(); + paint.setColor(Color.WHITE); + paint.setAlpha((int) (0.2 * 255)); + canvas.drawRect(0, 0, width, height, paint); + + Bitmap blurredBitmap = HokoBlur.with(context) + .forceCopy(true) + .scheme(HokoBlur.SCHEME_OPENGL) + .sampleFactor(pref.getWallpaperBlurFactorThreshold().get()) + .radius(pref.getWallpaperBlur().get()) + .blur(originalBitmap); + + setBackground(new BitmapDrawable(getContext().getResources(), blurredBitmap)); + } + + private Drawable getScaledWallpaperDrawable(int width, int height) { + WallpaperManager wallpaperManager = WallpaperManager.getInstance(getContext()); + Drawable wallpaperDrawable = wallpaperManager.getDrawable(); + + if (wallpaperDrawable != null) { + Bitmap originalBitmap = Bitmap.createBitmap( + width, height, Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(originalBitmap); + + wallpaperDrawable.setBounds(0, 0, width, height); + wallpaperDrawable.draw(canvas); + + return new BitmapDrawable(getContext().getResources(), originalBitmap); + } + return null; } private void handleSystemWindowInsets(Rect insets) { @@ -65,7 +132,6 @@ public WindowInsets onApplyWindowInsets(WindowInsets insets) { insets = WindowManagerProxy.INSTANCE.get(getContext()) .normalizeWindowInsets(getContext(), insets, mTempRect); handleSystemWindowInsets(mTempRect); -// computeGestureExclusionRect(); return insets; } @@ -114,7 +180,6 @@ protected void dispatchDraw(Canvas canvas) { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); -// computeGestureExclusionRect(); mSysUiScrim.setSize(r - l, b - t); } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 498c1be9735..878a2c9b6a0 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -113,6 +113,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import app.lawnchair.icons.ExtendedBitmapDrawable; import app.lawnchair.preferences.PreferenceManager; /** @@ -767,6 +768,9 @@ public static Pair intersectingViews = new ArrayList<>(); Rect occupiedRect = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); + if (PreferenceExtensionsKt.firstBlocking(mCellLayout.pref.getAllowWidgetOverlap())) { + solution.intersectingViews = new ArrayList<>(intersectingViews); + return true; + } + // Mark the desired location of the view currently being dragged. if (ignoreView != null) { CellAndSpan c = solution.map.get(ignoreView); @@ -150,18 +156,11 @@ private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, ).thenComparing( view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY() ); - List views = new ArrayList<>(); - if (Utilities.ATLEAST_U) { - views = solution.map.keySet().stream().sorted(comparator).toList(); - } else { - List keys = new ArrayList<>(solution.map.keySet()); - for (Object key : keys) { - if (key instanceof View) { - views.add((View) key); - } - } - views.sort(comparator); - } + List views = solution.map.keySet().stream() + .filter(View.class::isInstance) + .map(View.class::cast) + .collect(Collectors.toList()); + views.sort(comparator); for (View child : views) { if (child == ignoreView) continue; CellAndSpan c = solution.map.get(child); diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index db693f0fa9a..3910da1c2b7 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -157,7 +157,7 @@ public boolean onInterceptHoverEvent(MotionEvent ev) { isOverFolderOrSearchBar = isEventOverView(topView, ev) || isEventOverAccessibleDropTargetBar(ev); if (!isOverFolderOrSearchBar) { - sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); + sendTapOutsideFolderAccessibilityEvent(currentFolder.getIsEditingName()); mHoverPointClosesFolder = true; return true; } @@ -167,7 +167,7 @@ public boolean onInterceptHoverEvent(MotionEvent ev) { isOverFolderOrSearchBar = isEventOverView(topView, ev) || isEventOverAccessibleDropTargetBar(ev); if (!isOverFolderOrSearchBar && !mHoverPointClosesFolder) { - sendTapOutsideFolderAccessibilityEvent(currentFolder.isEditingName()); + sendTapOutsideFolderAccessibilityEvent(currentFolder.getIsEditingName()); mHoverPointClosesFolder = true; return true; } else if (!isOverFolderOrSearchBar) { diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index de457fc0638..fd02972363d 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -67,6 +67,8 @@ import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import app.lawnchair.icons.CustomAdaptiveIconDrawable; + /** A custom view for rendering an icon, folder, shortcut or widget during drag-n-drop. */ public abstract class DragView extends FrameLayout { @@ -265,7 +267,7 @@ public void setItemInfo(final ItemInfo info) { try (LauncherIcons li = LauncherIcons.obtain(mActivity)) { // Since we just want the scale, avoid heavy drawing operations Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale( - new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null), + new CustomAdaptiveIconDrawable (new ColorDrawable(Color.BLACK), null), null, null, null)); } diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index 3c79c2aaa22..5622504741f 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -139,7 +139,7 @@ private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, Path bgPath = new Path(); int radius = bg.getRadius(); - IconShape.getShape().addToPath( + IconShape.INSTANCE.get (icon.getContext()).getShape().addToPath( bgPath, dragViewSize.x / 2f - radius, dragViewSize.y / 2f - radius, diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index a1fab8052db..7495cdbcfbd 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -19,6 +19,7 @@ package com.android.launcher3.folder; import static android.text.TextUtils.isEmpty; + import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; @@ -27,6 +28,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; +import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; import static com.android.launcher3.testing.shared.TestProtocol.FOLDER_OPENED_MESSAGE; @@ -35,6 +37,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.annotation.ColorInt; import android.annotation.SuppressLint; import android.appwidget.AppWidgetHostView; import android.content.Context; @@ -64,10 +67,9 @@ import android.view.inputmethod.EditorInfo; import android.widget.TextView; -import androidx.annotation.ColorInt; -import androidx.core.graphics.ColorUtils; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.core.content.res.ResourcesCompat; import androidx.core.view.WindowInsetsCompat; @@ -102,13 +104,15 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.Themes; import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.ClipPathView; import com.android.launcher3.widget.PendingAddShortcutInfo; +import com.androidinternal.graphics.ColorUtils; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -121,6 +125,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import app.lawnchair.preferences2.PreferenceManager2; +import app.lawnchair.theme.color.ColorOption; +import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.theme.drawable.DrawableTokens; import app.lawnchair.util.EditTextExtensions; import app.lawnchair.util.LawnchairUtilsKt; @@ -141,17 +148,17 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo /** * We avoid measuring {@link #mContent} with a 0 width or height, as this - * results in CellLayout being measured as UNSPECIFIED, which it does not - * support. + * results in CellLayout being measured as UNSPECIFIED, which it does not support. */ - private static final int MIN_CONTENT_DIMEN = 5; + @VisibleForTesting + static final int MIN_CONTENT_DIMEN = 5; public static final int STATE_CLOSED = 0; public static final int STATE_ANIMATING = 1; public static final int STATE_OPEN = 2; @Retention(RetentionPolicy.SOURCE) - @IntDef({ STATE_CLOSED, STATE_ANIMATING, STATE_OPEN }) + @IntDef({STATE_CLOSED, STATE_ANIMATING, STATE_OPEN}) public @interface FolderState { } @@ -174,7 +181,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private static final int FOLDER_COLOR_ANIMATION_DURATION = 200; private static final int REORDER_DELAY = 250; - private static final int ON_EXIT_CLOSE_DELAY = 400; + static final int ON_EXIT_CLOSE_DELAY = 400; private static final Rect sTempRect = new Rect(); private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10; @@ -194,21 +201,20 @@ public static boolean willAcceptItemType(int itemType) { || itemType == ITEM_TYPE_APP_PAIR; } - private final Alarm mReorderAlarm = new Alarm(Looper.getMainLooper()); - private final Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper()); - private final Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper()); - final Alarm mScrollPauseAlarm = new Alarm(Looper.getMainLooper()); + private Alarm mReorderAlarm = new Alarm(Looper.getMainLooper()); + private Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper()); + private Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper()); + private Alarm mScrollPauseAlarm = new Alarm(Looper.getMainLooper()); final ArrayList mItemsInReadingOrder = new ArrayList(); private AnimatorSet mCurrentAnimator; private boolean mIsAnimatingClosed = false; - // Folder can be displayed in Launcher's activity or a separate window (e.g. - // Taskbar). + // Folder can be displayed in Launcher's activity or a separate window (e.g. Taskbar). // Anything specific to Launcher should use mLauncherDelegate, otherwise should // use mActivityContext. - protected final LauncherDelegate mLauncherDelegate; + protected LauncherDelegate mLauncherDelegate; protected final ActivityContext mActivityContext; protected DragController mDragController; @@ -221,7 +227,7 @@ public static boolean willAcceptItemType(int itemType) { @Thunk FolderPagedView mContent; - public FolderNameEditText mFolderName; + FolderNameEditText mFolderName; private PageIndicatorDots mPageIndicator; protected View mFooter; @@ -233,21 +239,22 @@ public static boolean willAcceptItemType(int itemType) { private Path mClipPath; - @ViewDebug.ExportedProperty(category = "launcher", mapping = { - @ViewDebug.IntToString(from = STATE_CLOSED, to = "STATE_CLOSED"), - @ViewDebug.IntToString(from = STATE_ANIMATING, to = "STATE_ANIMATING"), - @ViewDebug.IntToString(from = STATE_OPEN, to = "STATE_OPEN"), - }) + @ViewDebug.ExportedProperty(category = "launcher", + mapping = { + @ViewDebug.IntToString(from = STATE_CLOSED, to = "STATE_CLOSED"), + @ViewDebug.IntToString(from = STATE_ANIMATING, to = "STATE_ANIMATING"), + @ViewDebug.IntToString(from = STATE_OPEN, to = "STATE_OPEN"), + }) private int mState = STATE_CLOSED; private final List mOnFolderStateChangedListeners = new ArrayList<>(); private OnFolderStateChangedListener mPriorityOnFolderStateChangedListener; @ViewDebug.ExportedProperty(category = "launcher") private boolean mRearrangeOnClose = false; - boolean mItemsInvalidated = false; + private boolean mItemsInvalidated = false; private View mCurrentDragView; private boolean mIsExternalDrag; - private boolean mDragInProgress = false; + private boolean mIsDragInProgress = false; private boolean mDeleteFolderOnDropCompleted = false; private boolean mSuppressFolderDeletion = false; private boolean mItemAddedBackToSelfViaIcon = false; @@ -260,7 +267,7 @@ public static boolean willAcceptItemType(int itemType) { private int mScrollAreaOffset; @Thunk - int mScrollHintDir = SCROLL_NONE; + private int mScrollHintDir = SCROLL_NONE; @Thunk int mCurrentScrollDir = SCROLL_NONE; @@ -270,13 +277,13 @@ public static boolean willAcceptItemType(int itemType) { private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback; private GradientDrawable mBackground; + PreferenceManager2 preferenceManager2; /** * Used to inflate the Workspace from XML. * * @param context The application's context. - * @param attrs The attributes set containing the Workspace's customization - * values. + * @param attrs The attributes set containing the Workspace's customization values. */ public Folder(Context context, AttributeSet attrs) { super(context, attrs); @@ -286,15 +293,12 @@ public Folder(Context context, AttributeSet attrs) { mLauncherDelegate = LauncherDelegate.from(mActivityContext); mStatsLogManager = StatsLogManager.newInstance(context); - // We need this view to be focusable in touch mode so that when text editing of - // the folder - // name is complete, we have something to focus on, thus hiding the cursor and - // giving - // reliable behavior when clicking the text field (since it will always gain - // focus on + // We need this view to be focusable in touch mode so that when text editing of the folder + // name is complete, we have something to focus on, thus hiding the cursor and giving + // reliable behavior when clicking the text field (since it will always gain focus on // click). setFocusableInTouchMode(true); - + preferenceManager2 = PreferenceManager2.INSTANCE.get(context); } @Override @@ -330,9 +334,9 @@ protected void onFinishInflate() { | InputType.TYPE_TEXT_FLAG_CAP_WORDS); mFolderName.forceDisableSuggestions(true); mFolderName.setPadding(mFolderName.getPaddingLeft(), - (mFooterHeight - mFolderName.getLineHeight()) / 2, + (getFooterHeight() - mFolderName.getLineHeight()) / 2, mFolderName.getPaddingRight(), - (mFooterHeight - mFolderName.getLineHeight()) / 2); + (getFooterHeight() - mFolderName.getLineHeight()) / 2); @ColorInt int accentColor = Themes.getColorAccent(mFolderName.getContext()); @@ -343,9 +347,6 @@ protected void onFinishInflate() { mFolderName.setHighlightColor(ColorUtils.setAlphaComponent(accentColor, 82)); } - mFooter = findViewById(R.id.folder_footer); - mFooterHeight = dp.folderFooterHeightPx; - if (Utilities.ATLEAST_R) { mKeyboardInsetAnimationCallback = new KeyboardInsetAnimationCallback(this); setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback); @@ -354,43 +355,54 @@ protected void onFinishInflate() { public boolean onLongClick(View v) { // Return if global dragging is not enabled - if (!mLauncherDelegate.isDraggingEnabled()) - return true; + if (!getIsLauncherDraggingEnabled()) return true; return startDrag(v, new DragOptions()); } + @VisibleForTesting + boolean getIsLauncherDraggingEnabled() { + return mLauncherDelegate.isDraggingEnabled(); + } + public boolean startDrag(View v, DragOptions options) { Object tag = v.getTag(); if (tag instanceof ItemInfo item) { mEmptyCellRank = item.rank; mCurrentDragView = v; - mDragController.addDragListener(this); - if (options.isAccessibleDrag) { - mDragController.addDragListener(new AccessibleDragListenerAdapter( - mContent, FolderAccessibilityHelper::new) { - @Override - protected void enableAccessibleDrag(boolean enable, - @Nullable DragObject dragObject) { - super.enableAccessibleDrag(enable, dragObject); - mFooter.setImportantForAccessibility(enable - ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : IMPORTANT_FOR_ACCESSIBILITY_AUTO); - } - }); - } - - mLauncherDelegate.beginDragShared(v, this, options); + addDragListener(options); + callBeginDragShared(v, options); } return true; } + void callBeginDragShared(View v, DragOptions options) { + mLauncherDelegate.beginDragShared(v, this, options); + } + + void addDragListener(DragOptions options) { + getDragController().addDragListener(this); + if (!options.isAccessibleDrag) { + return; + } + getDragController().addDragListener(new AccessibleDragListenerAdapter( + mContent, FolderAccessibilityHelper::new) { + @Override + protected void enableAccessibleDrag(boolean enable, + @Nullable DragObject dragObject) { + super.enableAccessibleDrag(enable, dragObject); + mFooter.setImportantForAccessibility(enable + ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } + }); + } + @Override public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { if (dragObject.dragSource != this) { return; } - mContent.removeItem(mCurrentDragView); mItemsInvalidated = true; @@ -399,35 +411,28 @@ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { try (SuppressInfoChanges s = new SuppressInfoChanges()) { mInfo.remove(dragObject.dragInfo, true); } - mDragInProgress = true; + mIsDragInProgress = true; mItemAddedBackToSelfViaIcon = false; } @Override public void onDragEnd() { - if (mIsExternalDrag && mDragInProgress) { + if (mIsExternalDrag && mIsDragInProgress) { completeDragExit(); } - mDragInProgress = false; - mDragController.removeDragListener(this); - } - - public boolean isEditingName() { - return mIsEditingName; + mIsDragInProgress = false; + getDragController().removeDragListener(this); } public void startEditingFolderName() { - post(() -> { - showLabelSuggestions(); - mFolderName.setHint(""); - mIsEditingName = true; - }); + showLabelSuggestions(); + mFolderName.setHint(""); + mIsEditingName = true; } @Override public boolean onBackKey() { - // Convert to a string here to ensure that no other state associated with the - // text field + // Convert to a string here to ensure that no other state associated with the text field // gets saved. String newTitle = mFolderName.getText().toString(); if (DEBUG) { @@ -447,8 +452,7 @@ public boolean onBackKey() { this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, getContext().getString(R.string.folder_renamed, newTitle)); - // This ensures that focus is gained every time the field is clicked, which - // selects all + // This ensures that focus is gained every time the field is clicked, which selects all // the text and brings up the soft keyboard if necessary. mFolderName.clearFocus(); @@ -473,7 +477,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) { this.setTranslationY(0); - if (Utilities.ATLEAST_R) { + try { if (windowInsets.isVisible(WindowInsets.Type.ime())) { Insets keyboardInsets = windowInsets.getInsets(WindowInsets.Type.ime()); int folderHeightFromBottom = getHeightFromBottom(); @@ -484,7 +488,7 @@ public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) { - mFolderName.getPaddingBottom()); } } - } else { + } catch (Throwable t) { WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets); if (insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { androidx.core.graphics.Insets keyboardInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.ime()); @@ -505,7 +509,11 @@ public FolderIcon getFolderIcon() { return mFolderIcon; } - public void setDragController(DragController dragController) { + DragController getDragController() { + return mDragController; + } + + void setDragController(DragController dragController) { mDragController = dragController; } @@ -516,10 +524,8 @@ public void setFolderIcon(FolderIcon icon) { @Override protected void onAttachedToWindow() { - // requestFocus() causes the focus onto the folder itself, which doesn't cause - // visual - // effect but the next arrow key can start the keyboard focus inside of the - // folder, not + // requestFocus() causes the focus onto the folder itself, which doesn't cause visual + // effect but the next arrow key can start the keyboard focus inside of the folder, not // the folder itself. requestFocus(); super.onAttachedToWindow(); @@ -540,8 +546,7 @@ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { @Override public View focusSearch(int direction) { - // When the folder is focused, further focus search should be within the folder - // contents. + // When the folder is focused, further focus search should be within the folder contents. return FocusFinder.getInstance().findNextFocus(this, null, direction); } @@ -576,8 +581,7 @@ void bind(FolderInfo info) { mFolderName.setText(""); mFolderName.setHint(R.string.folder_hint_text); } - // In case any children didn't come across during loading, clean up the folder - // accordingly + // In case any children didn't come across during loading, clean up the folder accordingly mFolderIcon.post(() -> { if (getItemCount() <= 1) { replaceFolderWithFinalItem(); @@ -585,12 +589,12 @@ void bind(FolderInfo info) { }); } + /** - * Show suggested folder title in FolderEditText if the first suggestion is - * non-empty, push + * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push * rest of the suggestions to InputMethodManager. */ - private void showLabelSuggestions() { + void showLabelSuggestions() { if (mInfo.suggestedFolderNames == null) { return; } @@ -617,10 +621,8 @@ private void showLabelSuggestions() { /** * Creates a new UserFolder, inflated from R.layout.user_folder. * - * @param activityContext The main ActivityContext in which to inflate this - * Folder. It must also - * be an instance or ContextWrapper around the Launcher - * activity context. + * @param activityContext The main ActivityContext in which to inflate this Folder. It must also + * be an instance or ContextWrapper around the Launcher activity context. * @return A new UserFolder. */ @SuppressLint("InflateParams") @@ -669,17 +671,14 @@ public void onAnimationEnd(Animator animation) { } private boolean shouldUseHardwareLayerForAnimation(CellLayout currentCellLayout) { - if (ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS.get()) - return true; + if (ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS.get()) return true; int folderCount = 0; final ShortcutAndWidgetContainer container = currentCellLayout.getShortcutsAndWidgets(); for (int i = container.getChildCount() - 1; i >= 0; --i) { final View child = container.getChildAt(i); - if (child instanceof AppWidgetHostView) - return false; - if (child instanceof FolderIcon) - ++folderCount; + if (child instanceof AppWidgetHostView) return false; + if (child instanceof FolderIcon) ++folderCount; } return folderCount >= MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION; } @@ -689,22 +688,21 @@ private boolean shouldUseHardwareLayerForAnimation(CellLayout currentCellLayout) */ public void beginExternalDrag() { mIsExternalDrag = true; - mDragInProgress = true; + mIsDragInProgress = true; // Since this folder opened by another controller, it might not get onDrop or // onDropComplete. Perform cleanup once drag-n-drop ends. - mDragController.addDragListener(this); + getDragController().addDragListener(this); ArrayList items = new ArrayList<>(mInfo.getContents()); mEmptyCellRank = items.size(); - items.add(null); // Add an empty spot at the end + items.add(null); // Add an empty spot at the end animateOpen(items, mEmptyCellRank / mContent.itemsPerPage()); } /** - * Opens the user folder described by the specified tag. The opening of the - * folder + * Opens the user folder described by the specified tag. The opening of the folder * is animated relative to the specified View. If the View is null, no animation * is played. */ @@ -713,22 +711,17 @@ public void animateOpen() { } /** - * Opens the user folder described by the specified tag. The opening of the - * folder + * Opens the user folder described by the specified tag. The opening of the folder * is animated relative to the specified View. If the View is null, no animation * is played. */ private void animateOpen(List items, int pageNo) { - if (items == null || items.size() <= 1) { - Log.d(TAG, "Couldn't animate folder open because items is: " + items); + if (!shouldAnimateOpen(items)) { return; } Folder openFolder = getOpen(mActivityContext); - if (openFolder != null && openFolder != this) { - // Close any open folder before opening a folder. - openFolder.close(true); - } + closeOpenFolder(openFolder); mContent.bindItems(items); centerAboutIcon(); @@ -742,7 +735,7 @@ private void animateOpen(List items, int pageNo) { // There was a one-off crash where the folder had a parent already. if (getParent() == null) { dragLayer.addView(this); - mDragController.addDropTarget(this); + getDragController().addDropTarget(this); } else { if (FeatureFlags.IS_STUDIO_BUILD) { Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" @@ -753,12 +746,9 @@ private void animateOpen(List items, int pageNo) { mContent.completePendingPageChanges(); mContent.setCurrentPage(pageNo); - // This is set to true in close(), but isn't reset to false until - // onDropCompleted(). This - // leads to an inconsistent state if you drag out of the folder and drag back in - // without - // dropping. One resulting issue is that replaceFolderWithFinalItem() can be - // called twice. + // This is set to true in close(), but isn't reset to false until onDropCompleted(). This + // leads to an inconsistent state if you drag out of the folder and drag back in without + // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. mDeleteFolderOnDropCompleted = false; cancelRunningAnimations(); @@ -792,10 +782,9 @@ public void onAnimationEnd(Animator animation) { mFolderName.setTranslationX(mContent.mIsRtl ? -translation : translation); mPageIndicator.prepareEntryAnimation(); - // Do not update the flag if we are in drag mode. The flag will be updated, when - // we + // Do not update the flag if we are in drag mode. The flag will be updated, when we // actually drop the icon. - final boolean updateAnimationFlag = !mDragInProgress; + final boolean updateAnimationFlag = !mIsDragInProgress; anim.addListener(new AnimatorListenerAdapter() { @SuppressLint("InlinedApi") @@ -825,17 +814,41 @@ public void onAnimationEnd(Animator animation) { addAnimationStartListeners(anim); // Because t=0 has the folder match the folder icon, we can skip the // first frame and have the same movement one frame earlier. + Log.d("b/311077782", "Folder.animateOpen"); anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration())); anim.start(); - // Make sure the folder picks up the last drag move even if the finger doesn't - // move. - if (mDragController.isDragging()) { - mDragController.forceTouchMove(); + // Make sure the folder picks up the last drag move even if the finger doesn't move. + if (getDragController().isDragging()) { + getDragController().forceTouchMove(); } mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } + /** + * Determines whether we should animate the folder opening. + */ + boolean shouldAnimateOpen(List items) { + if (items == null || items.size() <= 1) { + Log.d(TAG, "Couldn't animate folder open because items is: " + items); + return false; + } + return true; + } + + /** + * If there's a folder already open, we want to close it before opening another one. + */ + @VisibleForTesting + boolean closeOpenFolder(Folder openFolder) { + if (openFolder != null && openFolder != this) { + // Close any open folder before opening a folder. + openFolder.close(true); + return true; + } + return false; + } + @Override protected boolean isOfType(int type) { return (type & TYPE_FOLDER) != 0; @@ -849,7 +862,7 @@ protected void handleClose(boolean animate) { mCurrentAnimator.cancel(); } - if (isEditingName()) { + if (mIsEditingName) { mFolderName.dispatchBackKey(); } @@ -864,8 +877,7 @@ protected void handleClose(boolean animate) { post(this::announceAccessibilityChanges); } - // Notify the accessibility manager that this folder "window" has disappeared - // and no + // Notify the accessibility manager that this folder "window" has disappeared and no // longer occludes the workspace items mActivityContext.getDragLayer().sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); @@ -906,8 +918,10 @@ public void onAnimationStart(Animator animation) { @Override public void onAnimationEnd(Animator animation) { - if (mKeyboardInsetAnimationCallback != null && Utilities.ATLEAST_R) { - setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback); + if (mKeyboardInsetAnimationCallback != null) { + if (Utilities.ATLEAST_R) { + setWindowInsetsAnimationCallback(mKeyboardInsetAnimationCallback); + } } closeComplete(true); announceAccessibilityChanges(); @@ -936,7 +950,7 @@ private void closeComplete(boolean wasAnimated) { if (parent != null) { parent.removeView(this); } - mDragController.removeDropTarget(this); + getDragController().removeDropTarget(this); clearFocus(); if (mFolderIcon != null) { mFolderIcon.setVisibility(View.VISIBLE); @@ -957,12 +971,12 @@ private void closeComplete(boolean wasAnimated) { mRearrangeOnClose = false; } if (getItemCount() <= 1) { - if (!mDragInProgress && !mSuppressFolderDeletion) { + if (!mIsDragInProgress && !mSuppressFolderDeletion) { replaceFolderWithFinalItem(); - } else if (mDragInProgress) { + } else if (mIsDragInProgress) { mDeleteFolderOnDropCompleted = true; } - } else if (!mDragInProgress) { + } else if (!mIsDragInProgress) { mContent.unbindItems(); } mSuppressFolderDeletion = false; @@ -981,8 +995,7 @@ public boolean acceptDrop(DragObject d) { public void onDragEnter(DragObject d) { mPrevTargetRank = -1; mOnExitAlarm.cancelAlarm(); - // Get the area offset such that the folder only closes if half the drag icon - // width + // Get the area offset such that the folder only closes if half the drag icon width // is outside the folder area mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset; } @@ -1083,7 +1096,8 @@ public void completeDragExit() { } } - private void clearDragInfo() { + @VisibleForTesting + void clearDragInfo() { mCurrentDragView = null; mIsExternalDrag = false; } @@ -1106,8 +1120,7 @@ public void onDragExit(DragObject d) { } /** - * When performing an accessibility drop, onDrop is sent immediately after - * onDragEnter. So we + * When performing an accessibility drop, onDrop is sent immediately after onDragEnter. So we * need to complete all transient states based on timers. */ @Override @@ -1120,20 +1133,20 @@ public void prepareAccessibilityDrop() { @Override public void onDropCompleted(final View target, final DragObject d, - final boolean success) { + final boolean success) { if (success) { if (getItemCount() <= 1) { mDeleteFolderOnDropCompleted = true; } - if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { + if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon + && target != this) { replaceFolderWithFinalItem(); } } else { // The drag failed, we need to return the item to the folder ItemInfo info = d.dragInfo; View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) - ? mCurrentDragView - : mContent.createNewView(info); + ? mCurrentDragView : mContent.createNewView(info); ArrayList views = getIconsInReadingOrder(); info.rank = Utilities.boundToRange(info.rank, 0, views.size()); views.add(info.rank, icon); @@ -1157,12 +1170,11 @@ public void onDropCompleted(final View target, final DragObject d, } mDeleteFolderOnDropCompleted = false; - mDragInProgress = false; + mIsDragInProgress = false; mItemAddedBackToSelfViaIcon = false; mCurrentDragView = null; - // Reordering may have occured, and we need to save the new item locations. We - // do this once + // Reordering may have occured, and we need to save the new item locations. We do this once // at the end to prevent unnecessary database operations. updateItemLocationsInDatabaseBatch(false); // Use the item count to check for multi-page as the folder UI may not have @@ -1175,7 +1187,7 @@ public void onDropCompleted(final View target, final DragObject d, } private void updateItemLocationsInDatabaseBatch(boolean isBind) { - FolderGridOrganizer verifier = new FolderGridOrganizer( + FolderGridOrganizer verifier = createFolderGridOrganizer( mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); ArrayList items = new ArrayList<>(); @@ -1201,7 +1213,7 @@ private void updateItemLocationsInDatabaseBatch(boolean isBind) { } public void notifyDrop() { - if (mDragInProgress) { + if (mIsDragInProgress) { mItemAddedBackToSelfViaIcon = true; } } @@ -1225,7 +1237,7 @@ private void centerAboutIcon() { sTempRect.set(mActivityContext.getFolderBoundingBox()); int left = Utilities.boundToRange(centeredLeft, sTempRect.left, sTempRect.right - width); int top = Utilities.boundToRange(centeredTop, sTempRect.top, sTempRect.bottom - height); - int[] inOutPosition = new int[] { left, top }; + int[] inOutPosition = new int[]{left, top}; mActivityContext.updateOpenFolderPosition(inOutPosition, sTempRect, width, height); left = inOutPosition[0]; top = inOutPosition[1]; @@ -1244,28 +1256,41 @@ private void centerAboutIcon() { } protected int getContentAreaHeight() { - DeviceProfile grid = mActivityContext.getDeviceProfile(); - int maxContentAreaHeight = grid.availableHeightPx - grid.getTotalWorkspacePadding().y - - mFooterHeight; - int height = Math.min(maxContentAreaHeight, + int height = Math.min(getMaxContentAreaHeight(), mContent.getDesiredHeight()); return Math.max(height, MIN_CONTENT_DIMEN); } - private int getContentAreaWidth() { + @VisibleForTesting + int getMaxContentAreaHeight() { + DeviceProfile grid = mActivityContext.getDeviceProfile(); + return grid.availableHeightPx - grid.getTotalWorkspacePadding().y + - getFooterHeight(); + } + + @VisibleForTesting + int getContentAreaWidth() { return Math.max(mContent.getDesiredWidth(), MIN_CONTENT_DIMEN); } - private int getFolderWidth() { + @VisibleForTesting + int getFolderWidth() { return getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); } - private int getFolderHeight() { + @VisibleForTesting + int getFolderHeight() { return getFolderHeight(getContentAreaHeight()); } - private int getFolderHeight(int contentAreaHeight) { - return getPaddingTop() + getPaddingBottom() + contentAreaHeight + mFooterHeight; + @VisibleForTesting + int getFolderHeight(int contentAreaHeight) { + return getPaddingTop() + getPaddingBottom() + contentAreaHeight + getFooterHeight(); + } + + @VisibleForTesting + int getFooterHeight() { + return mFooterHeight; } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @@ -1309,8 +1334,7 @@ public boolean isDestroyed() { return mDestroyed; } - // This method keeps track of the first and last item in the folder for the - // purposes + // This method keeps track of the first and last item in the folder for the purposes // of keyboard focus public void updateTextViewFocus() { final View firstChild = mContent.getFirstItem(); @@ -1320,13 +1344,10 @@ public void updateTextViewFocus() { mFolderName.setNextFocusRightId(lastChild.getId()); mFolderName.setNextFocusLeftId(lastChild.getId()); mFolderName.setNextFocusUpId(lastChild.getId()); - // Hitting TAB from the folder name wraps around to the first item on the - // current - // folder page, and hitting SHIFT+TAB from that item wraps back to the folder - // name. + // Hitting TAB from the folder name wraps around to the first item on the current + // folder page, and hitting SHIFT+TAB from that item wraps back to the folder name. mFolderName.setNextFocusForwardId(firstChild.getId()); - // When clicking off the folder when editing the name, this Folder gains focus. - // When + // When clicking off the folder when editing the name, this Folder gains focus. When // pressing an arrow key from that state, give the focus to the first item. this.setNextFocusDownId(firstChild.getId()); this.setNextFocusRightId(firstChild.getId()); @@ -1370,12 +1391,11 @@ public void onDrop(DragObject d, DragOptions options) { } PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo - ? (PendingAddShortcutInfo) d.dragInfo - : null; - WorkspaceItemInfo pasiSi = pasi != null ? pasi.getActivityInfo(launcher).createWorkspaceItemInfo() : null; + ? (PendingAddShortcutInfo) d.dragInfo : null; + WorkspaceItemInfo pasiSi = + pasi != null ? pasi.getActivityInfo(launcher).createWorkspaceItemInfo() : null; if (pasi != null && pasiSi == null) { - // There is no WorkspaceItemInfo, so we have to go through a configuration - // activity. + // There is no WorkspaceItemInfo, so we have to go through a configuration activity. pasi.container = mInfo.id; pasi.rank = mEmptyCellRank; @@ -1440,7 +1460,7 @@ public void onDrop(DragObject d, DragOptions options) { } // Clear the drag info, as it is no longer being dragged. - mDragInProgress = false; + mIsDragInProgress = false; if (mContent.getPageCount() > 1) { // The animation has already been shown while opening the folder. @@ -1459,10 +1479,8 @@ public void onDrop(DragObject d, DragOptions options) { .log(LAUNCHER_ITEM_DROP_COMPLETED); } - // This is used so the item doesn't immediately appear in the folder when added. - // In one case - // we need to create the illusion that the item isn't added back to the folder - // yet, to + // This is used so the item doesn't immediately appear in the folder when added. In one case + // we need to create the illusion that the item isn't added back to the folder yet, to // to correspond to the animation of the icon back into the folder. This is public void hideItem(ItemInfo info) { View v = getViewForInfo(info); @@ -1480,7 +1498,7 @@ public void showItem(ItemInfo info) { @Override public void onAdd(ItemInfo item, int rank) { - FolderGridOrganizer verifier = new FolderGridOrganizer( + FolderGridOrganizer verifier = createFolderGridOrganizer( mActivityContext.getDeviceProfile()).setFolderInfo(mInfo); verifier.updateRankAndPos(item, rank); mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, @@ -1511,7 +1529,8 @@ public void onRemove(List items) { } } - private View getViewForInfo(final ItemInfo item) { + @VisibleForTesting + View getViewForInfo(final ItemInfo item) { return mContent.iterateOverItems((info, view) -> info == item); } @@ -1569,7 +1588,7 @@ public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { mFromLabelState = mInfo.getFromLabelState(); mFromTitle = mInfo.title; - startEditingFolderName(); + post(this::startEditingFolderName); } else { StatsLogger statsLogger = mStatsLogManager.logger() .withItemInfo(mInfo) @@ -1661,8 +1680,7 @@ public void onAlarm(Alarm alarm) { } } - // Compares item position based on rank and position giving priority to the - // rank. + // Compares item position based on rank and position giving priority to the rank. public static final Comparator ITEM_POS_COMPARATOR = new Comparator() { @Override @@ -1703,7 +1721,7 @@ public static Folder getOpen(ActivityContext activityContext) { /** Navigation bar back key or hardware input back key has been issued. */ @Override public void onBackInvoked() { - if (isEditingName()) { + if (mIsEditingName) { mFolderName.dispatchBackKey(); } else { super.onBackInvoked(); @@ -1715,7 +1733,7 @@ public boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { BaseDragLayer dl = (BaseDragLayer) getParent(); - if (isEditingName()) { + if (mIsEditingName) { if (!dl.isEventOverView(mFolderName, ev)) { mFolderName.dispatchBackKey(); return true; @@ -1735,8 +1753,7 @@ public boolean canInterceptEventsInSystemGestureRegion() { } /** - * Alternative to using {@link #getClipToOutline()} as it only works with - * derivatives of + * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of * rounded rect. */ @Override @@ -1763,10 +1780,96 @@ public FolderPagedView getContent() { return mContent; } - /** - * Returns the height of the current folder's bottom edge from the bottom of the - * screen. - */ + @VisibleForTesting + void setItemAddedBackToSelfViaIcon(boolean value) { + mItemAddedBackToSelfViaIcon = value; + } + + @VisibleForTesting + boolean getItemAddedBackToSelfViaIcon() { + return mItemAddedBackToSelfViaIcon; + } + + @VisibleForTesting + void setIsDragInProgress(boolean value) { + mIsDragInProgress = value; + } + + @VisibleForTesting + boolean getIsDragInProgress() { + return mIsDragInProgress; + } + + @VisibleForTesting + View getCurrentDragView() { + return mCurrentDragView; + } + + @VisibleForTesting + void setCurrentDragView(View view) { + mCurrentDragView = view; + } + + @VisibleForTesting + boolean getItemsInvalidated() { + return mItemsInvalidated; + } + + @VisibleForTesting + void setItemsInvalidated(boolean value) { + mItemsInvalidated = value; + } + + @VisibleForTesting + boolean getIsExternalDrag() { + return mIsExternalDrag; + } + + @VisibleForTesting + void setIsExternalDrag(boolean value) { + mIsExternalDrag = value; + } + + public boolean getIsEditingName() { + return mIsEditingName; + } + + @VisibleForTesting + void setIsEditingName(boolean value) { + mIsEditingName = value; + } + + @VisibleForTesting + void setFolderName(FolderNameEditText value) { + mFolderName = value; + } + + @VisibleForTesting + FolderNameEditText getFolderName() { + return mFolderName; + } + + @VisibleForTesting + boolean getIsOpen() { + return mIsOpen; + } + + @VisibleForTesting + void setIsOpen(boolean value) { + mIsOpen = value; + } + + @VisibleForTesting + boolean getRearrangeOnClose() { + return mRearrangeOnClose; + } + + @VisibleForTesting + void setRearrangeOnClose(boolean value) { + mRearrangeOnClose = value; + } + + /** Returns the height of the current folder's bottom edge from the bottom of the screen. */ private int getHeightFromBottom() { BaseDragLayer.LayoutParams layoutParams = (BaseDragLayer.LayoutParams) getLayoutParams(); int folderBottomPx = layoutParams.y + layoutParams.height; @@ -1775,6 +1878,16 @@ private int getHeightFromBottom() { return windowBottomPx - folderBottomPx; } + @VisibleForTesting + boolean getDeleteFolderOnDropCompleted() { + return mDeleteFolderOnDropCompleted; + } + + @VisibleForTesting + void setDeleteFolderOnDropCompleted(boolean value) { + mDeleteFolderOnDropCompleted = value; + } + /** * Save this listener for the special case of when we update the state and concurrently * add another listener to {@link #mOnFolderStateChangedListeners} to avoid a @@ -1784,7 +1897,13 @@ public void setPriorityOnFolderStateChangedListener(OnFolderStateChangedListener mPriorityOnFolderStateChangedListener = listener; } - private void setState(@FolderState int newState) { + @VisibleForTesting + int getState() { + return mState; + } + + @VisibleForTesting + void setState(@FolderState int newState) { mState = newState; if (mPriorityOnFolderStateChangedListener != null) { mPriorityOnFolderStateChangedListener.onFolderStateChanged(mState); @@ -1796,6 +1915,60 @@ private void setState(@FolderState int newState) { } } + @VisibleForTesting + Alarm getOnExitAlarm() { + return mOnExitAlarm; + } + + @VisibleForTesting + void setOnExitAlarm(Alarm value) { + mOnExitAlarm = value; + } + + @VisibleForTesting + Alarm getReorderAlarm() { + return mReorderAlarm; + } + + @VisibleForTesting + void setReorderAlarm(Alarm value) { + mReorderAlarm = value; + } + + @VisibleForTesting + Alarm getOnScrollHintAlarm() { + return mOnScrollHintAlarm; + } + + @VisibleForTesting + void setOnScrollHintAlarm(Alarm value) { + mOnScrollHintAlarm = value; + } + + @VisibleForTesting + Alarm getScrollPauseAlarm() { + return mScrollPauseAlarm; + } + + @VisibleForTesting + void setScrollPauseAlarm(Alarm value) { + mScrollPauseAlarm = value; + } + + @VisibleForTesting + int getScrollHintDir() { + return mScrollHintDir; + } + + @VisibleForTesting + void setScrollHintDir(int value) { + mScrollHintDir = value; + } + + @VisibleForTesting + int getScrollAreaOffset() { + return mScrollAreaOffset; + } /** * Adds the provided listener to the running list of Folder listeners * {@link #mOnFolderStateChangedListeners} @@ -1816,4 +1989,4 @@ public interface OnFolderStateChangedListener { /** See {@link Folder.FolderState} */ void onFolderStateChanged(@FolderState int newState); } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 8a2bcdabcfa..5241fe40624 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -21,6 +21,7 @@ import static com.android.launcher3.BubbleTextView.TEXT_ALPHA_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -49,9 +50,11 @@ import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer; import com.androidinternal.graphics.ColorUtils; +import com.patrykmichalik.opto.core.PreferenceExtensionsKt; import java.util.List; +import app.lawnchair.theme.color.ColorOption; import app.lawnchair.theme.color.tokens.ColorTokens; import app.lawnchair.util.LawnchairUtilsKt; @@ -64,6 +67,7 @@ */ public class FolderAnimationManager { + private static final float EXTRA_FOLDER_REVEAL_RADIUS_PERCENTAGE = 0.125F; private static final int FOLDER_NAME_ALPHA_DURATION = 32; private static final int LARGE_FOLDER_FOOTER_DURATION = 128; @@ -102,7 +106,7 @@ public FolderAnimationManager(Folder folder, boolean isOpening) { mContext = folder.getContext(); mDeviceProfile = folder.mActivityContext.getDeviceProfile(); - mPreviewVerifier = new FolderGridOrganizer(mDeviceProfile); + mPreviewVerifier = createFolderGridOrganizer(mDeviceProfile); mIsOpening = isOpening; @@ -162,12 +166,9 @@ public AnimatorSet getAnimator() { mFolder.mFooter.setPivotX(0); mFolder.mFooter.setPivotY(0); - // We want to create a small X offset for the preview items, so that they follow their - // expected path to their final locations. ie. an icon should not move right, if it's final - // location is to its left. This value is arbitrarily defined. - int previewItemOffsetX = (int) (previewSize / 2); + int previewItemOffsetX = 0; if (Utilities.isRtl(mContext.getResources())) { - previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX); + previewItemOffsetX = (int) (lp.width * initialScale - initialSize); } final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale); @@ -183,9 +184,17 @@ public AnimatorSet getAnimator() { final float yDistance = initialY - lp.y; // Set up the Folder background. - final int previewColor = ColorTokens.FolderPreviewColor.resolveColor(mContext); - final int initialColor = ColorUtils.setAlphaComponent(previewColor, LawnchairUtilsKt.getFolderPreviewAlpha(mContext)); - final int finalColor = ColorTokens.FolderBackgroundColor.resolveColor(mContext); + int previewColor = ColorTokens.FolderPreviewColor.resolveColor(mContext); + int initialColor = ColorUtils.setAlphaComponent(previewColor, LawnchairUtilsKt.getFolderPreviewAlpha(mContext)); + int finalColor = ColorTokens.FolderBackgroundColor.resolveColor(mContext); + + ColorOption colorOption = PreferenceExtensionsKt.firstBlocking(mFolder.preferenceManager2.getFolderColor()); + int folderColor = colorOption.getColorPreferenceEntry().getLightColor().invoke(mContext); + + if (folderColor != 0) { + initialColor = folderColor; + finalColor = folderColor; + } mFolderBackground.mutate(); mFolderBackground.setColor(mIsOpening ? initialColor : finalColor); @@ -244,36 +253,26 @@ public AnimatorSet getAnimator() { play(a, shapeDelegate.createRevealAnimator( mFolder, startRect, endRect, finalRadius, !mIsOpening)); - // Create reveal animator for the folder content (capture the top 4 icons 2x2) - int width = mDeviceProfile.folderCellLayoutBorderSpacePx.x - + mDeviceProfile.folderCellWidthPx * 2; - int rtlExtraWidth = 0; - int height = mDeviceProfile.folderCellLayoutBorderSpacePx.y - + mDeviceProfile.folderCellHeightPx * 2; int page = mIsOpening ? mContent.getCurrentPage() : mContent.getDestinationPage(); - // In RTL we want to move to the last 2 columns of icons in the folder. if (Utilities.isRtl(mContext.getResources())) { page = (mContent.getPageCount() - 1) - page; - CellLayout clAtPage = mContent.getPageAt(page); - if (clAtPage != null) { - int numExtraRows = clAtPage.getCountX() - 2; - rtlExtraWidth = (int) Math.max(numExtraRows * (mDeviceProfile.folderCellWidthPx - + mDeviceProfile.folderCellLayoutBorderSpacePx.x), rtlExtraWidth); - } } - int left = mContent.getPaddingLeft() + page * lp.width; + int left = page * lp.width; + + int extraRadius = (int) ((mDeviceProfile.folderIconSizePx / initialScale) + * EXTRA_FOLDER_REVEAL_RADIUS_PERCENTAGE); Rect contentStart = new Rect( - left + rtlExtraWidth, - 0, - left + width + mContent.getPaddingRight() + rtlExtraWidth, - height); + (int) (left + (startRect.left / initialScale)) - extraRadius, + (int) (startRect.top / initialScale) - extraRadius, + (int) (left + (startRect.right / initialScale)) + extraRadius, + (int) (startRect.bottom / initialScale) + extraRadius); Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height); play(a, shapeDelegate.createRevealAnimator( mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening)); // Fade in the folder name, as the text can overlap the icons when grid size is small. - mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f); - play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1), + mFolder.getFolderName().setAlpha(mIsOpening ? 0f : 1f); + play(a, getAnimator(mFolder.getFolderName(), View.ALPHA, 0, 1), mIsOpening ? FOLDER_NAME_ALPHA_DURATION : 0, mIsOpening ? mDuration - FOLDER_NAME_ALPHA_DURATION : FOLDER_NAME_ALPHA_DURATION); @@ -334,7 +333,7 @@ public void onAnimationEnd(Animator animation) { mFolder.mFooter.setScaleX(1f); mFolder.mFooter.setScaleY(1f); mFolder.mFooter.setTranslationX(0f); - mFolder.mFolderName.setAlpha(1f); + mFolder.getFolderName().setAlpha(1f); mFolder.setClipChildren(mFolderClipChildren); mFolder.setClipToPadding(mFolderClipToPadding); diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java index 593673d4bdf..6219651e8b5 100644 --- a/src/com/android/launcher3/folder/FolderGridOrganizer.java +++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java @@ -47,12 +47,19 @@ public class FolderGridOrganizer { /** * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work. */ - public FolderGridOrganizer(DeviceProfile profile) { - mMaxCountX = profile.numFolderColumns; - mMaxCountY = profile.numFolderRows; + public FolderGridOrganizer(int maxCountX, int maxCountY) { + mMaxCountX = maxCountX; + mMaxCountY = maxCountY; mMaxItemsPerPage = mMaxCountX * mMaxCountY; } + /** + * Creates a FolderGridOrganizer for the given DeviceProfile + */ + public static FolderGridOrganizer createFolderGridOrganizer(DeviceProfile profile) { + return new FolderGridOrganizer(profile.numFolderColumns, profile.numFolderRows); + } + /** * Updates the organizer with the provided folder info */ @@ -194,6 +201,7 @@ public boolean isItemInPreview(int page, int rank) { int row = rank / mCountX; return col < PREVIEW_MAX_COLUMNS && row < PREVIEW_MAX_ROWS; } + // If we have less than 4 items do this return rank < MAX_NUM_ITEMS_IN_PREVIEW; } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index a455bd26330..4c2180cecd7 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -21,6 +21,7 @@ import static com.android.launcher3.Flags.enableCursorHoverStates; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELING_SKIPPED_EMPTY_PRIMARY; @@ -84,7 +85,7 @@ import com.android.launcher3.util.MultiTranslateDelegate; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.IconLabelDotView; +import com.android.launcher3.views.FloatingIconViewCompanion; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; @@ -94,29 +95,25 @@ /** * An icon that can appear on in the workspace representing an {@link Folder}. */ -public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView, +public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion, DraggableView, Reorderable { private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); - @Thunk - ActivityContext mActivity; - @Thunk - Folder mFolder; + @Thunk ActivityContext mActivity; + @Thunk Folder mFolder; public FolderInfo mInfo; private CheckLongPressHelper mLongPressHelper; static final int DROP_IN_ANIMATION_DURATION = 400; - // Flag whether the folder should open itself when an item is dragged over is - // enabled. + // Flag whether the folder should open itself when an item is dragged over is enabled. public static final boolean SPRING_LOADING_ENABLED = true; // Delay when drag enters until the folder opens, in miliseconds. private static final int ON_OPEN_DELAY = 800; - @Thunk - BubbleTextView mFolderName; + @Thunk BubbleTextView mFolderName; PreviewBackground mBackground = new PreviewBackground(getContext()); private boolean mBackgroundIsVisible = true; @@ -144,8 +141,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private float mScaleForReorderBounce = 1f; - private static final Property DOT_SCALE_PROPERTY = new Property(Float.TYPE, - "dotScale") { + private static final Property DOT_SCALE_PROPERTY + = new Property(Float.TYPE, "dotScale") { @Override public Float get(FolderIcon folderIcon) { return folderIcon.mDotScale; @@ -176,7 +173,7 @@ private void init() { } public static FolderIcon inflateFolderAndIcon(int resId, - T activityContext, ViewGroup group, FolderInfo folderInfo) { + T activityContext, ViewGroup group, FolderInfo folderInfo) { Folder folder = Folder.fromXml(activityContext); FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo); @@ -190,7 +187,7 @@ public static FolderIcon inflateFolderAndI * Builds a FolderIcon to be added to the Launcher */ public static FolderIcon inflateIcon(int resId, ActivityContext activity, - @Nullable ViewGroup group, FolderInfo folderInfo) { + @Nullable ViewGroup group, FolderInfo folderInfo) { @SuppressWarnings("all") // suppress dead code warning final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION; if (error) { @@ -229,7 +226,7 @@ public static FolderIcon inflateIcon(int resId, ActivityContext activity, icon.setAccessibilityDelegate(activity.getAccessibilityDelegate()); - icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile()); + icon.mPreviewVerifier = createFolderGridOrganizer(activity.getDeviceProfile()); icon.mPreviewVerifier.setFolderInfo(folderInfo); icon.updatePreviewItems(false); @@ -284,8 +281,7 @@ public void removeItem(ItemInfo item, boolean animate) { } public void onDragEnter(ItemInfo dragInfo) { - if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) - return; + if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return; CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams(); CellLayout cl = (CellLayout) getParent().getParent(); @@ -310,8 +306,8 @@ public Drawable prepareCreateAnimation(final View destView) { } public void performCreateAnimation(final ItemInfo destInfo, final View destView, - final ItemInfo srcInfo, final DragObject d, Rect dstRect, - float scaleRelativeToDragLayer) { + final ItemInfo srcInfo, final DragObject d, Rect dstRect, + float scaleRelativeToDragLayer) { final DragView srcView = d.dragView; prepareCreateAnimation(destView); addItem(destInfo); @@ -337,14 +333,12 @@ public void onDragExit() { } private void onDrop(final ItemInfo item, DragObject d, Rect finalRect, - float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) { + float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) { item.cellX = -1; item.cellY = -1; DragView animateView = d.dragView; - // Typically, the animateView corresponds to the DragView; however, if this is - // being done - // after a configuration activity (ie. for a Shortcut being dragged from - // AllApps) we + // Typically, the animateView corresponds to the DragView; however, if this is being done + // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we // will not have a view to animate if (animateView != null && mActivity instanceof Launcher) { final Launcher launcher = (Launcher) mActivity; @@ -353,8 +347,7 @@ private void onDrop(final ItemInfo item, DragObject d, Rect finalRect, if (to == null) { to = new Rect(); Workspace workspace = launcher.getWorkspace(); - // Set cellLayout and this to it's final state to compute final animation - // locations + // Set cellLayout and this to it's final state to compute final animation locations workspace.setFinalTransitionTransform(); float scaleX = getScaleX(); float scaleY = getScaleY(); @@ -426,8 +419,7 @@ private void onDrop(final ItemInfo item, DragObject d, Rect finalRect, mFolder.hideItem(item); - if (!itemAdded) - mPreviewItemManager.hidePreviewItem(index, true); + if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true); FolderNameInfos nameInfos = new FolderNameInfos(); Executors.MODEL_EXECUTOR.post(() -> { @@ -469,7 +461,7 @@ public void setLabelSuggestion(FolderNameInfos nameInfos, InstanceId instanceId) mInfo.setTitle(newTitle, mFolder.mLauncherDelegate.getModelWriter()); onTitleChanged(mInfo.title); - mFolder.mFolderName.setText(mInfo.title); + mFolder.getFolderName().setText(mInfo.title); // Logging for folder creation flow StatsLogManager.newInstance(getContext()).logger() @@ -483,12 +475,13 @@ public void setLabelSuggestion(FolderNameInfos nameInfos, InstanceId instanceId) .log(LAUNCHER_FOLDER_AUTO_LABELED); } + public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) { ItemInfo item; if (d.dragInfo instanceof WorkspaceItemFactory) { // Came from all apps -- make a copy item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext()); - } else if (d.dragSource instanceof BaseItemDragListener) { + } else if (d.dragSource instanceof BaseItemDragListener){ // Came from a different window -- make a copy if (d.dragInfo instanceof AppPairInfo) { // dragged item is app pair @@ -611,8 +604,7 @@ public PreviewItemManager getPreviewItemManager() { protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (!mBackgroundIsVisible) - return; + if (!mBackgroundIsVisible) return; mPreviewItemManager.recomputePreviewDrawingParams(); @@ -620,8 +612,7 @@ protected void dispatchDraw(Canvas canvas) { mBackground.drawBackground(canvas); } - if (mCurrentPreviewItems.isEmpty() && !mAnimating) - return; + if (mCurrentPreviewItems.isEmpty() && !mAnimating) return; mPreviewItemManager.draw(canvas); @@ -635,8 +626,7 @@ protected void dispatchDraw(Canvas canvas) { public void drawDot(Canvas canvas) { if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) { Rect iconBounds = mDotParams.iconBounds; - // FolderIcon draws the icon to be top-aligned (with padding) & - // horizontally-centered + // FolderIcon draws the icon to be top-aligned (with padding) & horizontally-centered int iconSize = mActivity.getDeviceProfile().iconSizePx; iconBounds.left = (getWidth() - iconSize) / 2; iconBounds.right = iconBounds.left + iconSize; @@ -749,8 +739,7 @@ && shouldIgnoreTouchDown(event.getX(), event.getY())) { return false; } - // Call the superclass onTouchEvent first, because sometimes it changes the - // state to + // Call the superclass onTouchEvent first, because sometimes it changes the state to // isPressed() on an ACTION_UP super.onTouchEvent(event); mLongPressHelper.onTouchEvent(event); @@ -847,21 +836,17 @@ public void onHoverChanged(boolean hovered) { } /** - * Interface that provides callbacks to a parent ViewGroup that hosts this - * FolderIcon. + * Interface that provides callbacks to a parent ViewGroup that hosts this FolderIcon. */ public interface FolderIconParent { /** - * Tells the FolderIconParent to draw a "leave-behind" when the Folder is open - * and leaving a + * Tells the FolderIconParent to draw a "leave-behind" when the Folder is open and leaving a * gap where the FolderIcon would be when the Folder is closed. */ void drawFolderLeaveBehindForIcon(FolderIcon child); - /** - * Tells the FolderIconParent to stop drawing the "leave-behind" as the Folder - * is closed. + * Tells the FolderIconParent to stop drawing the "leave-behind" as the Folder is closed. */ void clearFolderLeaveBehind(FolderIcon child); } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 8eaa0dceab2..6baba99f56f 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -103,7 +103,7 @@ public FolderPagedView(Context context, AttributeSet attrs) { super(context, attrs); ActivityContext activityContext = ActivityContext.lookupContext(context); DeviceProfile profile = activityContext.getDeviceProfile(); - mOrganizer = new FolderGridOrganizer(profile); + mOrganizer = FolderGridOrganizer.createFolderGridOrganizer(profile); mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java index 4802e6461ef..f005c6320a6 100644 --- a/src/com/android/launcher3/graphics/IconShape.java +++ b/src/com/android/launcher3/graphics/IconShape.java @@ -74,7 +74,7 @@ private IconShape(Context context) { pickBestShape(context); } - public static ShapeDelegate getShape() { + public ShapeDelegate getShape() { return mDelegate; } diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java index 09e98588135..1b8ddaf0a7f 100644 --- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java +++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java @@ -104,12 +104,10 @@ public boolean addToMemCache() { * Launcher specific checks */ public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) { - if (!WIDGETS_ENABLED) { - return null; - } try { - return context.getSystemService(LauncherApps.class) + Drawable icon = context.getSystemService(LauncherApps.class) .getShortcutIconDrawable(shortcutInfo, density); + return CustomAdaptiveIconDrawable.wrap(icon); } catch (SecurityException | IllegalStateException | NullPointerException e) { Log.e(TAG, "Failed to get shortcut icon", e); return null; diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index 55dd121589e..6a691723f81 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -417,15 +417,8 @@ private void inflateAsyncAndBind( ModelWriter writer = mApp.getModel() .getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null); - List> bindItems = null; - if (Utilities.ATLEAST_U) { - bindItems = items.stream().map(i -> - Pair.create(i, inflater.inflateItem(i, writer, null))).toList(); - } else { - bindItems = items.stream().map(i -> + List> finalBindItems = items.stream().map(i -> Pair.create(i, inflater.inflateItem(i, writer, null))).collect(Collectors.toList()); - } - List> finalBindItems = bindItems; executeCallbacksTask(c -> c.bindInflatedItems(finalBindItems), executor); } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index eaf342df2ec..d71a8486fff 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -23,6 +23,7 @@ import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE; import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE; import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; +import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; @@ -576,17 +577,9 @@ private void queryPinnedShortcutsForUnlockedUsers(Context context, */ @WorkerThread private void processFolderItems() { - // Sort the folder items, update ranks, and make sure all preview items are high - // res. - List verifiers; - - if (Utilities.ATLEAST_U) { - verifiers = mApp.getInvariantDeviceProfile().supportedProfiles - .stream().map(FolderGridOrganizer::new).toList(); - } else { - verifiers = mApp.getInvariantDeviceProfile().supportedProfiles - .stream().map(FolderGridOrganizer::new).collect(Collectors.toList()); - } + // Sort the folder items, update ranks, and make sure all preview items are high res. + List verifiers = mApp.getInvariantDeviceProfile().supportedProfiles + .stream().map(FolderGridOrganizer::createFolderGridOrganizer).collect(Collectors.toList()); for (CollectionInfo collection : mBgDataModel.collections) { if (!(collection instanceof FolderInfo folder)) { continue; diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt index b3648df55fc..76932176767 100644 --- a/src/com/android/launcher3/model/ModelTaskController.kt +++ b/src/com/android/launcher3/model/ModelTaskController.kt @@ -19,7 +19,6 @@ package com.android.launcher3.model import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherModel import com.android.launcher3.LauncherModel.CallbackTask -import com.android.launcher3.Utilities import com.android.launcher3.celllayout.CellPosMapper import com.android.launcher3.model.BgDataModel.FixedContainerItems import com.android.launcher3.model.data.ItemInfo @@ -54,12 +53,7 @@ class ModelTaskController( fun bindUpdatedWorkspaceItems(allUpdates: List) { // Bind workspace items - val workspaceUpdates: MutableList - if (Utilities.ATLEAST_U) { - workspaceUpdates = allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList() - } else { - workspaceUpdates = allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.collect(Collectors.toList()) - } + val workspaceUpdates: MutableList = allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.collect(Collectors.toList()) if (workspaceUpdates.isNotEmpty()) { scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) } } diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index 6c8747dace4..34fdc1c17c2 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -94,12 +94,7 @@ public synchronized ArrayList getFilteredWidgetsListForPic Stream widgetItems = entry.getValue() .stream() .filter(widgetItemFilter); - List widgetItemsList; - if (Utilities.ATLEAST_U) { - widgetItemsList = widgetItems.toList(); - } else { - widgetItemsList = widgetItems.collect(toList()); - }; + List widgetItemsList = widgetItems.collect(toList()); if (!widgetItemsList.isEmpty()) { String sectionName = (pkgItem.title == null) ? "" : indexer.computeSectionName(pkgItem.title); diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 93f8c7554d7..0ae7b546f6e 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -51,6 +51,7 @@ import com.android.launcher3.Workspace; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.AllAppsContainer; +import com.android.launcher3.logger.LauncherAtom.Attribute; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; import com.android.launcher3.logger.LauncherAtom.PredictionContainer; import com.android.launcher3.logger.LauncherAtom.SettingsContainer; @@ -67,6 +68,9 @@ import com.android.launcher3.util.UserIconInfo; import com.android.systemui.shared.system.SysUiStatsLog; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Optional; import app.lawnchair.LawnchairApp; @@ -79,9 +83,6 @@ public class ItemInfo { public static final boolean DEBUG = false; public static final int NO_ID = -1; - // An id that doesn't match any item, including predicted apps with have an - // id=NO_ID - public static final int NO_MATCHING_ID = Integer.MIN_VALUE; /** Hidden field Settings.Secure.NAV_BAR_KIDS_MODE */ private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor("nav_bar_kids_mode"); @@ -114,8 +115,7 @@ public class ItemInfo { /** * The id of the container that holds this item. For the desktop, this will be * {@link Favorites#CONTAINER_DESKTOP}. For the all applications folder it - * will be {@link #NO_ID} (since it is not stored in the settings DB). For user - * folders + * will be {@link #NO_ID} (since it is not stored in the settings DB). For user folders * it will be the id of the folder. */ public int container = NO_ID; @@ -169,8 +169,7 @@ public class ItemInfo { public CharSequence title; /** - * Optionally set: The appTitle might e.g. be different if {@code title} is used - * to + * Optionally set: The appTitle might e.g. be different if {@code title} is used to * display progress (e.g. Downloading..). */ @Nullable @@ -183,8 +182,7 @@ public class ItemInfo { public CharSequence contentDescription; /** - * When the instance is created using {@link #copyFrom}, this field is used to - * keep track of + * When the instance is created using {@link #copyFrom}, this field is used to keep track of * original {@link ComponentName}. */ @Nullable @@ -193,6 +191,12 @@ public class ItemInfo { @NonNull public UserHandle user; + @NonNull + private ExtendedContainers mExtendedContainers = ExtendedContainers.getDefaultInstance(); + + @NonNull + private List mAttributeList = Collections.EMPTY_LIST; + public ItemInfo() { user = Process.myUserHandle(); } @@ -239,8 +243,7 @@ public final ComponentKey getComponentKey() { /** * Returns this item's package name. * - * Prioritizes the component package name, then uses the intent package name as - * a fallback. + * Prioritizes the component package name, then uses the intent package name as a fallback. * This ensures deep shortcuts are supported. */ @Nullable @@ -251,8 +254,8 @@ public String getTargetPackage() { return component != null ? component.getPackageName() : intent != null - ? intent.getPackage() - : null; + ? intent.getPackage() + : null; } public void writeToValues(@NonNull final ContentWriter writer) { @@ -348,8 +351,7 @@ public boolean shouldUseBackgroundAnimation() { } /** - * Creates {@link LauncherAtom.ItemInfo} with important fields and parent - * container info. + * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. */ @NonNull public LauncherAtom.ItemInfo buildProto() { @@ -357,8 +359,7 @@ public LauncherAtom.ItemInfo buildProto() { } /** - * Creates {@link LauncherAtom.ItemInfo} with important fields and parent - * container info. + * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. */ @NonNull public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) { @@ -408,7 +409,8 @@ public LauncherAtom.ItemInfo buildProto(@Nullable final CollectionInfo cInfo) { break; } if (cInfo != null) { - LauncherAtom.FolderContainer.Builder folderBuilder = LauncherAtom.FolderContainer.newBuilder(); + LauncherAtom.FolderContainer.Builder folderBuilder = + LauncherAtom.FolderContainer.newBuilder(); folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId); switch (cInfo.container) { @@ -442,6 +444,7 @@ protected LauncherAtom.ItemInfo.Builder getDefaultItemInfoBuilder() { } UserCache.INSTANCE.executeIfCreated(cache -> itemBuilder.setUserType(getUserType(cache.getUserInfo(user)))); itemBuilder.setRank(rank); + itemBuilder.addAllItemAttributes(mAttributeList); return itemBuilder; } @@ -457,7 +460,7 @@ public ContainerInfo getContainerInfo() { .build(); case CONTAINER_HOTSEAT_PREDICTION: return ContainerInfo.newBuilder().setPredictedHotseatContainer( - LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId)) + LauncherAtom.PredictedHotseatContainer.newBuilder().setIndex(screenId)) .build(); case CONTAINER_DESKTOP: return ContainerInfo.newBuilder() @@ -500,7 +503,7 @@ public ContainerInfo getContainerInfo() { default: if (container <= EXTENDED_CONTAINERS) { return ContainerInfo.newBuilder() - .setExtendedContainers(getExtendedContainer()) + .setExtendedContainers(mExtendedContainers) .build(); } } @@ -508,13 +511,21 @@ public ContainerInfo getContainerInfo() { } /** - * Returns non-AOSP container wrapped by {@link ExtendedContainers} object. - * Should be overridden - * by build variants. + * Sets extra container info wrapped by {@link ExtendedContainers} object. */ - @NonNull - protected ExtendedContainers getExtendedContainer() { - return ExtendedContainers.getDefaultInstance(); + public void setExtendedContainers(@NonNull ExtendedContainers extendedContainers) { + mExtendedContainers = extendedContainers; + } + + /** + * Adds extra attributes to be added during logs + */ + public void addLogAttributes(List attributeList) { + if (mAttributeList.isEmpty()) { + mAttributeList = new ArrayList<>(attributeList); + } else { + mAttributeList.addAll(attributeList); + } } /** @@ -531,10 +542,18 @@ public ItemInfo makeShallowCopy() { * Sets the title of the item and writes to DB model if needed. */ public void setTitle(@Nullable final CharSequence title, - @Nullable final ModelWriter modelWriter) { + @Nullable final ModelWriter modelWriter) { this.title = title; } + /** + * Returns a string ID that is stable for a user session, but may not be persisted + */ + @Nullable + public Object getStableId() { + return getComponentKey(); + } + private int getUserType(UserIconInfo info) { if (info == null) { return SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_UNKNOWN; @@ -550,4 +569,4 @@ private int getUserType(UserIconInfo info) { return SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_UNKNOWN; } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 47ce39e4094..b093fe73722 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -31,6 +31,8 @@ import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID; +import static java.util.stream.Collectors.toList; + import android.app.backup.BackupManager; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; @@ -178,7 +180,7 @@ private static List existingDbs() { // phone return LauncherFiles.GRID_DB_FILES.stream() .filter(dbName -> new File(dbName).exists()) - .toList(); + .collect(toList()); } /** diff --git a/src/com/android/launcher3/util/StableViewInfo.kt b/src/com/android/launcher3/util/StableViewInfo.kt new file mode 100644 index 00000000000..34e3fc663f6 --- /dev/null +++ b/src/com/android/launcher3/util/StableViewInfo.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 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.launcher3.util + +import android.os.IBinder +import com.android.launcher3.model.data.ItemInfo +import com.android.launcher3.model.data.ItemInfo.NO_ID + +/** Info parameters that can be used to identify a Launcher object */ +data class StableViewInfo(val itemId: Int, val containerId: Int, val stableId: Any) { + + fun matches(info: ItemInfo?) = + info != null && + itemId == info.id && + containerId == info.container && + stableId == info.stableId + + companion object { + + private fun ItemInfo.toStableViewInfo() = + stableId?.let { sId -> + if (id != NO_ID || container != NO_ID) StableViewInfo(id, container, sId) else null + } + + /** + * Return a new launch cookie for the activity launch if supported. + * + * @param info the item info for the launch + */ + @JvmStatic + fun toLaunchCookie(info: ItemInfo?) = + info?.toStableViewInfo()?.let { ObjectWrapper.wrap(it) } + + /** + * Unwraps the binder and returns the first non-null StableViewInfo in the list or null if + * none can be found + */ + @JvmStatic + fun fromLaunchCookies(launchCookies: List) = + launchCookies.firstNotNullOfOrNull { ObjectWrapper.unwrap(it) } + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/views/BubbleTextHolder.java b/src/com/android/launcher3/views/BubbleTextHolder.java index 84f8049dedf..d2ae93b5be3 100644 --- a/src/com/android/launcher3/views/BubbleTextHolder.java +++ b/src/com/android/launcher3/views/BubbleTextHolder.java @@ -20,7 +20,7 @@ /** * Views that contain {@link BubbleTextView} should implement this interface. */ -public interface BubbleTextHolder extends IconLabelDotView { +public interface BubbleTextHolder extends FloatingIconViewCompanion { BubbleTextView getBubbleText(); @Override diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index cca2719ea40..c6642f36e91 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -22,7 +22,7 @@ import static com.android.launcher3.Utilities.getFullDrawable; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible; +import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible; import android.animation.Animator; import android.content.Context; @@ -175,8 +175,9 @@ public void update(float alpha, RectF rect, float progress, float shapeProgressS mLauncher.getDeviceProfile(), taskViewDrawAlpha); if (mFadeOutView != null) { - // The alpha goes from 1 to 0 when progress is 0 and 0.33 respectively. - mFadeOutView.setAlpha(1 - Math.min(1f, mapToRange(progress, 0, 0.33f, 0, 1, LINEAR))); + // The alpha goes from 1 to 0 when progress is 0 and 0.15 respectively. + // This value minimizes view display time while still allowing the view to fade out. + mFadeOutView.setAlpha(1 - Math.min(1f, mapToRange(progress, 0, 0.15f, 0, 1, LINEAR))); } } @@ -252,11 +253,14 @@ private static void getLocationBoundsForView(Launcher launcher, View v, boolean public static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, RectF outRect, Rect outViewBounds) { boolean ignoreTransform = !isOpening; - if (v instanceof BubbleTextHolder) { - v = ((BubbleTextHolder) v).getBubbleText(); + if (v instanceof DeepShortcutView dsv) { + v = dsv.getIconView(); ignoreTransform = false; - } else if (v.getParent() instanceof DeepShortcutView) { - v = ((DeepShortcutView) v.getParent()).getIconView(); + } else if (v.getParent() instanceof DeepShortcutView dsv) { + v = dsv.getIconView(); + ignoreTransform = false; + } else if (v instanceof BubbleTextHolder bth) { + v = bth.getBubbleText(); ignoreTransform = false; } if (v == null) { @@ -297,10 +301,10 @@ private static void getIconResult(Launcher l, View originalView, ItemInfo info, Drawable badge = null; if (info instanceof SystemShortcut) { - if (originalView instanceof ImageView) { - drawable = ((ImageView) originalView).getDrawable(); - } else if (originalView instanceof DeepShortcutView) { - drawable = ((DeepShortcutView) originalView).getIconView().getBackground(); + if (originalView instanceof ImageView iv) { + drawable = iv.getDrawable(); + } else if (originalView instanceof DeepShortcutView dsv) { + drawable = dsv.getIconView().getBackground(); } else { drawable = originalView.getBackground(); } @@ -515,6 +519,10 @@ public void onAnimationStart(Animator animator) { // When closing an app, we want the item on the workspace to be invisible immediately updateViewsVisibility(false /* isVisible */); } + if (mFadeOutView instanceof FloatingIconViewCompanion fivc) { + fivc.setForceHideDot(true); + fivc.setForceHideRing(true); + } } @Override @@ -652,6 +660,10 @@ public static FloatingIconView getFloatingIconView(Launcher launcher, View origi if (view.mFadeOutView != null) { view.mFadeOutView.setAlpha(1f); } + if (view.mFadeOutView instanceof FloatingIconViewCompanion fivc) { + fivc.setForceHideDot(false); + fivc.setForceHideRing(false); + } if (hideOriginal) { view.updateViewsVisibility(true /* isVisible */); @@ -673,10 +685,10 @@ public static FloatingIconView getFloatingIconView(Launcher launcher, View origi private void updateViewsVisibility(boolean isVisible) { if (mOriginalIcon != null) { - setIconAndDotVisible(mOriginalIcon, isVisible); + setPropertiesVisible(mOriginalIcon, isVisible); } if (mMatchVisibilityView != null) { - setIconAndDotVisible(mMatchVisibilityView, isVisible); + setPropertiesVisible(mMatchVisibilityView, isVisible); } } diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/FloatingIconViewCompanion.java similarity index 52% rename from src/com/android/launcher3/views/IconLabelDotView.java rename to src/com/android/launcher3/views/FloatingIconViewCompanion.java index e9113cf874d..bcf39e0e82f 100644 --- a/src/com/android/launcher3/views/IconLabelDotView.java +++ b/src/com/android/launcher3/views/FloatingIconViewCompanion.java @@ -18,21 +18,28 @@ import android.view.View; /** - * A view that has an icon, label, and notification dot. + * A view that can be drawn (in some capacity) via) {@link FloatingIconView}. + * This interface allows us to hide certain properties of the view that the FloatingIconView + * cannot draw, which allows us to make a seamless handoff between the FloatingIconView and + * the companion view. */ -public interface IconLabelDotView { +public interface FloatingIconViewCompanion { void setIconVisible(boolean visible); void setForceHideDot(boolean hide); + default void setForceHideRing(boolean hide) {} + default void resetIconScale(boolean shouldReset) {} /** * Sets the visibility of icon and dot of the view */ - static void setIconAndDotVisible(View view, boolean visible) { - if (view instanceof IconLabelDotView) { - ((IconLabelDotView) view).setIconVisible(visible); - ((IconLabelDotView) view).setForceHideDot(!visible); + static void setPropertiesVisible(View view, boolean visible) { + if (view instanceof FloatingIconViewCompanion) { + ((FloatingIconViewCompanion) view).setIconVisible(visible); + ((FloatingIconViewCompanion) view).setForceHideDot(!visible); + ((FloatingIconViewCompanion) view).setForceHideRing(!visible); + ((FloatingIconViewCompanion) view).resetIconScale(true); } else { view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java index cab798215c2..07cc84c133f 100644 --- a/src/com/android/launcher3/views/FloatingSurfaceView.java +++ b/src/com/android/launcher3/views/FloatingSurfaceView.java @@ -15,9 +15,8 @@ */ package com.android.launcher3.views; -import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView; -import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible; +import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible; import android.content.Context; import android.graphics.Canvas; @@ -106,7 +105,9 @@ private void removeViewFromParent() { private void removeViewImmediate() { // Cancel any pending remove Executors.MAIN_EXECUTOR.getHandler().removeCallbacks(mRemoveViewRunnable); - removeViewFromParent(); + if (isAttachedToWindow()) { + removeViewFromParent(); + } } /** @@ -160,7 +161,7 @@ private void updateIconLocation() { if (mContract == null) { return; } - View icon = mLauncher.getFirstMatchForAppClose(NO_MATCHING_ID, + View icon = mLauncher.getFirstMatchForAppClose(null /* StableViewInfo */, mContract.componentName.getPackageName(), mContract.user, false /* supportsAllAppsState */); @@ -213,7 +214,7 @@ public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { @Override public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, - int format, int width, int height) { + int format, int width, int height) { drawOnSurface(); } @@ -227,17 +228,23 @@ public void surfaceRedrawNeeded(@NonNull SurfaceHolder surfaceHolder) { private void drawOnSurface() { SurfaceHolder surfaceHolder = mSurfaceView.getHolder(); - - Canvas c = surfaceHolder.lockHardwareCanvas(); - if (c != null) { - mPicture.draw(c); - surfaceHolder.unlockCanvasAndPost(c); + if (!surfaceHolder.getSurface().isValid()) return; + + synchronized (this) { + Canvas c = surfaceHolder.lockHardwareCanvas(); + if (c != null) { + try { + mPicture.draw(c); + } finally { + surfaceHolder.unlockCanvasAndPost(c); + } + } } } private void setCurrentIconVisible(boolean isVisible) { if (mIcon != null) { - setIconAndDotVisible(mIcon, isVisible); + setPropertiesVisible(mIcon, isVisible); } } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 9fca894574e..b0d0761bff2 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -71,6 +71,7 @@ import app.lawnchair.LawnchairAppWidgetHostView; import app.lawnchair.font.FontManager; +import app.lawnchair.theme.drawable.DrawableTokens; /** * Represents the individual cell of the widget inside the widget tray. The @@ -168,6 +169,10 @@ protected void onFinishInflate() { FontManager fontManager = FontManager.INSTANCE.get(getContext()); fontManager.setCustomFont(mWidgetName, R.id.font_body_medium); fontManager.setCustomFont(mWidgetDescription, R.id.font_body); + + // LC: Allow customisability to the Add Button, Test: Press on any Widget on the Widget sheet. + mWidgetAddButton.setBackground(DrawableTokens.WidgetAddButtonBackground.resolve(getContext())); + fontManager.setCustomFont(mWidgetAddButton, R.id.font_body_medium); } public void setRemoteViewsPreview(RemoteViews view) { diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java index 9260af9b22e..5e19d24e93b 100644 --- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java +++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java @@ -18,6 +18,8 @@ import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering; +import static java.util.stream.Collectors.toList; + import android.content.ComponentName; import android.content.Context; import android.os.Bundle; @@ -318,7 +320,7 @@ private Set maybeDisplayInTable(List recommendedWidge // Show only those widgets that were displayed when user first opened the picker. if (!mDisplayedWidgets.isEmpty()) { filteredRecommendedWidgets = recommendedWidgets.stream().filter( - w -> mDisplayedWidgets.contains(w.componentName)).toList(); + w -> mDisplayedWidgets.contains(w.componentName)).collect(toList()); } Context context = getContext(); LayoutInflater inflater = LayoutInflater.from(context); diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java index 47a98990bc0..0cbad821789 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java +++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java @@ -21,6 +21,7 @@ import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR; import static java.lang.Math.max; +import static java.util.stream.Collectors.toList; import android.content.Context; import android.util.AttributeSet; @@ -186,6 +187,6 @@ private List> selectRowsThatFitInAvailableHeight( } // Perform re-ordering once we have filtered out recommendations that fit. - return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList(); + return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).collect(toList()); } } diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java index 81ea0190bf7..0e007a0edc0 100644 --- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java +++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java @@ -83,11 +83,7 @@ public static List> groupWidgetItemsUsingRowPxWithReorderi sortedWidgetItems, context, dp, rowPx, cellPadding); Stream> sortedRows = rows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR); - if (Utilities.ATLEAST_U) { - return sortedRows.toList(); - } else { - return sortedRows.collect(Collectors.toList()); - } + return sortedRows.collect(Collectors.toList()); } /** diff --git a/systemUI/README.md b/systemUI/README.md index 578e9457092..5b44a6c6c3f 100644 --- a/systemUI/README.md +++ b/systemUI/README.md @@ -1,3 +1,5 @@ # SystemUI Module -https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/packages/SystemUI/ \ No newline at end of file +This directory contains all of the required SystemUI modules. Some examples of modules include `Common`, which serves as a helper for other SystemUI modules, and `Unfold`, which handles devices with hinges. + +Upstream source code: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/packages/SystemUI/ \ No newline at end of file diff --git a/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java b/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java index 61b0e4d9024..6627b776f8b 100644 --- a/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java +++ b/systemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java @@ -21,6 +21,8 @@ import android.app.ActivityManager; import android.os.SystemProperties; +import app.lawnchair.compat.LawnchairQuickstepCompat; + public abstract class BlurUtils { /** @@ -29,7 +31,7 @@ public abstract class BlurUtils { * @return {@code true} when supported. */ public static boolean supportsBlursOnWindows() { - return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() + return LawnchairQuickstepCompat.ATLEAST_R && CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() && !SystemProperties.getBoolean("persist.sysui.disableBlur", false); } } diff --git a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java index d74d0420297..85df9a9e361 100644 --- a/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java +++ b/systemUI/viewcapture/src/com/android/app/viewcapture/ViewCapture.java @@ -19,6 +19,8 @@ import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_H; import static com.android.app.viewcapture.data.ExportedData.MagicNumber.MAGIC_NUMBER_L; +import static java.util.stream.Collectors.toList; + import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.Configuration; @@ -206,7 +208,7 @@ public ExportedData getExportedData(Context context) } private static List toStringList(List classList) { - return classList.stream().map(Class::getName).toList(); + return classList.stream().map(Class::getName).collect(toList()); } public CompletableFuture> getDumpTask(View view) { @@ -224,8 +226,8 @@ private CompletableFuture> getWindowData(Context context, ArrayList outClassList, Predicate filter) { ViewIdProvider idProvider = new ViewIdProvider(context.getResources()); return CompletableFuture.supplyAsync(() -> - mListeners.stream().filter(filter).toList(), MAIN_EXECUTOR).thenApplyAsync(it -> - it.stream().map(l -> l.dumpToProto(idProvider, outClassList)).toList(), + mListeners.stream().filter(filter).collect(toList()), MAIN_EXECUTOR).thenApplyAsync(it -> + it.stream().map(l -> l.dumpToProto(idProvider, outClassList)).collect(toList()), mBgExecutor); } diff --git a/wmshell/src/com/android/wm/shell/bubbles/BubbleController.java b/wmshell/src/com/android/wm/shell/bubbles/BubbleController.java index d2c36e6b637..33474091dd5 100644 --- a/wmshell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/wmshell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1516,7 +1516,7 @@ void loadOverflowBubblesFromDisk() { } mOverflowDataLoadNeeded = false; List users = mUserManager.getAliveUsers(); - List userIds = users.stream().map(userInfo -> userInfo.id).toList(); + List userIds = users.stream().map(userInfo -> userInfo.id).collect(Collectors.toList()); mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {