diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index db9c232673b..08dbf7be85d 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -64,6 +64,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/ic_music_note_24dp.xml b/res/drawable/ic_music_note_24dp.xml
new file mode 100644
index 00000000000..323633f8327
--- /dev/null
+++ b/res/drawable/ic_music_note_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/res/drawable/ic_quickspace_derp.xml b/res/drawable/ic_quickspace_derp.xml
new file mode 100644
index 00000000000..83bd670054b
--- /dev/null
+++ b/res/drawable/ic_quickspace_derp.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/ic_quickspace_evening.xml b/res/drawable/ic_quickspace_evening.xml
new file mode 100644
index 00000000000..2192cf4d329
--- /dev/null
+++ b/res/drawable/ic_quickspace_evening.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/ic_quickspace_midnight.xml b/res/drawable/ic_quickspace_midnight.xml
new file mode 100644
index 00000000000..5b83510320e
--- /dev/null
+++ b/res/drawable/ic_quickspace_midnight.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/res/drawable/ic_quickspace_morning.xml b/res/drawable/ic_quickspace_morning.xml
new file mode 100644
index 00000000000..b74fc674873
--- /dev/null
+++ b/res/drawable/ic_quickspace_morning.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/quickspace_alternate_double.xml b/res/layout/quickspace_alternate_double.xml
new file mode 100644
index 00000000000..8ec007f0643
--- /dev/null
+++ b/res/layout/quickspace_alternate_double.xml
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/quickspace_doubleline.xml b/res/layout/quickspace_doubleline.xml
new file mode 100644
index 00000000000..3eb9aec7886
--- /dev/null
+++ b/res/layout/quickspace_doubleline.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/reserved_container_alternate_workspace.xml b/res/layout/reserved_container_alternate_workspace.xml
new file mode 100644
index 00000000000..d67157f18be
--- /dev/null
+++ b/res/layout/reserved_container_alternate_workspace.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/reserved_container_workspace.xml b/res/layout/reserved_container_workspace.xml
new file mode 100644
index 00000000000..76efb132480
--- /dev/null
+++ b/res/layout/reserved_container_workspace.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
diff --git a/res/values-de/derp_strings.xml b/res/values-de/derp_strings.xml
index 80c20f2e1cc..588d68fbed1 100644
--- a/res/values-de/derp_strings.xml
+++ b/res/values-de/derp_strings.xml
@@ -155,7 +155,6 @@
Basierend auf AOSP Launcher3
- AP1A.240305.019
Besonderen Dank an
Über
Info über den DerpLauncher
diff --git a/res/values-zh-rTW/derp_strings.xml b/res/values-zh-rTW/derp_strings.xml
new file mode 100644
index 00000000000..488ae47389d
--- /dev/null
+++ b/res/values-zh-rTW/derp_strings.xml
@@ -0,0 +1,408 @@
+
+
+
+
+
+ 主畫面設定
+
+
+ 主畫面
+ Set home panel and more
+ 圖示
+ Set icon size and more
+ 應用程式抽屜
+ 自訂你的應用程式抽屜
+ Recents
+ Revamp the overview screen
+ 雜項
+ 其他選項
+ 一般
+ Interface
+ 資訊一覽
+ 搜尋列
+ 快速操作
+
+
+ 開發人員選項
+ Enable some hidden features at your own risk
+
+
+ 鎖定佈局
+ 禁止在桌面上新增、移除或移動圖示和小工具
+ 允許在桌面上新增、移除或移動圖示和小工
+ 無法將小工具新增至主畫面
+
+
+ 桌面標籤
+ 在桌面上顯示圖示名稱
+ 應用程式抽屜標籤
+ 在應用程式抽屜上顯示圖示名稱
+
+
+ Swipe to access Google app
+
+ When you swipe right from main home screen
+
+ When you swipe left from main home screen
+
+
+ 輕觸兩下即可休眠
+ 在空白處點兩下即可關閉螢幕
+
+
+ 值:%s
+ 預設
+ 預設值:%s\n長按即可設定
+ 已設定為預設值
+
+
+ 圖示大小
+
+
+ 字體大小
+
+
+ Max lines for app label
+
+
+ 搜尋
+ 智慧鏡頭
+ 語音搜尋
+ Google 搜尋列
+ 在底部顯示搜尋列
+
+
+ 底欄背景
+ 在應用程式底欄上添加透明背景
+
+
+ 桌布滑動
+ Wallpaper scrolling effect for multiple screens
+
+
+ 桌布縮放
+ Zoom in or out the wallpaper when using drawer or recent apps
+
+
+ 顯示狀態列
+ 在主畫面上顯示狀態列
+
+
+ 顯示頂部陰影
+ 在狀態列下方添加陰影
+
+
+ 列高
+
+
+ 背景模糊度
+ 設定最近應用程式和應用程式抽屜的背景模糊度
+
+
+ 建議
+ 在應用程式抽屜 & 主畫面上顯示建議
+
+
+ 背景不透明度
+
+
+ 使用緊湊設計的操作按鈕圖示
+ 在顯示較多操作按鈕時可能需要
+ 螢幕截圖
+ 全部清除
+ 智慧鏡頭
+
+
+ 來源
+ 未知
+ 上次更新
+ 版本
+ 更多
+
+
+ 強制關閉
+ 應用程式已強制關閉
+
+
+ %1$s 可用 | %2$s
+ 記憶體資訊
+
+
+ 應用程式搜尋欄
+ Search bar on top of the app drawer
+
+
+ 重新啟動
+ Restart the DerpLauncher manually to apply any pending settings
+ 正在重新啟動 DerpLauncher...
+ Restarting DerpLauncher to apply changes...
+ Restarting DerpLauncher to update components...
+
+
+ Shake phone to clear all tasks
+
+
+ Allow short parallax
+ Enable full wallpaper scroll effect on smaller numbers of pages instead of cropping the wallpaper
+ Single page center
+ Center wallpaper if only using a single page
+
+
+ 圖示包
+ 預設
+ 安裝更多
+ 沒有可用的應用程式商店
+
+
+ 基於 AOSP Launcher3
+ 特別感謝
+ 關於
+ 關於 DerpLauncher 的資訊
+
+
+ 隱藏 & 受保護的應用程式
+ 解鎖後即可管理隱藏和受保護的應用程式
+ 進行身份驗證後即可開啟 %1$s
+ 載入中\u2026
+ Please set up a secure lock screen to restrict app access
+ 說明
+ Hidden apps and their widgets are hidden from the drawer
+ Protected apps require authentication to be opened from the launcher
+
+
+ 主題圖示
+ Follow themed icons used on home screen
+
+
+ 深色狀態列
+ 在主畫面上使用深色狀態列
+
+
+ 強制使用單色圖示
+ Force monochrome icons for apps that don\'t support them natively (requires re-toggling of themed icons)
+
+
+ 快速查看
+ 在主畫面頂部上顯示
+
+
+ 歡迎來到 DerpFest!
+ DerpFest 時間!
+
+ - 感謝你選擇我們
+ - 有史以來最好的 ROM
+ - 來我們的 Telegram 群組上 Say hi 吧!
+ - 輕觸此處開始
+ - 我們愛你 3000 次
+ - DerpFest 萬歲!
+ - #StayDerped
+
+
+
+ 早安
+ 晚安
+ 午安
+ 晚安
+ 你好
+ 今天是
+ 現在是
+
+
+ 未知藝術家
+ 正在播放
+ 作者
+
+
+ 正在播放
+ Show the song you\'re playing
+
+
+ Extended style
+ Switch to extended style
+
+
+ 隨機訊息
+ Make your companion more lively with random messages
+
+
+ - Good morning!
+ - Good morning, time to rock!
+ - What a beautiful day!
+ - Have a nice day!
+ - What about a small 5 minutes nap?
+ - Let\'s get this bread.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not your fears.
+ - In every moment, a new opportunity appears.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story, make each chapter count.
+ - Find joy in every moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'how about that\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is yours, not your parents nor your friends.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every failure means a step closer to your success.
+ - Your potential is endless, unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey, enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it, make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself, you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of the this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas, paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime, pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice, choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning, make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - Is it time to flash another update already?
+ - Make peace, not war
+ - Oh hey, what\'s up?
+ - Focus on your tasks
+ - How many screenshots do you take?
+ - Open goodness with DerpFest
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - Time for some good music
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - You need DerpFest Premium to see this
+ - Remember the Lineage of the Unicorn
+ - Starting from the ground zero
+ - We love you 3000
+ - Delicious even without Sushi
+ - Do something nice today
+ - This is best Pixel Experience, isn\'t it?
+ - No illusions, welcome to reality!
+ - Thank you for your support
+ - Is your device derped?
+ - One of the buildbot\'s best picks
+ - Sanity for your Paranoia
+ - Try Ice Cold desserts
+ - What a lovely experience, isn\'t it?
+ - DerpFest is a myth, right?
+ - You are what you flash, don\'t be Potato
+ - What\'s on your mind?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winner Winner ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - #StayDerped \m/
+ - DerpFest FTW \m/
+ - Enjoying Android 14?
+ - Hyped about Android 15?
+ - Proudly presented without donations
+ - Did you checkout all the tweaks?
+ - You have tapped almost a 100k times.
+ - Lots of options!
+ - I am not your F1 button
+ - Don\'t pass me!
+ - Did you finish your homework? ツ
+ - This is best ROM Experience, isn\'t it?
+ - Well done! You have earned a bonus feature
+ - That\'s enough, stop tapping me!
+ - Only for pro users
+ - Got an issue? Don\'t forget a logcat
+
+
+
+ 天氣更新
+ 顯示目前的天氣更新
+ 需要啟用天氣服務
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ 目前位置
+ 顯示目前天氣位置
+ Current condition
+ Display current weather condition summary
+
+ Unable to find calendar or clock activity
+
+
+ 天氣設定
+ 設定圖示包和天氣服務
+
diff --git a/res/values/derp_config.xml b/res/values/derp_config.xml
index 7a30e19cab3..596c83a5055 100644
--- a/res/values/derp_config.xml
+++ b/res/values/derp_config.xml
@@ -32,4 +32,7 @@
false
+
+
+
diff --git a/res/values/derp_dimens.xml b/res/values/derp_dimens.xml
index ccc2faa22c4..62b97566d3b 100644
--- a/res/values/derp_dimens.xml
+++ b/res/values/derp_dimens.xml
@@ -12,4 +12,10 @@
96dp
15dp
12dp
+
+
+ 20sp
+ 16sp
+ 18sp
+ 14sp
diff --git a/res/values/derp_strings.xml b/res/values/derp_strings.xml
index 3f0af120ff1..b39e3593b8f 100644
--- a/res/values/derp_strings.xml
+++ b/res/values/derp_strings.xml
@@ -27,6 +27,7 @@
Other options
General
Interface
+ Quickspace
Search bar
Quick actions
@@ -166,7 +167,7 @@
DerpLauncher
Based on AOSP Launcher3
- AP1A.240305.019
+ AP1A.240405.002
Special thanks to
About
Info about the DerpLauncher
@@ -192,4 +193,228 @@
Force monochrome icons
Force monochrome icons for apps that don\'t support them natively (requires re-toggling of themed icons)
+
+
+ At A Glance
+ Show at the top of your home screen
+ EEE, MMM d
+ EEEE, MMMM d
+
+
+ Welcome to DerpFest!
+ DerpFest time!
+
+ - Thank you for choosing us
+ - Best Rom Ever.
+ - Say hi to us on Telegram
+ - Tap here to begin
+ - We love you 3000
+ - DerpFest FTW!
+ - #StayDerped
+
+
+
+ Good morning.
+ Good evening.
+ Good afternoon.
+ Good night.
+ Good day.
+ Today is
+ It\'s
+
+
+ Unknown Artist
+ Now playing
+ By
+
+
+ Now playing
+ Show the song you\'re playing
+
+
+ Extended style
+ Switch to extended style
+
+
+ Random messages
+ Make your companion more lively with random messages
+
+
+ - Good morning!
+ - Good morning, time to rock!
+ - What a beautiful day!
+ - Have a nice day!
+ - What about a small 5 minutes nap?
+ - Let\'s get this bread.
+ - Mornings are a fresh canvas for your daily masterpiece.
+ - Embrace the sunrise of opportunity each morning.
+ - In the early light, find the power to illuminate your path.
+ - Your day begins with endless possibilities.
+ - Every sunrise is a reminder that you can start anew.
+ - Awake with purpose, conquer the day ahead.
+ - Your thoughts can be the foundation of great achievements.
+ - The world awakens, and so does your potential.
+ - The first chapter in your daily adventure.
+ - Let your morning routine be the launchpad for success.
+ - Sunrise or not, your potential is always on the rise.
+ - It\'s time for fresh ideas and bold actions.
+ - The early bird catches the worm, but you can catch your dreams.
+ - Find the courage to chase your aspirations.
+ - Rise and shine, for greatness awaits.
+ - The best time to start is now.
+
+
+ - Chase your dreams, not your fears.
+ - In every moment, a new opportunity appears.
+ - Embrace the journey, not just the destination.
+ - Today is a gift, that\'s why it\'s called the present.
+ - Let your smile change the world.
+ - Life is a story, make each chapter count.
+ - Find joy in every moments.
+ - The best is yet to come, keep it up.
+ - Your chance to turn \'what if\' into \'how about that\'
+ - Seize the day, starting right now.
+ - Happiness is a choice you can make any time.
+ - Believe in yourself, even on a sleepy afternoon.
+ - Let your actions speak louder than the clock.
+ - Your life is yours, not your parents nor your friends.
+ - Dream big, work hard, and never give up.
+ - You are stronger than you think.
+ - Every failure means a step closer to your success.
+ - Your potential is endless, unlock it.
+
+
+ - Embrace the present moment, no matter the hour.
+ - In every dusk, find a new dawn of opportunity.
+ - Life is a journey, enjoy the scenery along the way.
+ - Your attitude shapes your reality.
+ - Your story is still being written, even as the sun sets.
+ - Capture the beauty in the ordinary moments.
+ - It\'s time to reflect and reset.
+ - Choose joy, no matter the time on the clock.
+ - Opportunities don\'t clock out in the evening.
+ - Be the reason someone smiles.
+ - Your potential knows no bounds.
+ - Moments of magic can happen at any hour.
+ - Your life is what you make of it, make it extraordinary.
+ - Stay curious, stay grateful.
+ - Be the change you wish to see in the world.
+ - Believe in yourself, you are capable of amazing things.
+ - The only limit is the one you set for yourself.
+ - Success begins with a single step.
+
+
+ - Seize the night, for it holds its own mysteries.
+ - In the quiet of the this evening, find serenity.
+ - Life\'s greatest adventures can start in the darkness.
+ - Your journey continues even after the sun has set.
+ - Let the night be your canvas, paint it with dreams.
+ - In the stillness of this evening, find your inner peace.
+ - Stars shine brightest in the darkest hours.
+ - Dream big, even in the late hours of the day.
+ - Your life is a story waiting to be written.
+ - It\'s time for reflection and renewal.
+ - Late night thoughts can lead to early morning revelations.
+ - Find joy in the stillness of this evening.
+ - The night sky is a vast canvas of dreams.
+ - Your potential knows no bedtime, pursue your passions.
+ - Stay hopeful, even in the darkest hours.
+ - In the midst of chaos, find your inner calm.
+ - Stay positive, work hard, make it happen.
+
+
+ - Nights are the quiet whispers of opportunity.
+ - In the stillness of the night, find your inner strength.
+ - Embrace the darkness, for it holds the keys to your dreams.
+ - The world sleeps, but your potential is wide awake.
+ - The night may be late, but your ambitions are timeless.
+ - Stay curious, stay awake, and let nights guide you.
+ - Find the courage to chase your dreams.
+ - Great things never come from comfort zones.
+ - You have the power to create your own destiny.
+ - Happiness is a choice, choose it every day.
+ - Life is what you make it, so make it count.
+ - Stay focused, stay determined, stay unstoppable.
+ - You are the author of your own story.
+ - Strive for progress, not perfection.
+ - Every day is a new beginning, make it a great one.
+ - Inspire others by being your authentic self.
+ - Your attitude determines your direction.
+
+
+ - Is it time to flash another update already?
+ - Make peace, not war
+ - Oh hey, what\'s up?
+ - Focus on your tasks
+ - How many screenshots do you take?
+ - Open goodness with DerpFest
+ - Spread love, not havoc
+ - We didn\'t start the fire!
+ - Time for some good music
+ - \u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD
+ - You need DerpFest Premium to see this
+ - Remember the Lineage of the Unicorn
+ - Starting from the ground zero
+ - We love you 3000
+ - Delicious even without Sushi
+ - Do something nice today
+ - This is best Pixel Experience, isn\'t it?
+ - No illusions, welcome to reality!
+ - Thank you for your support
+ - Is your device derped?
+ - One of the buildbot\'s best picks
+ - Sanity for your Paranoia
+ - Try Ice Cold desserts
+ - What a lovely experience, isn\'t it?
+ - DerpFest is a myth, right?
+ - You are what you flash, don\'t be Potato
+ - What\'s on your mind?
+ - Expecto Patronum
+ - Wubba Lubba Dub Dub
+ - Winner Winner ....?
+ - rm -rf \/
+ - Pringles aren\'t actually potato chips..
+ - Remember to check device\'s battery level!
+ - Check your email/messages.
+ - Check your to-do list if you have one.
+ - You can disable quickspace via home settings.
+ - #StayDerped \m/
+ - DerpFest FTW \m/
+ - Enjoying Android 14?
+ - Hyped about Android 15?
+ - Proudly presented without donations
+ - Did you checkout all the tweaks?
+ - You have tapped almost a 100k times.
+ - Lots of options!
+ - I am not your F1 button
+ - Don\'t pass me!
+ - Did you finish your homework? ツ
+ - This is best ROM Experience, isn\'t it?
+ - Well done! You have earned a bonus feature
+ - That\'s enough, stop tapping me!
+ - Only for pro users
+ - Got an issue? Don\'t forget a logcat
+
+
+
+ Weather update
+ Display current weather update
+ Requires weather service to be enabled
+ Cloudy
+ Rainy
+ Sunny
+ Stormy
+ Snowy
+ Windy
+ Misty
+ Current location
+ Display current weather location
+ Current condition
+ Display current weather condition summary
+
+ Unable to find calendar or clock activity
+
+
+ Weather settings
+ Setup icon pack and weather service
diff --git a/res/values/derp_styles.xml b/res/values/derp_styles.xml
new file mode 100644
index 00000000000..5f9725bdbec
--- /dev/null
+++ b/res/values/derp_styles.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f2dc189e774..32a218af51e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -406,6 +406,8 @@
0dp
0dp
0dp
+ 0dp
+ 0dp
0dp
diff --git a/res/xml/default_workspace_4x4.xml b/res/xml/default_workspace_4x4.xml
new file mode 100644
index 00000000000..7a743f398ad
--- /dev/null
+++ b/res/xml/default_workspace_4x4.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 9ec56fa63a8..ab9534ef3db 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -93,6 +93,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:defaultValue="91" />
diff --git a/res/xml/launcher_home_screen_preferences.xml b/res/xml/launcher_home_screen_preferences.xml
index dc4ab92254e..a9e2aa22334 100644
--- a/res/xml/launcher_home_screen_preferences.xml
+++ b/res/xml/launcher_home_screen_preferences.xml
@@ -140,6 +140,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/launcher_misc_preferences.xml b/res/xml/launcher_misc_preferences.xml
index cbf0b438240..2f53750a333 100644
--- a/res/xml/launcher_misc_preferences.xml
+++ b/res/xml/launcher_misc_preferences.xml
@@ -46,7 +46,7 @@
android:title="@string/background_blur_title"
android:summary="@string/background_blur_summary"
android:persistent="true"
- android:max="175"
+ android:max="225"
android:min="0"
settings:units="px"
android:defaultValue="23" />
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index c7cdfa8c693..9316b1473f6 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -50,7 +50,6 @@
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.IntArray;
@@ -128,7 +127,6 @@ public static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWi
private static final String TAG_AUTO_INSTALL = "autoinstall";
private static final String TAG_FOLDER = "folder";
private static final String TAG_APPWIDGET = "appwidget";
- protected static final String TAG_SEARCH_WIDGET = "searchwidget";
private static final String TAG_SHORTCUT = "shortcut";
private static final String TAG_EXTRA = "extra";
@@ -342,7 +340,6 @@ protected ArrayMap getLayoutElementsMap() {
parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
parsers.put(TAG_FOLDER, new FolderParser());
parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
- parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
parsers.put(TAG_SHORTCUT, new ShortcutParser());
return parsers;
}
@@ -534,24 +531,6 @@ protected int verifyAndInsert(ComponentName cn, Bundle extras) {
}
}
- protected class SearchWidgetParser extends PendingWidgetParser {
- @Override
- @Nullable
- @WorkerThread
- public ComponentName getComponentName(XmlPullParser parser) {
- return QsbContainerView.getSearchComponentName(mContext);
- }
-
- @Override
- protected int verifyAndInsert(ComponentName cn, Bundle extras) {
- mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET);
- int flags = mValues.getAsInteger(Favorites.RESTORED)
- | WorkspaceItemInfo.FLAG_RESTORE_STARTED;
- mValues.put(Favorites.RESTORED, flags);
- return super.verifyAndInsert(cn, extras);
- }
- }
-
protected class FolderParser implements TagParser {
private final ArrayMap mFolderElements;
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java
index c748693f9bd..1949c9a2c3b 100644
--- a/src/com/android/launcher3/DefaultLayoutParser.java
+++ b/src/com/android/launcher3/DefaultLayoutParser.java
@@ -70,7 +70,6 @@ protected ArrayMap getLayoutElementsMap() {
ArrayMap parsers = new ArrayMap<>();
parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
parsers.put(TAG_APPWIDGET, new AppWidgetParser());
- parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser());
parsers.put(TAG_SHORTCUT, new ShortcutParser());
parsers.put(TAG_RESOLVE, new ResolveParser());
parsers.put(TAG_FOLDER, new MyFolderParser());
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 76e6b826146..e0819c28a46 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -243,31 +243,6 @@ public class InvariantDeviceProfile implements OnSharedPreferenceChangeListener
private final ArrayList mChangeListeners = new ArrayList<>();
- private static final Uri ENABLE_TASKBAR_URI = Settings.System.getUriFor(
- Settings.System.ENABLE_TASKBAR);
-
- private final class SettingsContentObserver extends ContentObserver {
- SettingsContentObserver() {
- super(new Handler(Looper.getMainLooper()));
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (ENABLE_TASKBAR_URI.equals(uri)) {
- // Create the illusion of this taking effect immediately
- // Also needed because TaskbarManager inits before SystemUiProxy on start
- boolean enabled = Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.ENABLE_TASKBAR, 0) == 1;
- SystemUiProxy.INSTANCE.get(mContext).setTaskbarEnabled(enabled);
-
- // Restart launcher
- System.exit(0);
- }
- }
- }
-
- private final SettingsContentObserver mSettingsObserver = new SettingsContentObserver();
-
@VisibleForTesting
public InvariantDeviceProfile() { }
@@ -293,10 +268,6 @@ private InvariantDeviceProfile(Context context) {
onConfigChanged(displayContext);
}
});
-
- final ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(ENABLE_TASKBAR_URI, false,
- mSettingsObserver);
}
/**
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7aa8c4a9b2a..8d3bffbb4ad 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -204,7 +204,7 @@
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.qsb.QsbContainerView;
+import com.android.launcher3.quickspace.QuickSpaceView;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -410,6 +410,9 @@ public class Launcher extends StatefulActivity
private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener =
enabled -> mIsNaturalScrollingEnabled = enabled;
+ // QuickSpace
+ private QuickSpaceView mQuickSpace;
+
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@@ -1050,6 +1053,9 @@ protected void onStop() {
} else {
mOverlayManager.onActivityStopped();
}
+ if (mQuickSpace != null) {
+ mQuickSpace.onPause();
+ }
hideKeyboard();
logStopAndResume(false /* isResume */);
mAppWidgetHolder.setActivityStarted(false);
@@ -1238,6 +1244,10 @@ protected void onResume() {
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
+ if (mQuickSpace != null) {
+ mQuickSpace.onResume();
+ }
+
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
} else {
@@ -1263,6 +1273,9 @@ protected void onPause() {
if (!mDeferOverlayCallbacks) {
mOverlayManager.onActivityPaused();
}
+ if (mQuickSpace != null) {
+ mQuickSpace.onPause();
+ }
mAppWidgetHolder.setActivityResumed(false);
}
@@ -1346,6 +1359,9 @@ protected void setupViews() {
// Setup Scrim
mScrimView = findViewById(R.id.scrim_view);
+ // QuickSpace
+ mQuickSpace = findViewById(R.id.reserved_container_workspace);
+
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mScrimView, mAppsView);
@@ -1746,6 +1762,10 @@ public void onDestroy() {
// changes while launcher is still loading.
getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
mOverlayManager.onActivityDestroyed();
+
+ if (mQuickSpace != null) {
+ mQuickSpace.onPause();
+ }
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -2315,14 +2335,6 @@ public void bindAppWidget(LauncherAppWidgetInfo item) {
}
private View inflateAppWidget(LauncherAppWidgetInfo item) {
- if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
- item.providerName = QsbContainerView.getSearchComponentName(this);
- if (item.providerName == null) {
- getModelWriter().deleteItemFromDatabase(item,
- "search widget removed because search component cannot be found");
- return null;
- }
- }
final AppWidgetHostView view;
if (mIsSafeModeEnabled) {
view = new PendingAppWidgetHostView(this, item, mIconCache, true);
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 51729992d45..ac6552df358 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -35,8 +35,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
var synchronouslyBoundPages = LIntSet()
var pagesToBindSynchronously = LIntSet()
- private var isFirstPagePinnedItemEnabled =
- (BuildConfig.QSB_ON_FIRST_SCREEN && !FeatureFlags.ENABLE_SMARTSPACE_REMOVAL.get())
+ private var isFirstPagePinnedItemEnabled = FeatureFlags.USE_QUICKSPACE_VIEW
var stringCache: StringCache? = null
@@ -309,15 +308,14 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
)
val firstScreenPosition = 0
if (
- (FeatureFlags.QSB_ON_FIRST_SCREEN &&
- isFirstPagePinnedItemEnabled &&
+ (isFirstPagePinnedItemEnabled &&
!shouldShowFirstPageWidget()) &&
orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition
) {
orderedScreenIds.removeValue(FIRST_SCREEN_ID)
orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID)
} else if (
- (!FeatureFlags.QSB_ON_FIRST_SCREEN && !isFirstPagePinnedItemEnabled ||
+ (!isFirstPagePinnedItemEnabled ||
shouldShowFirstPageWidget()) && orderedScreenIds.isEmpty
) {
// If there are no screens, we need to have an empty screen
@@ -374,8 +372,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks {
}
orderedScreenIds
.filterNot { screenId ->
- FeatureFlags.QSB_ON_FIRST_SCREEN &&
- isFirstPagePinnedItemEnabled &&
+ isFirstPagePinnedItemEnabled &&
!FeatureFlags.shouldShowFirstPageWidget() &&
screenId == WorkspaceLayoutManager.FIRST_SCREEN_ID
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index bbbe984c724..e65efac70d9 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -58,6 +58,8 @@
import android.graphics.drawable.InsetDrawable;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricPrompt;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.CancellationSignal;
@@ -69,6 +71,7 @@
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -197,6 +200,14 @@ public final class Utilities {
public static final String KEY_LENS = "pref_recents_lens";
public static final String KEY_SHORT_PARALLAX = "pref_short_parallax";
public static final String KEY_SINGLE_PAGE_CENTER = "pref_single_page_center";
+ public static final String KEY_RECENTS_CHIPS = "pref_recents_chips";
+ public static final String DESKTOP_SHOW_QUICKSPACE = "pref_show_quickspace";
+ public static final String KEY_SHOW_ALT_QUICKSPACE = "pref_show_alt_quickspace";
+ public static final String KEY_SHOW_QUICKSPACE_PSONALITY = "pref_quickspace_psonality";
+ public static final String KEY_SHOW_QUICKSPACE_NOWPLAYING = "pref_quickspace_np";
+ public static final String KEY_SHOW_QUICKSPACE_WEATHER = "pref_quickspace_weather";
+ public static final String KEY_SHOW_QUICKSPACE_WEATHER_CITY = "pref_quickspace_weather_city";
+ public static final String KEY_SHOW_QUICKSPACE_WEATHER_TEXT = "pref_quickspace_weather_text";
/**
* Returns true if theme is dark.
@@ -879,6 +890,19 @@ public static void translateOverlappingView(
}
}
+ public static String formatDateTime(Context context) {
+ String styleText;
+ DateFormat dateFormat;
+ if (useAlternativeQuickspaceUI(context)) {
+ styleText = context.getString(R.string.quickspace_date_format_minimalistic);
+ } else {
+ styleText = context.getString(R.string.quickspace_date_format);
+ }
+ dateFormat = DateFormat.getInstanceForSkeleton(styleText, Locale.getDefault());
+ dateFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ return dateFormat.format(System.currentTimeMillis());
+ }
+
public static boolean isWorkspaceEditAllowed(Context context) {
SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
return !prefs.getBoolean(InvariantDeviceProfile.KEY_WORKSPACE_LOCK, false);
@@ -988,7 +1012,7 @@ public static int getRecentsOpacity(Context context) {
public static int getAllAppsOpacity(Context context) {
SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
- return prefs.getInt(KEY_APP_DRAWER_OPACITY, 80);
+ return prefs.getInt(KEY_APP_DRAWER_OPACITY, 91);
}
public static boolean isShowMeminfo(Context context) {
@@ -1019,4 +1043,39 @@ public static boolean isSinglePageCentered(Context context) {
public static boolean isDebugDevice() {
return !Build.IS_USER;
}
+
+ public static boolean showQuickspace(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(DESKTOP_SHOW_QUICKSPACE, true);
+ }
+
+ public static boolean useAlternativeQuickspaceUI(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_SHOW_ALT_QUICKSPACE, false);
+ }
+
+ public static boolean isQuickspacePersonalityEnabled(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_SHOW_QUICKSPACE_PSONALITY, true);
+ }
+
+ public static boolean isQuickspaceNowPlaying(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_SHOW_QUICKSPACE_NOWPLAYING, true);
+ }
+
+ public static boolean isQuickspaceWeather(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_SHOW_QUICKSPACE_WEATHER, true);
+ }
+
+ public static boolean QuickSpaceShowCity(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_SHOW_QUICKSPACE_WEATHER_CITY, false);
+ }
+
+ public static boolean QuickSpaceShowWeatherText(Context context) {
+ SharedPreferences prefs = LauncherPrefs.getPrefs(context.getApplicationContext());
+ return prefs.getBoolean(KEY_SHOW_QUICKSPACE_WEATHER_TEXT, true);
+ }
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 3fbc584f084..2a08fd2b315 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -600,9 +600,10 @@ public void onViewAdded(View child) {
* Initializes and binds the first page
*/
public void bindAndInitFirstWorkspaceScreen() {
- if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
+ if ((!Utilities.showQuickspace(getContext())
|| !mLauncher.getIsFirstPagePinnedItemEnabled())
|| shouldShowFirstPageWidget()) {
+ insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
mFirstPagePinnedItem = null;
return;
}
@@ -614,14 +615,14 @@ public void bindAndInitFirstWorkspaceScreen() {
// As workspace does not touch the edges, we do not need a full
// width first page pinned item.
mFirstPagePinnedItem = LayoutInflater.from(getContext())
- .inflate(R.layout.search_container_workspace, firstPage, false);
+ .inflate(R.layout.reserved_container_workspace, firstPage, false);
}
int cellHSpan = mLauncher.getDeviceProfile().inv.numSearchContainerColumns;
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(0, 0, cellHSpan, 1);
lp.canReorder = false;
if (!firstPage.addViewToCellLayout(
- mFirstPagePinnedItem, 0, R.id.search_container_workspace, lp, true)) {
+ mFirstPagePinnedItem, 0, R.id.reserved_container_workspace, lp, true)) {
Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
mFirstPagePinnedItem = null;
}
@@ -812,7 +813,7 @@ private void convertFinalScreenToEmptyScreenIfNecessary() {
// We don't want to remove the first screen even if it's empty because that's where
// first page pinned item would go if it gets turned back on.
- if (ENABLE_SMARTSPACE_REMOVAL.get() && screenId == FIRST_SCREEN_ID) {
+ if (FeatureFlags.USE_QUICKSPACE_VIEW && screenId == FIRST_SCREEN_ID) {
continue;
}
@@ -1029,7 +1030,7 @@ public void stripEmptyScreens() {
int id = mWorkspaceScreens.keyAt(i);
CellLayout cl = mWorkspaceScreens.valueAt(i);
// FIRST_SCREEN_ID can never be removed.
- if (((!FeatureFlags.QSB_ON_FIRST_SCREEN
+ if (((!FeatureFlags.USE_QUICKSPACE_VIEW
|| shouldShowFirstPageWidget())
|| id > FIRST_SCREEN_ID)
&& cl.getShortcutsAndWidgets().getChildCount() == 0) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f807bdf7b19..a9a21be0f8f 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -62,7 +62,7 @@ private FeatureFlags() { }
* @deprecated Use {@link BuildConfig#QSB_ON_FIRST_SCREEN} directly
*/
@Deprecated
- public static final boolean QSB_ON_FIRST_SCREEN = BuildConfig.QSB_ON_FIRST_SCREEN;
+ public static final boolean USE_QUICKSPACE_VIEW = true;
/**
* Feature flag to handle define config changes dynamically instead of killing the process.
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 33304489196..126c04c91ce 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -527,14 +527,14 @@ private void populate(BgDataModel dataModel,
}
// Add first page QSB
- if (FeatureFlags.QSB_ON_FIRST_SCREEN && dataModel.isFirstPagePinnedItemEnabled
+ if (FeatureFlags.USE_QUICKSPACE_VIEW && dataModel.isFirstPagePinnedItemEnabled
&& !shouldShowFirstPageWidget()) {
CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
0, 0, firstScreen.getCountX(), 1);
lp.canReorder = false;
- firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+ firstScreen.addViewToCellLayout(qsb, 0, R.id.reserved_container_workspace, lp, true);
}
measureView(mRootView, mDp.widthPx, mDp.heightPx);
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index d8388c27090..5fa2d57868f 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -281,6 +281,12 @@ public enum LauncherEvent implements EventEnum {
@UiEvent(doc = "User long presses on the bottom bezel area.")
LAUNCHER_LONG_PRESS_NAVBAR(1544),
+ @UiEvent(doc = "User deep presses on the stashed taskbar")
+ LAUNCHER_DEEP_PRESS_STASHED_TASKBAR(1602),
+
+ @UiEvent(doc = "User long presses on the stashed taskbar")
+ LAUNCHER_LONG_PRESS_STASHED_TASKBAR(1592),
+
@UiEvent(doc = "User swipes or fling in UP direction from bottom bazel area.")
LAUNCHER_HOME_GESTURE(574),
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 7f0f683091e..a30d30da763 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,7 +17,6 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
-import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
@@ -132,7 +131,7 @@ public class BgDataModel {
* Load id for which the callbacks were successfully bound
*/
public int lastLoadId = -1;
- public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+ public boolean isFirstPagePinnedItemEnabled = FeatureFlags.USE_QUICKSPACE_VIEW
&& !ENABLE_SMARTSPACE_REMOVAL.get();
/**
@@ -157,7 +156,7 @@ public synchronized IntArray collectWorkspaceScreens() {
screenSet.add(item.screenId);
}
}
- if ((FeatureFlags.QSB_ON_FIRST_SCREEN
+ if ((FeatureFlags.USE_QUICKSPACE_VIEW
&& !shouldShowFirstPageWidget())
|| screenSet.isEmpty()) {
screenSet.add(Workspace.FIRST_SCREEN_ID);
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 13605101e75..de9eb5039b0 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -258,7 +258,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
}
case 30: {
- if (FeatureFlags.QSB_ON_FIRST_SCREEN
+ if (Utilities.showQuickspace(mContext)
&& !shouldShowFirstPageWidget()) {
// Clean up first row in screen 0 as it might contain junk data.
Log.d(TAG, "Cleaning up first row");
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index efd55745196..086c7664be2 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -332,7 +332,7 @@ private static void solveGridPlacement(@NonNull final DatabaseHelper helper,
final GridOccupancy occupied = new GridOccupancy(trgX, trgY);
final Point trg = new Point(trgX, trgY);
final Point next = new Point(0, screenId == 0
- && (FeatureFlags.QSB_ON_FIRST_SCREEN
+ && (Utilities.showQuickspace(destReader.mContext)
&& (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(destReader.mContext)
.getBoolean(SMARTSPACE_ON_HOME_SCREEN, true))
&& !shouldShowFirstPageWidget())
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 43700435694..5ffdb137385 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -529,7 +529,7 @@ protected boolean checkItemPlacement(ItemInfo item, boolean isFirstPagePinnedIte
if (!mOccupied.containsKey(item.screenId)) {
GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
- if (item.screenId == Workspace.FIRST_SCREEN_ID && (FeatureFlags.QSB_ON_FIRST_SCREEN
+ if (item.screenId == Workspace.FIRST_SCREEN_ID && (Utilities.showQuickspace(mContext)
&& !shouldShowFirstPageWidget() && isFirstPagePinnedItemEnabled)) {
// Mark the first X columns (X is width of the search container) in the first row as
// occupied (if the feature is enabled) in order to account for the search
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 10ae171f9a3..5492bc40da0 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -383,9 +383,7 @@ protected void loadWorkspace(
mModelDelegate.markActive();
logASplit("workspaceDelegateItems");
}
- mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
- && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(
- mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
+ mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.USE_QUICKSPACE_VIEW;
}
private void loadWorkspaceImpl(
@@ -770,15 +768,8 @@ private void processWorkspaceItem(LoaderCursor c,
String savedProvider = c.getAppWidgetProvider();
final ComponentName component;
- if ((c.getOptions() & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0) {
- component = QsbContainerView.getSearchComponentName(mApp.getContext());
- if (component == null) {
- c.markDeleted("Discarding SearchWidget without packagename ");
- return;
- }
- } else {
- component = ComponentName.unflattenFromString(savedProvider);
- }
+ component = ComponentName.unflattenFromString(savedProvider);
+
final boolean isIdValid =
!c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
final boolean wasProviderReady =
diff --git a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
index 929f698236e..01536c6219c 100644
--- a/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
+++ b/src/com/android/launcher3/model/WorkspaceItemSpaceFinder.java
@@ -23,6 +23,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.GridOccupancy;
@@ -67,7 +68,7 @@ public int[] findSpaceForItem(LauncherAppState app, BgDataModel dataModel,
int screenCount = workspaceScreens.size();
// First check the preferred screen.
IntSet screensToExclude = new IntSet();
- if (FeatureFlags.QSB_ON_FIRST_SCREEN
+ if (Utilities.showQuickspace(app.getContext())
&& !shouldShowFirstPageWidget()) {
screensToExclude.add(FIRST_SCREEN_ID);
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 6528e757bf6..34ec2ae506e 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -1,10 +1,14 @@
package com.android.launcher3.popup;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
+import android.app.Activity;
+import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Context;
@@ -16,10 +20,12 @@
import android.net.Uri;
import android.util.Log;
import android.view.View;
+import android.view.WindowInsets;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
+import android.window.SplashScreen;
import android.os.UserHandle;
import androidx.annotation.Nullable;
@@ -37,6 +43,7 @@
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.WidgetsBottomSheet;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.net.URISyntaxException;
import java.util.List;
@@ -313,6 +320,45 @@ public void onClick(View view) {
}
}
+ public static final Factory FREE_FORM = (activity, itemInfo, originalView) ->
+ ActivityManagerWrapper.getInstance().supportsFreeformMultiWindow(activity)
+ ? new FreeForm(activity, itemInfo, originalView)
+ : null;
+
+ public static class FreeForm extends SystemShortcut {
+ private final String mPackageName;
+
+ public FreeForm(BaseDraggingActivity target, ItemInfo itemInfo, View originalView) {
+ super(R.drawable.ic_caption_desktop_button_foreground, R.string.recent_task_option_freeform, target, itemInfo, originalView);
+ mPackageName = itemInfo.getTargetComponent().getPackageName();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mPackageName != null) {
+ Intent intent = mTarget.getPackageManager().getLaunchIntentForPackage(mPackageName);
+ if (intent != null) {
+ ActivityOptions options = makeLaunchOptions(mTarget);
+ mTarget.startActivity(intent, options.toBundle());
+ AbstractFloatingView.closeAllOpenViews(mTarget);
+ }
+ }
+ }
+
+ private ActivityOptions makeLaunchOptions(Activity activity) {
+ ActivityOptions activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+ final View decorView = activity.getWindow().getDecorView();
+ final WindowInsets insets = decorView.getRootWindowInsets();
+ final Rect r = new Rect(0, 0, decorView.getWidth() / 2, decorView.getHeight() / 2);
+ r.offsetTo(insets.getSystemWindowInsetLeft() + 50, insets.getSystemWindowInsetTop() + 50);
+ activityOptions.setLaunchBounds(r);
+ activityOptions.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ activityOptions.setTaskOverlay(true /* taskOverlay */, true /* canResume */);
+ return activityOptions;
+ }
+ }
+
public static void dismissTaskMenuView(T activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/quickspace/QuickEventsController.java b/src/com/android/launcher3/quickspace/QuickEventsController.java
new file mode 100644
index 00000000000..0d9cee9a093
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/QuickEventsController.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2018 CypherOS
+ *
+ * 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.quickspace;
+
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.SharedPreferences;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.net.Uri;
+import android.provider.AlarmClock;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.widget.Toast;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+import java.util.Calendar;
+import java.util.Random;
+
+import java.util.List;
+
+public class QuickEventsController {
+
+ private static final String SETTING_DEVICE_INTRO_COMPLETED = "device_introduction_completed";
+ private Context mContext;
+
+ private String mEventTitle;
+ private String mEventTitleSub;
+ private String mGreetings;
+ private String mClockExt;
+ private OnClickListener mEventTitleSubAction = null;
+ private int mEventSubIcon = 0;
+
+ private boolean mIsQuickEvent = false;
+ private boolean mRunning = true;
+ private boolean mRegistered = false;
+
+ // Device Intro
+ private boolean mIsFirstTimeDone = false;
+ private SharedPreferences mPreferences;
+
+ // PSA + Personality
+ private String[] mPSAMorningStr;
+ private String[] mPSAEvenStr;
+ private String[] mPSAAfterNoonStr;
+ private String[] mPSAMidniteStr;
+ private String[] mPSARandomStr;
+ private String[] mPSAEarlyEvenStr;
+ private String[] mWelcomeStr;
+ private BroadcastReceiver mPSAListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ psonalityEvent();
+ }
+ };
+
+ // NowPlaying
+ private boolean mEventNowPlaying = false;
+ private String mNowPlayingTitle;
+ private String mNowPlayingArtist;
+ private boolean mClientLost = true;
+ private boolean mPlayingActive = false;
+
+ public QuickEventsController(Context context) {
+ mContext = context;
+ initQuickEvents();
+ }
+
+ public void initQuickEvents() {
+ mPreferences = mContext.getSharedPreferences(LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
+ mIsFirstTimeDone = mPreferences.getBoolean(SETTING_DEVICE_INTRO_COMPLETED, false);
+ registerPSAListener();
+ updateQuickEvents();
+ }
+
+ private void registerPSAListener() {
+ if (mRegistered) return;
+ mRegistered = true;
+ IntentFilter psonalityIntent = new IntentFilter();
+ psonalityIntent.addAction(Intent.ACTION_TIME_TICK);
+ psonalityIntent.addAction(Intent.ACTION_TIME_CHANGED);
+ psonalityIntent.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ mContext.registerReceiver(mPSAListener, psonalityIntent, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private void unregisterPSAListener() {
+ if (!mRegistered) return;
+ mRegistered = false;
+ mContext.unregisterReceiver(mPSAListener);
+ }
+
+ public void updateQuickEvents() {
+ deviceIntroEvent();
+ nowPlayingEvent();
+ initNowPlayingEvent();
+ psonalityEvent();
+ }
+
+ private void deviceIntroEvent() {
+ if (!mRunning) return;
+
+ if (mIsFirstTimeDone) return;
+
+ mIsQuickEvent = true;
+
+ if (Utilities.useAlternativeQuickspaceUI(mContext)) {
+ mEventTitle = mContext.getResources().getString(R.string.quick_event_rom_intro_welcome_ext);
+ } else {
+ mEventTitle = mContext.getResources().getString(R.string.quick_event_rom_intro_welcome);
+ }
+ mWelcomeStr = mContext.getResources().getStringArray(R.array.welcome_message_variants);
+ mEventTitleSub = mWelcomeStr[getLuckyNumber(0,mWelcomeStr.length - 1)];
+ mEventSubIcon = R.drawable.ic_quickspace_derp;
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_general);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_two);
+
+ mEventTitleSubAction = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mContext.getSharedPreferences(LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(SETTING_DEVICE_INTRO_COMPLETED, true)
+ .commit();
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ try {
+ Launcher.getLauncher(mContext).startActivitySafely(view, intent, null);
+ } catch (ActivityNotFoundException ex) {
+ }
+ mIsQuickEvent = false;
+ }
+ };
+ }
+
+ public void nowPlayingEvent() {
+ if (mEventNowPlaying) {
+ boolean infoExpired = !mPlayingActive || mClientLost;
+ if (infoExpired) {
+ mIsQuickEvent = false;
+ mEventNowPlaying = false;
+ }
+ }
+ }
+
+ public void initNowPlayingEvent() {
+ if (!mRunning) return;
+
+ if (!mIsFirstTimeDone) return;
+
+ if (!Utilities.isQuickspaceNowPlaying(mContext)) return;
+
+ if (!mPlayingActive) return;
+
+ if (mNowPlayingTitle == null) return;
+
+ mEventTitle = mNowPlayingTitle;
+ mGreetings = mContext.getResources().getString(R.string.qe_now_playing_ext_one);
+ mClockExt = "";
+ if (mNowPlayingArtist == null ) {
+ mEventTitleSub = mContext.getResources().getString(R.string.qe_now_playing_unknown_artist);
+ } else {
+ mEventTitleSub = mNowPlayingArtist;
+ }
+ mEventSubIcon = R.drawable.ic_music_note_24dp;
+ mIsQuickEvent = true;
+ mEventNowPlaying = true;
+
+ mEventTitleSubAction = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mPlayingActive) {
+ MediaSessionManager mediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ List sessions = mediaSessionManager.getActiveSessions(null);
+
+ if (sessions != null && !sessions.isEmpty()) {
+ MediaController mediaController = sessions.get(0);
+ MediaSession.Token token = mediaController.getSessionToken();
+ PendingIntent sessionActivity = mediaController.getSessionActivity();
+
+ if (sessionActivity != null) {
+ Intent intent = sessionActivity.getIntent();
+
+ if (intent != null) {
+ ComponentName componentName = intent.getComponent();
+ if (componentName != null) {
+ String packageName = componentName.getPackageName();
+ if (packageName != null) {
+ Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
+
+ if (launchIntent != null) {
+ launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ // try if package name launch intent works
+ mContext.startActivity(launchIntent);
+ return; // Exit the method after starting the activity
+ } catch (Exception e) {}
+ }
+ }
+ }
+
+ try {
+ // try session activity
+ mContext.startActivity(intent);
+ return; // Exit the method after starting the activity
+ } catch (Exception e) {}
+ }
+ }
+
+ // last resort: Work required for local media actions
+ Intent npIntent = new Intent(Intent.ACTION_MAIN);
+ npIntent.addCategory(Intent.CATEGORY_APP_MUSIC);
+ npIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ try {
+ Launcher.getLauncher(mContext).startActivitySafely(view, npIntent, null);
+ } catch (ActivityNotFoundException ex) {}
+ }
+ }
+ }
+ };
+ }
+
+ public void psonalityEvent() {
+ if (!mIsFirstTimeDone || mEventNowPlaying) return;
+ mEventSubIcon = 0;
+
+ mEventTitle = Utilities.formatDateTime(mContext);
+ mPSAMorningStr = mContext.getResources().getStringArray(R.array.quickspace_psa_morning);
+ mPSAEvenStr = mContext.getResources().getStringArray(R.array.quickspace_psa_evening);
+ mPSAEarlyEvenStr = mContext.getResources().getStringArray(R.array.quickspace_psa_early_evening);
+ mPSAMidniteStr = mContext.getResources().getStringArray(R.array.quickspace_psa_midnight);
+ mPSAAfterNoonStr = mContext.getResources().getStringArray(R.array.quickspace_psa_noon);
+ mPSARandomStr = mContext.getResources().getStringArray(R.array.quickspace_psa_random);
+ int psaLength;
+
+ mEventTitleSubAction = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
+ calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
+
+ Intent clockIntent = new Intent(AlarmClock.ACTION_SHOW_ALARMS);
+
+ PackageManager packageManager = mContext.getPackageManager();
+ List calendarApps = packageManager.queryIntentActivities(calendarIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ List clockApps = packageManager.queryIntentActivities(clockIntent, PackageManager.MATCH_DEFAULT_ONLY);
+
+ if (!calendarApps.isEmpty()) {
+ calendarIntent.setPackage(calendarApps.get(0).activityInfo.packageName);
+ try {
+ mContext.startActivity(calendarIntent);
+ } catch (ActivityNotFoundException e) {
+ }
+ } else if (!clockApps.isEmpty()) {
+ clockIntent.setPackage(clockApps.get(0).activityInfo.packageName);
+ try {
+ mContext.startActivity(clockIntent);
+ } catch (ActivityNotFoundException e) {
+ }
+ } else {
+ Toast.makeText(mContext, R.string.intent_no_app_clock_found, Toast.LENGTH_SHORT).show();
+ }
+ }
+ };
+
+ int hourOfDay = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
+
+ if (hourOfDay >= 5 && hourOfDay <= 9) {
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_morning);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_one);
+ } else if (hourOfDay >= 12 && hourOfDay <= 15) {
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_afternoon);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_two);
+ } else if (hourOfDay >= 16 && hourOfDay <= 20) {
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_evening);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_two);
+ } else if (hourOfDay >= 21 && hourOfDay <= 23) {
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_night);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_two);
+ } else if (hourOfDay >= 0 && hourOfDay <= 3) {
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_night);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_two);
+ } else {
+ mGreetings = mContext.getResources().getString(R.string.quickspace_grt_general);
+ mClockExt = mContext.getResources().getString(R.string.quickspace_ext_two);
+ }
+
+ if (Utilities.isQuickspacePersonalityEnabled(mContext)) {
+ if (getLuckyNumber(13) == 7) {
+ psaLength = mPSARandomStr.length - 1;
+ mEventTitleSub = mPSARandomStr[getLuckyNumber(0, psaLength)];
+ mEventSubIcon = R.drawable.ic_quickspace_derp;
+ mIsQuickEvent = true;
+ } else {
+ switch (hourOfDay) {
+ case 5: case 6: case 7: case 8: case 9:
+ psaLength = mPSAMorningStr.length - 1;
+ mEventTitleSub = mPSAMorningStr[getLuckyNumber(0, psaLength)];
+ mIsQuickEvent = true;
+ break;
+
+ case 19: case 20: case 21:
+ psaLength = mPSAEvenStr.length - 1;
+ mEventTitleSub = mPSAEvenStr[getLuckyNumber(0, psaLength)];
+ mIsQuickEvent = true;
+ break;
+
+ case 16: case 17: case 18:
+ psaLength = mPSAEarlyEvenStr.length - 1;
+ mEventTitleSub = mPSAEarlyEvenStr[getLuckyNumber(0, psaLength)];
+ mIsQuickEvent = true;
+ break;
+
+ case 12: case 13: case 14: case 15:
+ psaLength = mPSAAfterNoonStr.length - 1;
+ mEventTitleSub = mPSAAfterNoonStr[getLuckyNumber(0, psaLength)];
+ mIsQuickEvent = true;
+ break;
+
+ case 0: case 1: case 2: case 3:
+ psaLength = mPSAMidniteStr.length - 1;
+ mEventTitleSub = mPSAMidniteStr[getLuckyNumber(0, psaLength)];
+ mIsQuickEvent = true;
+ break;
+
+ default:
+ mEventTitleSub = null;
+ mIsQuickEvent = false;
+ break;
+ }
+ }
+ } else {
+ mEventTitleSub = null;
+ mIsQuickEvent = false;
+ }
+ }
+
+ public boolean isQuickEvent() {
+ return mIsQuickEvent;
+ }
+
+ public boolean isDeviceIntroCompleted() {
+ return mIsFirstTimeDone;
+ }
+
+ public String getTitle() {
+ return mEventTitle;
+ }
+
+ public String getActionTitle() {
+ return mEventTitleSub;
+ }
+
+ public String getClockExt() {
+ return mClockExt;
+ }
+
+ public String getGreetings() {
+ return mGreetings;
+ }
+
+ public OnClickListener getAction() {
+ return mEventTitleSubAction;
+ }
+
+ public int getActionIcon() {
+ return mEventSubIcon;
+ }
+
+ public int getLuckyNumber(int max) {
+ return getLuckyNumber(0, max);
+ }
+
+ public int getLuckyNumber(int min, int max) {
+ Random r = new Random();
+ return r.nextInt((max - min) + 1) + min;
+ }
+
+ public void setMediaInfo(String title, String artist, boolean clientLost, boolean activePlayback) {
+ mNowPlayingTitle = title;
+ mNowPlayingArtist = artist;
+ mClientLost = clientLost;
+ mPlayingActive = activePlayback;
+ }
+
+ public boolean isNowPlaying() {
+ return mPlayingActive && Utilities.isQuickspaceNowPlaying(mContext);
+ }
+
+ public void onPause() {
+ mRunning = false;
+ unregisterPSAListener();
+ }
+
+ public void onResume() {
+ mRunning = true;
+ registerPSAListener();
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/QuickSpaceView.java b/src/com/android/launcher3/quickspace/QuickSpaceView.java
new file mode 100644
index 00000000000..7902cd96db5
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/QuickSpaceView.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2018-2023 crDroid Android 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.quickspace;
+
+import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.text.TextUtils.TruncateAt;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Themes;
+
+import com.android.launcher3.quickspace.QuickspaceController.OnDataListener;
+import com.android.launcher3.quickspace.receivers.QuickSpaceActionReceiver;
+import com.android.launcher3.quickspace.views.DateTextView;
+
+public class QuickSpaceView extends FrameLayout implements AnimatorUpdateListener, OnDataListener {
+
+ private static final String TAG = "Launcher3:QuickSpaceView";
+ private static final boolean DEBUG = false;
+
+ public final ColorStateList mColorStateList;
+ public BubbleTextView mBubbleTextView;
+ public final int mQuickspaceBackgroundRes;
+
+ public ViewGroup mQuickspaceContent;
+ public ImageView mEventSubIcon;
+ public TextView mEventTitleSub;
+ public TextView mEventTitleSubColored;
+ public TextView mGreetingsExt;
+ public TextView mGreetingsExtClock;
+ public ViewGroup mWeatherContentSub;
+ public ImageView mWeatherIconSub;
+ public TextView mWeatherTempSub;
+ public TextView mEventTitle;
+
+ public boolean mIsQuickEvent;
+ public boolean mFinishedInflate;
+ public boolean mWeatherAvailable;
+ public boolean mAttached;
+
+ private QuickSpaceActionReceiver mActionReceiver;
+ public QuickspaceController mController;
+
+ public QuickSpaceView(Context context, AttributeSet set) {
+ super(context, set);
+ mActionReceiver = new QuickSpaceActionReceiver(context);
+ mController = new QuickspaceController(context);
+ mColorStateList = ColorStateList.valueOf(Themes.getAttrColor(getContext(), R.attr.workspaceTextColor));
+ mQuickspaceBackgroundRes = R.drawable.bg_quickspace;
+ setClipChildren(false);
+ }
+
+ @Override
+ public void onDataUpdated() {
+ boolean altUI = Utilities.useAlternativeQuickspaceUI(getContext());
+ mController.getEventController().initQuickEvents();
+ mIsQuickEvent = mController.isQuickEvent();
+ if (mEventTitle == null || (altUI && mGreetingsExt == null)) {
+ prepareLayout(altUI);
+ }
+ mWeatherAvailable = mController.isWeatherAvailable() &&
+ mController.getEventController().isDeviceIntroCompleted();
+ loadDoubleLine(altUI);
+ }
+
+ private final void loadDoubleLine(boolean useAlternativeQuickspaceUI) {
+ setBackgroundResource(mQuickspaceBackgroundRes);
+ mEventTitle.setText(mController.getEventController().getTitle());
+ if (useAlternativeQuickspaceUI) {
+ if (!mController.getEventController().getGreetings().isEmpty()) {
+ mGreetingsExt.setVisibility(View.VISIBLE);
+ mGreetingsExt.setText(mController.getEventController().getGreetings());
+ mGreetingsExt.setEllipsize(TruncateAt.END);
+ mGreetingsExt.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ mGreetingsExt.setVisibility(View.GONE);
+ }
+ if (!mController.getEventController().getClockExt().isEmpty()) {
+ mGreetingsExtClock.setVisibility(View.VISIBLE);
+ mGreetingsExtClock.setText(mController.getEventController().getClockExt());
+ mGreetingsExtClock.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ mGreetingsExtClock.setVisibility(View.GONE);
+ }
+ }
+ if (mIsQuickEvent && (Utilities.isQuickspacePersonalityEnabled(getContext()) ||
+ mController.getEventController().isNowPlaying())) {
+ mEventTitle.setEllipsize(TruncateAt.MARQUEE);
+ mEventTitle.setMarqueeRepeatLimit(3);
+ mEventTitle.setSelected(true);
+ mEventTitle.setOnClickListener(mController.getEventController().getAction());
+ mEventTitleSub.setVisibility(View.VISIBLE);
+ mEventTitleSub.setText(mController.getEventController().getActionTitle());
+ mEventTitleSub.setEllipsize(TruncateAt.MARQUEE);
+ mEventTitleSub.setMarqueeRepeatLimit(3);
+ mEventTitleSub.setSelected(true);
+ mEventTitleSub.setOnClickListener(mController.getEventController().getAction());
+ if (useAlternativeQuickspaceUI) {
+ if (mController.getEventController().isNowPlaying()) {
+ mEventSubIcon.setVisibility(View.GONE);
+ mEventTitleSub.setOnClickListener(mController.getEventController().getAction());
+ mEventTitleSubColored.setVisibility(View.VISIBLE);
+ mEventTitleSubColored.setText(getContext().getString(R.string.qe_now_playing_by));
+ mEventTitleSubColored.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ setEventSubIcon();
+ mEventTitleSubColored.setText("");
+ mEventTitleSubColored.setVisibility(View.GONE);
+ }
+ } else {
+ setEventSubIcon();
+ }
+ } else {
+ mEventTitleSub.setVisibility(View.GONE);
+ mEventSubIcon.setVisibility(View.GONE);
+ if (useAlternativeQuickspaceUI) {
+ mEventTitleSubColored.setVisibility(View.GONE);
+ }
+ }
+ bindWeather(mWeatherContentSub, mWeatherTempSub, mWeatherIconSub);
+ }
+
+ private void setEventSubIcon() {
+ int icon = mController.getEventController().getActionIcon();
+ if (icon > 0) {
+ mEventSubIcon.setVisibility(View.VISIBLE);
+ mEventSubIcon.setImageTintList(mColorStateList);
+ mEventSubIcon.setImageResource(mController.getEventController().getActionIcon());
+ mEventSubIcon.setOnClickListener(mController.getEventController().getAction());
+ } else {
+ mEventSubIcon.setVisibility(View.GONE);
+ }
+ }
+
+ private final void bindWeather(View container, TextView title, ImageView icon) {
+ if (!mWeatherAvailable || mController.getEventController().isNowPlaying()) {
+ container.setVisibility(View.GONE);
+ return;
+ }
+ String weatherTemp = mController.getWeatherTemp();
+ if (weatherTemp == null || weatherTemp.isEmpty()) {
+ container.setVisibility(View.GONE);
+ return;
+ }
+ boolean hasGoogleApp = isPackageEnabled("com.google.android.googlequicksearchbox", getContext());
+ container.setVisibility(View.VISIBLE);
+ container.setOnClickListener(hasGoogleApp ? mActionReceiver.getWeatherAction() : null);
+ title.setText(weatherTemp);
+ icon.setImageDrawable(mController.getWeatherIcon());
+ }
+
+ private final void loadViews() {
+ mEventTitle = (TextView) findViewById(R.id.quick_event_title);
+ mEventTitleSub = (TextView) findViewById(R.id.quick_event_title_sub);
+ mEventTitleSubColored = (TextView) findViewById(R.id.quick_event_title_sub_colored);
+ mEventSubIcon = (ImageView) findViewById(R.id.quick_event_icon_sub);
+ mWeatherIconSub = (ImageView) findViewById(R.id.quick_event_weather_icon);
+ mQuickspaceContent = (ViewGroup) findViewById(R.id.quickspace_content);
+ mWeatherContentSub = (ViewGroup) findViewById(R.id.quick_event_weather_content);
+ mWeatherTempSub = (TextView) findViewById(R.id.quick_event_weather_temp);
+ if (Utilities.useAlternativeQuickspaceUI(getContext())) {
+ mGreetingsExtClock = (TextView) findViewById(R.id.extended_greetings_clock);
+ mGreetingsExt = (TextView) findViewById(R.id.extended_greetings);
+ }
+ }
+
+ private void prepareLayout(boolean useAlternativeQuickspaceUI) {
+ int indexOfChild = indexOfChild(mQuickspaceContent);
+ removeView(mQuickspaceContent);
+ if (useAlternativeQuickspaceUI) {
+ addView(LayoutInflater.from(getContext()).inflate(R.layout.quickspace_alternate_double, this, false), indexOfChild);
+ } else {
+ addView(LayoutInflater.from(getContext()).inflate(R.layout.quickspace_doubleline, this, false), indexOfChild);
+ }
+
+ loadViews();
+ getQuickSpaceView();
+ }
+
+ private void getQuickSpaceView() {
+ if (mQuickspaceContent.getVisibility() != View.VISIBLE) {
+ mQuickspaceContent.setVisibility(View.VISIBLE);
+ mQuickspaceContent.setAlpha(0.0f);
+ mQuickspaceContent.animate().setDuration(200).alpha(1.0f);
+ }
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ invalidate();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mAttached)
+ return;
+
+ mAttached = true;
+ if (mController != null && mFinishedInflate) {
+ mController.addListener(this);
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (!mAttached)
+ return;
+
+ mAttached = false;
+ if (mController != null) {
+ mController.removeListener(this);
+ }
+ }
+
+ public boolean isPackageEnabled(String pkgName, Context context) {
+ try {
+ return context.getPackageManager().getApplicationInfo(pkgName, 0).enabled;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ loadViews();
+ mFinishedInflate = true;
+ mBubbleTextView = findViewById(R.id.dummyBubbleTextView);
+ mBubbleTextView.setTag(new ItemInfo() {
+ @Override
+ public ComponentName getTargetComponent() {
+ return new ComponentName(getContext(), "");
+ }
+ });
+ mBubbleTextView.setContentDescription("");
+ if (isAttachedToWindow()) {
+ if (mController != null) {
+ mController.addListener(this);
+ }
+ }
+ }
+
+ @Override
+ public void onLayout(boolean b, int n, int n2, int n3, int n4) {
+ super.onLayout(b, n, n2, n3, n4);
+ }
+
+ public void onPause() {
+ mController.onPause();
+ }
+
+ public void onResume() {
+ mController.onResume();
+ }
+
+ public void setPadding(int n, int n2, int n3, int n4) {
+ super.setPadding(0, 0, 0, 0);
+ }
+
+}
diff --git a/src/com/android/launcher3/quickspace/QuickspaceController.java b/src/com/android/launcher3/quickspace/QuickspaceController.java
new file mode 100644
index 00000000000..c7dae879b8f
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/QuickspaceController.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2018 CypherOS
+ *
+ * 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.quickspace;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.media.AudioManager;
+import android.media.MediaMetadataRetriever;
+import android.media.RemoteControlClient;
+import android.media.RemoteController;
+import android.os.Handler;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.internal.util.derp.OmniJawsClient;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.notification.NotificationKeyData;
+import com.android.launcher3.notification.NotificationListener;
+import com.android.launcher3.util.PackageUserKey;
+
+import java.util.ArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.List;
+
+public class QuickspaceController implements NotificationListener.NotificationsChangedListener, OmniJawsClient.OmniJawsObserver {
+
+ public final ArrayList mListeners = new ArrayList();
+ private static final String SETTING_WEATHER_LOCKSCREEN_UNIT = "weather_lockscreen_unit";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "Launcher3:QuickspaceController";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private QuickEventsController mEventsController;
+ private OmniJawsClient mWeatherClient;
+ private OmniJawsClient.WeatherInfo mWeatherInfo;
+ private Drawable mConditionImage;
+
+ private boolean mUseImperialUnit;
+
+ private AudioManager mAudioManager;
+ private Metadata mMetadata = new Metadata();
+ private RemoteController mRemoteController;
+ private boolean mClientLost = true;
+ private boolean mMediaActive = false;
+ private ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+ public interface OnDataListener {
+ void onDataUpdated();
+ }
+
+ public QuickspaceController(Context context) {
+ mContext = context;
+ mHandler = new Handler();
+ mEventsController = new QuickEventsController(context);
+ mWeatherClient = new OmniJawsClient(context);
+ mRemoteController = new RemoteController(context, mRCClientUpdateListener);
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mAudioManager.registerRemoteController(mRemoteController);
+ }
+
+ private void addWeatherProvider() {
+ if (!Utilities.isQuickspaceWeather(mContext)) return;
+ mWeatherClient.addObserver(this);
+ queryAndUpdateWeather();
+ }
+
+ public void addListener(OnDataListener listener) {
+ mListeners.add(listener);
+ addWeatherProvider();
+ listener.onDataUpdated();
+ }
+
+ public void removeListener(OnDataListener listener) {
+ if (mWeatherClient != null) {
+ mWeatherClient.removeObserver(this);
+ }
+ mListeners.remove(listener);
+ }
+
+ public boolean isQuickEvent() {
+ return mEventsController.isQuickEvent();
+ }
+
+ public QuickEventsController getEventController() {
+ return mEventsController;
+ }
+
+ public boolean isWeatherAvailable() {
+ return mWeatherClient != null && mWeatherClient.isOmniJawsEnabled();
+ }
+
+ public Drawable getWeatherIcon() {
+ return mConditionImage;
+ }
+
+ public String getWeatherTemp() {
+ boolean shouldShowCity = Utilities.QuickSpaceShowCity(mContext);
+ boolean showWeatherText = Utilities.QuickSpaceShowWeatherText(mContext);
+ if (mWeatherInfo != null) {
+ String formattedCondition = mWeatherInfo.condition;
+ if (formattedCondition.toLowerCase().contains("clouds")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_clouds);
+ } else if (formattedCondition.toLowerCase().contains("rain")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_rain);
+ } else if (formattedCondition.toLowerCase().contains("clear")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_clear);
+ } else if (formattedCondition.toLowerCase().contains("storm")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_storm);
+ } else if (formattedCondition.toLowerCase().contains("snow")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_snow);
+ } else if (formattedCondition.toLowerCase().contains("wind")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_wind);
+ } else if (formattedCondition.toLowerCase().contains("mist")) {
+ formattedCondition = mContext.getResources().getString(R.string.quick_event_weather_mist);
+ }
+ String weatherTemp = (shouldShowCity ? mWeatherInfo.city : "") + " " + mWeatherInfo.temp +
+ mWeatherInfo.tempUnits + (showWeatherText ? " · " + formattedCondition : "");
+ return weatherTemp;
+ }
+ return null;
+ }
+
+ private void playbackStateUpdate(int state) {
+ boolean active;
+ switch (state) {
+ case RemoteControlClient.PLAYSTATE_PLAYING:
+ active = true;
+ break;
+ case RemoteControlClient.PLAYSTATE_ERROR:
+ case RemoteControlClient.PLAYSTATE_PAUSED:
+ default:
+ active = false;
+ break;
+ }
+ if (active != mMediaActive) {
+ mMediaActive = active;
+ }
+ updateMediaInfo();
+ }
+
+ public void updateMediaInfo() {
+ if (mEventsController != null) {
+ mEventsController.setMediaInfo(mMetadata.trackTitle, mMetadata.trackArtist, mClientLost, mMediaActive);
+ mEventsController.updateQuickEvents();
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void onNotificationPosted(PackageUserKey postedPackageUserKey,
+ NotificationKeyData notificationKey) {
+ updateMediaInfo();
+ }
+
+ @Override
+ public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
+ NotificationKeyData notificationKey) {
+ updateMediaInfo();
+ }
+
+ @Override
+ public void onNotificationFullRefresh(List activeNotifications) {
+ updateMediaInfo();
+ }
+
+ public void onPause() {
+ if (mEventsController != null) mEventsController.onPause();
+ }
+
+ public void onResume() {
+ if (mEventsController != null) {
+ updateMediaInfo();
+ mEventsController.onResume();
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void weatherUpdated() {
+ queryAndUpdateWeather();
+ }
+
+ @Override
+ public void weatherError(int errorReason) {
+ Log.d(TAG, "weatherError " + errorReason);
+ if (errorReason == OmniJawsClient.EXTRA_ERROR_DISABLED) {
+ mWeatherInfo = null;
+ notifyListeners();
+ }
+ }
+
+ @Override
+ public void updateSettings() {
+ Log.i(TAG, "updateSettings");
+ queryAndUpdateWeather();
+ }
+
+ private void queryAndUpdateWeather() {
+ executorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mWeatherClient.queryWeather();
+ mWeatherInfo = mWeatherClient.getWeatherInfo();
+ if (mWeatherInfo != null) {
+ mConditionImage = mWeatherClient.getWeatherConditionImage(mWeatherInfo.conditionCode);
+ }
+ notifyListeners();
+ } catch(Exception e) {
+ // Do nothing
+ }
+ }
+ });
+ }
+
+ public void notifyListeners() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (OnDataListener list : mListeners) {
+ list.onDataUpdated();
+ }
+ }
+ });
+ }
+
+ private RemoteController.OnClientUpdateListener mRCClientUpdateListener =
+ new RemoteController.OnClientUpdateListener() {
+
+ @Override
+ public void onClientChange(boolean clearing) {
+ if (clearing) {
+ mMetadata.clear();
+ mMediaActive = false;
+ mClientLost = true;
+ }
+ updateMediaInfo();
+ }
+
+ @Override
+ public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
+ long currentPosMs, float speed) {
+ mClientLost = false;
+ playbackStateUpdate(state);
+ }
+
+ @Override
+ public void onClientPlaybackStateUpdate(int state) {
+ mClientLost = false;
+ playbackStateUpdate(state);
+ }
+
+ @Override
+ public void onClientMetadataUpdate(RemoteController.MetadataEditor data) {
+ mMetadata.trackTitle = data.getString(MediaMetadataRetriever.METADATA_KEY_TITLE,
+ mMetadata.trackTitle);
+ mMetadata.trackArtist = data.getString(MediaMetadataRetriever.METADATA_KEY_ARTIST,
+ mMetadata.trackArtist);
+ mClientLost = false;
+ updateMediaInfo();
+ }
+
+ @Override
+ public void onClientTransportControlUpdate(int transportControlFlags) {
+ }
+ };
+
+ class Metadata {
+ private String trackTitle;
+ private String trackArtist;
+
+ public void clear() {
+ trackTitle = null;
+ trackArtist = null;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/receivers/QuickSpaceActionReceiver.java b/src/com/android/launcher3/quickspace/receivers/QuickSpaceActionReceiver.java
new file mode 100644
index 00000000000..e67645ae60b
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/receivers/QuickSpaceActionReceiver.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2018-2023 crDroid Android 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.quickspace.receivers;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.CalendarContract;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+public class QuickSpaceActionReceiver {
+
+ private static Context mContext;
+ private final LauncherApps mLauncherApps;
+
+ public OnClickListener mCalendarClickListener;
+ public OnClickListener mWeatherClickListener;
+
+ public QuickSpaceActionReceiver(Context context) {
+ mContext = context;
+ mLauncherApps = context.getSystemService(LauncherApps.class);
+
+ mCalendarClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ openGoogleCalendar(view);
+ }
+ };
+
+ mWeatherClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ openGoogleWeather(view);
+ }
+ };
+ }
+
+ private void openGoogleCalendar(View view) {
+ final Uri content_URI = CalendarContract.CONTENT_URI;
+ final Uri.Builder appendPath = content_URI.buildUpon().appendPath("time");
+ ContentUris.appendId(appendPath, System.currentTimeMillis());
+ final Intent addFlags = new Intent(Intent.ACTION_VIEW)
+ .setData(appendPath.build())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ try {
+ Launcher.getLauncher(mContext).startActivitySafely(view, addFlags, null);
+ } catch (ActivityNotFoundException ex) {
+ mLauncherApps.startAppDetailsActivity(new ComponentName("com.google.android.googlequicksearchbox", ""), Process.myUserHandle(), null, null);
+ }
+ }
+
+ private void openGoogleWeather(View view) {
+ Intent intent = new Intent("android.intent.action.VIEW");
+ intent.setData(Uri.parse("dynact://velour/weather/ProxyActivity"));
+ intent.setComponent(new ComponentName("com.google.android.googlequicksearchbox", "com.google.android.apps.gsa.velour.DynamicActivityTrampoline"));
+ try {
+ Launcher.getLauncher(mContext).startActivitySafely(view, intent, null);
+ } catch (ActivityNotFoundException ex) {
+ mLauncherApps.startAppDetailsActivity(new ComponentName("com.google.android.googlequicksearchbox",
+ "com.google.android.apps.gsa.velour.DynamicActivityTrampoline"), Process.myUserHandle(), null, null);
+ }
+ }
+
+ public OnClickListener getCalendarAction() {
+ return mCalendarClickListener;
+ }
+
+ public OnClickListener getWeatherAction() {
+ return mWeatherClickListener;
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/views/DateTextView.java b/src/com/android/launcher3/quickspace/views/DateTextView.java
new file mode 100644
index 00000000000..015ce71459d
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/views/DateTextView.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018-2023 crDroid Android 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.quickspace.views;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
+import java.util.Locale;
+
+public class DateTextView extends DoubleShadowTextView {
+
+ private DateFormat mDateFormat;
+ private final BroadcastReceiver mTimeChangeReceiver;
+ private boolean mIsVisible = false;
+
+ public DateTextView(final Context context) {
+ this(context, null);
+ }
+
+ public DateTextView(final Context context, final AttributeSet set) {
+ super(context, set, 0);
+ mTimeChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadDateFormat(!Intent.ACTION_TIME_TICK.equals(intent.getAction()));
+ }
+ };
+ }
+
+ public void reloadDateFormat(boolean forcedChange) {
+ String format;
+ if (mDateFormat == null || forcedChange) {
+ String styleText;
+ Context context = getContext();
+ if (Utilities.useAlternativeQuickspaceUI(context)) {
+ styleText = context.getString(R.string.quickspace_date_format_minimalistic);
+ } else {
+ styleText = context.getString(R.string.quickspace_date_format);
+ }
+ mDateFormat = DateFormat.getInstanceForSkeleton(styleText, Locale.getDefault());
+ mDateFormat.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ }
+ format = mDateFormat.format(System.currentTimeMillis());
+ setText(format);
+ setContentDescription(format);
+ }
+
+ private void registerReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_TIME_TICK);
+ intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
+ intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ getContext().registerReceiver(mTimeChangeReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private void unregisterReceiver() {
+ getContext().unregisterReceiver(mTimeChangeReceiver);
+ }
+
+ public void onVisibilityAggregated(boolean isVisible) {
+ super.onVisibilityAggregated(isVisible);
+ if (!mIsVisible && isVisible) {
+ mIsVisible = true;
+ registerReceiver();
+ reloadDateFormat(true);
+ } else if (mIsVisible && !isVisible) {
+ unregisterReceiver();
+ mIsVisible = false;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/quickspace/views/DoubleShadowTextView.java b/src/com/android/launcher3/quickspace/views/DoubleShadowTextView.java
new file mode 100644
index 00000000000..1bf019f0d6b
--- /dev/null
+++ b/src/com/android/launcher3/quickspace/views/DoubleShadowTextView.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018-2023 crDroid Android 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.quickspace.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.launcher3.views.DoubleShadowBubbleTextView;
+
+public class DoubleShadowTextView extends TextView {
+
+ private final DoubleShadowBubbleTextView.ShadowInfo mShadowInfo;
+
+ public DoubleShadowTextView(Context context) {
+ this(context, null);
+ }
+
+ public DoubleShadowTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mShadowInfo = new DoubleShadowBubbleTextView.ShadowInfo(context, attrs, defStyleAttr);
+ setShadowLayer(Math.max(mShadowInfo.keyShadowBlur + mShadowInfo.keyShadowOffsetX, mShadowInfo.ambientShadowBlur), 0f, 0f, mShadowInfo.keyShadowColor);
+ }
+
+ protected void onDraw(Canvas canvas) {
+ if (mShadowInfo.skipDoubleShadow(this)) {
+ super.onDraw(canvas);
+ return;
+ }
+ getPaint().setShadowLayer(mShadowInfo.ambientShadowBlur, 0.0f, 0.0f, mShadowInfo.ambientShadowColor);
+ super.onDraw(canvas);
+ getPaint().setShadowLayer(mShadowInfo.keyShadowBlur, 0.0f, mShadowInfo.keyShadowOffsetX, mShadowInfo.keyShadowColor);
+ super.onDraw(canvas);
+ }
+}
diff --git a/src/com/android/launcher3/settings/SettingsHomescreen.java b/src/com/android/launcher3/settings/SettingsHomescreen.java
index dea4e35c105..83c44ee9a82 100644
--- a/src/com/android/launcher3/settings/SettingsHomescreen.java
+++ b/src/com/android/launcher3/settings/SettingsHomescreen.java
@@ -46,6 +46,8 @@
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.internal.util.derp.OmniJawsClient;
+
public class SettingsHomescreen extends CollapsingToolbarBaseActivity
implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback,
SharedPreferences.OnSharedPreferenceChangeListener{
@@ -109,6 +111,13 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin
case Utilities.KEY_HOTSEAT_OPACITY:
case Utilities.KEY_SHORT_PARALLAX:
case Utilities.KEY_SINGLE_PAGE_CENTER:
+ case Utilities.DESKTOP_SHOW_QUICKSPACE:
+ case Utilities.KEY_SHOW_ALT_QUICKSPACE:
+ case Utilities.KEY_SHOW_QUICKSPACE_NOWPLAYING:
+ case Utilities.KEY_SHOW_QUICKSPACE_WEATHER:
+ case Utilities.KEY_SHOW_QUICKSPACE_PSONALITY:
+ case Utilities.KEY_SHOW_QUICKSPACE_WEATHER_CITY:
+ case Utilities.KEY_SHOW_QUICKSPACE_WEATHER_TEXT:
LauncherAppState.getInstanceNoCreate().setNeedsRestart();
break;
default:
@@ -168,6 +177,9 @@ public static class HomescreenSettingsFragment extends PreferenceFragmentCompat
private Preference mShowGoogleAppPref;
private Preference mShowGoogleBarPref;
+ private Preference mWeatherPref;
+
+ private OmniJawsClient mWeatherClient;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -193,6 +205,13 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
mShowGoogleBarPref = screen.findPreference(Utilities.KEY_DOCK_SEARCH);
updateIsGoogleAppEnabled();
+ mWeatherClient = new OmniJawsClient(getContext());
+ mWeatherPref = getPreferenceScreen().findPreference(Utilities.KEY_SHOW_QUICKSPACE_WEATHER);
+ if (!mWeatherClient.isOmniJawsEnabled()) {
+ mWeatherPref.setEnabled(false);
+ mWeatherPref.setSummary(R.string.quick_event_ambient_weather_enabled_info);
+ }
+
if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) {
getActivity().setTitle(getPreferenceScreen().getTitle());
}
diff --git a/src/com/android/launcher3/settings/SettingsRecents.java b/src/com/android/launcher3/settings/SettingsRecents.java
index aca62037006..07fc11fb7d7 100644
--- a/src/com/android/launcher3/settings/SettingsRecents.java
+++ b/src/com/android/launcher3/settings/SettingsRecents.java
@@ -100,7 +100,15 @@ protected void onCreate(Bundle savedInstanceState) {
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { }
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ switch (key) {
+ case Utilities.KEY_RECENTS_CHIPS:
+ LauncherAppState.getInstanceNoCreate().setNeedsRestart();
+ break;
+ default:
+ break;
+ }
+ }
private boolean startPreference(String fragment, Bundle args, String key) {
if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) {