From b3de38c976eeb5a8c4b5c1b0d68d9c84253d3187 Mon Sep 17 00:00:00 2001 From: Yvonne Wong Date: Fri, 20 Nov 2015 10:49:59 -0800 Subject: [PATCH] Reimplement CM Settings Overview Panel in the new Launcher Part 1 - Adds vertical sliding panel and animations associated with opening and closing the panel - Adds the views for settings and animation for the drawer arrow - Enables hiding workspace icon labels, hiding drawer icon labels, scrolling wallpaper, and larger icons - Changes how ragged grid custom icon sizes gets defined Change-Id: I1a82215a09486b4770494e665e598efdbabd1d3e --- res/drawable-hdpi/ic_default_screen.png | Bin 0 -> 2046 bytes res/drawable-hdpi/ic_default_screen_pressed.png | Bin 0 -> 1993 bytes res/drawable-mdpi/ic_default_screen.png | Bin 0 -> 1597 bytes res/drawable-mdpi/ic_default_screen_pressed.png | Bin 0 -> 1626 bytes res/drawable-xhdpi/ic_default_screen.png | Bin 0 -> 2323 bytes res/drawable-xhdpi/ic_default_screen_pressed.png | Bin 0 -> 2385 bytes res/drawable-xxhdpi/ic_default_screen.png | Bin 0 -> 3187 bytes res/drawable-xxhdpi/ic_default_screen_pressed.png | Bin 0 -> 3237 bytes res/drawable/above_shadow.xml | 8 + res/drawable/below_shadow.xml | 8 + res/drawable/default_screen_button.xml | 19 + res/drawable/launcheranimatedarrow_00000.png | Bin 0 -> 661 bytes res/drawable/launcheranimatedarrow_00001.png | Bin 0 -> 749 bytes res/drawable/launcheranimatedarrow_00002.png | Bin 0 -> 705 bytes res/drawable/launcheranimatedarrow_00003.png | Bin 0 -> 746 bytes res/drawable/launcheranimatedarrow_00004.png | Bin 0 -> 747 bytes res/drawable/launcheranimatedarrow_00005.png | Bin 0 -> 664 bytes res/drawable/launcheranimatedarrow_00006.png | Bin 0 -> 1007 bytes res/drawable/launcheranimatedarrow_00007.png | Bin 0 -> 999 bytes res/drawable/launcheranimatedarrow_00008.png | Bin 0 -> 710 bytes res/drawable/launcheranimatedarrow_00009.png | Bin 0 -> 968 bytes res/drawable/launcheranimatedarrow_00010.png | Bin 0 -> 990 bytes res/drawable/launcheranimatedarrow_00011.png | Bin 0 -> 671 bytes res/drawable/launcheranimatedarrow_00012.png | Bin 0 -> 744 bytes res/drawable/launcheranimatedarrow_00013.png | Bin 0 -> 750 bytes res/drawable/launcheranimatedarrow_00014.png | Bin 0 -> 747 bytes res/drawable/launcheranimatedarrow_00015.png | Bin 0 -> 736 bytes res/drawable/launcheranimatedarrow_00016.png | Bin 0 -> 668 bytes res/drawable/listitem_bg.xml | 6 + res/drawable/listitem_text.xml | 6 + res/drawable/transition_arrow.xml | 21 + res/drawable/transition_arrow_reverse.xml | 21 + res/layout/overview_panel.xml | 175 ++- res/layout/settings_pane_list_header.xml | 19 + res/layout/settings_pane_list_item.xml | 38 + res/values/attrs.xml | 10 + res/values/cm_strings.xml | 36 + res/values/colors.xml | 4 + res/values/dimens.xml | 11 +- res/values/preferences_defaults.xml | 9 + src/com/android/launcher3/BubbleTextView.java | 14 +- src/com/android/launcher3/CellLayout.java | 5 +- src/com/android/launcher3/DeviceProfile.java | 4 +- src/com/android/launcher3/Folder.java | 14 +- src/com/android/launcher3/FolderPagedView.java | 6 + .../android/launcher3/InvariantDeviceProfile.java | 42 +- src/com/android/launcher3/Launcher.java | 123 +- src/com/android/launcher3/LauncherAppState.java | 4 + src/com/android/launcher3/LauncherModel.java | 1 + src/com/android/launcher3/OverviewPanel.java | 32 + .../android/launcher3/OverviewSettingsPanel.java | 74 ++ .../android/launcher3/VerticalSlidingPanel.java | 1317 ++++++++++++++++++++ src/com/android/launcher3/Workspace.java | 47 +- .../launcher3/allapps/AllAppsContainerView.java | 5 + .../launcher3/allapps/AllAppsGridAdapter.java | 31 +- .../android/launcher3/list/AutoScrollListView.java | 117 ++ .../launcher3/list/CompositeCursorAdapter.java | 532 ++++++++ .../launcher3/list/PinnedHeaderListAdapter.java | 137 ++ .../launcher3/list/PinnedHeaderListView.java | 565 +++++++++ .../list/SettingsPinnedHeaderAdapter.java | 307 +++++ .../launcher3/settings/SettingsProvider.java | 84 ++ .../launcher3/InvariantDeviceProfileTest.java | 2 +- 62 files changed, 3756 insertions(+), 98 deletions(-) create mode 100644 res/drawable-hdpi/ic_default_screen.png create mode 100644 res/drawable-hdpi/ic_default_screen_pressed.png create mode 100644 res/drawable-mdpi/ic_default_screen.png create mode 100644 res/drawable-mdpi/ic_default_screen_pressed.png create mode 100644 res/drawable-xhdpi/ic_default_screen.png create mode 100644 res/drawable-xhdpi/ic_default_screen_pressed.png create mode 100644 res/drawable-xxhdpi/ic_default_screen.png create mode 100644 res/drawable-xxhdpi/ic_default_screen_pressed.png create mode 100644 res/drawable/above_shadow.xml create mode 100644 res/drawable/below_shadow.xml create mode 100644 res/drawable/default_screen_button.xml create mode 100644 res/drawable/launcheranimatedarrow_00000.png create mode 100644 res/drawable/launcheranimatedarrow_00001.png create mode 100644 res/drawable/launcheranimatedarrow_00002.png create mode 100644 res/drawable/launcheranimatedarrow_00003.png create mode 100644 res/drawable/launcheranimatedarrow_00004.png create mode 100644 res/drawable/launcheranimatedarrow_00005.png create mode 100644 res/drawable/launcheranimatedarrow_00006.png create mode 100644 res/drawable/launcheranimatedarrow_00007.png create mode 100644 res/drawable/launcheranimatedarrow_00008.png create mode 100644 res/drawable/launcheranimatedarrow_00009.png create mode 100644 res/drawable/launcheranimatedarrow_00010.png create mode 100644 res/drawable/launcheranimatedarrow_00011.png create mode 100644 res/drawable/launcheranimatedarrow_00012.png create mode 100644 res/drawable/launcheranimatedarrow_00013.png create mode 100644 res/drawable/launcheranimatedarrow_00014.png create mode 100644 res/drawable/launcheranimatedarrow_00015.png create mode 100644 res/drawable/launcheranimatedarrow_00016.png create mode 100644 res/drawable/listitem_bg.xml create mode 100644 res/drawable/listitem_text.xml create mode 100644 res/drawable/transition_arrow.xml create mode 100644 res/drawable/transition_arrow_reverse.xml create mode 100644 res/layout/settings_pane_list_header.xml create mode 100644 res/layout/settings_pane_list_item.xml create mode 100644 res/values/preferences_defaults.xml create mode 100644 src/com/android/launcher3/OverviewPanel.java create mode 100644 src/com/android/launcher3/OverviewSettingsPanel.java create mode 100644 src/com/android/launcher3/VerticalSlidingPanel.java create mode 100644 src/com/android/launcher3/list/AutoScrollListView.java create mode 100644 src/com/android/launcher3/list/CompositeCursorAdapter.java create mode 100644 src/com/android/launcher3/list/PinnedHeaderListAdapter.java create mode 100644 src/com/android/launcher3/list/PinnedHeaderListView.java create mode 100644 src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java create mode 100644 src/com/android/launcher3/settings/SettingsProvider.java diff --git a/res/drawable-hdpi/ic_default_screen.png b/res/drawable-hdpi/ic_default_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..41dcf8f6ed481e721a1b22c186640e4fc31bfbe3 GIT binary patch literal 2046 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 MxhzI{Qk+)$Uk!-~ng9R* literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_default_screen.png b/res/drawable-mdpi/ic_default_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..8a2c1e1c06b39c8d312a6007ddc6698bddb122a2 GIT binary patch literal 1597 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xd_B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxR5#hc$WX!DQqR!T z#M01EN5ROz&{*HlK;Otx*U-?)#N5izOaTg%fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7KMf`)Hma%LWg zuL;)R>ucqiS6q^qmz?V9Vygr+LN7Bj#md>j+{x0_)WF5m%*fEt)!5O{$kolr)!5S1 z&B@H!*cGPNB|o_|H#M&WrZ)wl*AS;(P+G_>0NU)5T9jFqn&MWJpQ`}&vsES*w>Vmw z8=IP0nBz1Lsy79TTcCO^aO%|uIz}H9vq&)w69Oh55EGtmfgE_sPt5}+_ab1rw#%Lz z&A`C4!PCVtq~g|<8F%w88;Beiza1-lB{X+i*kzZ~3A(c-SUDHQ792b}uUR~zlJ@~` z1>=#)4KpelI%gYQSt~Bh6B-sS|DFJ@;k;0s|gf4-r}_Jdf1)ka3f9{p&^J8=#jTMwRRoZ`yFVj&kEDj45w zB)6Qm;f*#U(;ZnxccB|=LLB~a{%crvM#j@%cF55k+nKv&cnj%qbS^)ePeD>0# zKOQzHO}(Lg+BCYL{JQw#-^X{0 zezs=L^naO9@a^mAkL?_H3+H7ES^Q9rUKnvE^jld!httnj?+#m~e(6e>BcF7q??&ph z;**j+yxYH5{5NH@_$Qk@=doS;(sQRI&YWS)t7Y6cV`9U#MGrU>UH`?oIB@r>dZ?aW ze>;2g85yS&_4-OFQA|lw!z^!~vsrbuFD*9gpPa+BN^4gK9maIMmBFi5SN!)}J5MPk zI9FxM!|l!Av_$vE=Y?*2Zy($^<=dwZA7q`5`JDWF`{c>l@zd=?gZtDk^K?h=%0DFEa#1jA{&~I zy<2TH>+x~f{T~k>e6wQSNxjn9Y)=35%3{AKZAp|!X7-wGo^)kth-hDNZ(ix5f444X zeDmAMWT+LIr=}TBCn#evQ7E zlb`bmLC;xJ>~Dm;bmp-1bh_lpbm{-c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxR5#hc$WX!DQqR!T z#M01EN5ROz&{*HlK;Otx*U-?)#N5izOaTg%fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7KMf`)Hma%LWg zuL;)R>ucqiS6q^qmz?V9Vygr+LN7Bj#mdpr+}PC2!qCOk%*fEt)!5O{$kolr)!5S1 z&B@H!*cGPNB|o_|H#M&WrZ)wl*9fOxP+G_>0NU)5T9jFqn&MWJpQ`}&vsETex0t)( zG!Lpb1-DxaaO%|uIz}H9vq&)w69Oh55EGtmfgE_sPt5}+_ab1rR*jkWn1O-mtfz}( zNX4xyGki0I9Yu~$extTK``U(OofV#IuQ>S0Wd*GNcA+^V@WY!}SAjjET47thJi55K zUF?fO$d|DF4y?A-x{WtvM6_m~O}l+IYuXRBeVmzh%x2zAF}ACS^e9WKt2F+ccF+D+ z)>b8(*&cR_E@~7Xe)%}Kuwu!9&;r&NCUKAQ2T$U6Kj7QJo7XsdY5vpuYaY9JvP3d4 z`ZT+XJ&ZWZa-S_A`9%YRTSL)cNj13eTCT7lN@$XOQeEA@n;ngoD7K=^m zKJD1&mYOm*k7>za7LI~;0Y9F^&1Ql{+rPI*%s|FS|CI^_&(qVl?*2YK@8qVMBZV@DS6$z(ZZxwd zbp15FGiPnT-FzE9%PXky;k0*mRSMp&)$o-1sOVGL<+o8v!Y@$GZG%`S*NPr4y_IVH zlfKt5X>DA`axx|T++155k0(N%F28N(PY_}iiHH*Ib}g+;efn#`Wv_e9yd}+Bl37Gv z=-Jt3aQj|vd9Hu$y-Mg;iEN89o!7^&o(`9jYUP|lu_oPUN%tm+{_W+3XIIoMzyJQ$ zw&>MYPgOf5zBP$_$PvDL)#2aQP24YP3$iXMmUppSbdWQq`IPeB@PAzD4Wj2~v1}Ep zJyTG%J?(L5Szh8Jxpqs7fB#Aixh!XExVL16{44nFC9mmdKI;Vst0J0lx6aWAK literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_default_screen.png b/res/drawable-xhdpi/ic_default_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..735332ae5d44213bad1c417e656a0298049af0ac GIT binary patch literal 2323 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_default_screen.png b/res/drawable-xxhdpi/ic_default_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..253bd2d4dcdde2477708c61aa36eeca469a51b74 GIT binary patch literal 3187 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_default_screen_pressed.png b/res/drawable-xxhdpi/ic_default_screen_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..5367b7abdd9736f97aee738acc9bb70a76e7d7b3 GIT binary patch literal 3237 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 EFM^OZWdHyG literal 0 HcmV?d00001 diff --git a/res/drawable/above_shadow.xml b/res/drawable/above_shadow.xml new file mode 100644 index 000000000..99db324ab --- /dev/null +++ b/res/drawable/above_shadow.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/below_shadow.xml b/res/drawable/below_shadow.xml new file mode 100644 index 000000000..fc70073a9 --- /dev/null +++ b/res/drawable/below_shadow.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/res/drawable/default_screen_button.xml b/res/drawable/default_screen_button.xml new file mode 100644 index 000000000..ad66137b0 --- /dev/null +++ b/res/drawable/default_screen_button.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/launcheranimatedarrow_00000.png b/res/drawable/launcheranimatedarrow_00000.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed7fe9a702c3163cc07544e71f85b5ff2fa14c6 GIT binary patch literal 661 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUN!-)LF{EP7 z+Z%iPUKxn6JlH5GDjKxlNM&P*$nSqw#a@15GF0t!aoy@w)BVFM`i*~*wdK9LKci+8 zm>g=9Y3P80ZHt$%SFGQAm*D}&0+s@%f)Dz(j3o?F4E>*q1ZTWUzA19!US2hWC`0r4 z=S2)U4a@e#+}k$mt#+iyi__HyzpS0TnEAsj=gz1qhWZ@sGy44h_D((DC8RHK+4elk zlb-WkrxqR%O585=bn~Jjje?UGeP{YAwKMVG-=8`)N61p-$Ks~}4}vvjyYR7=Io(t# z(%5kHOUuS7pCbc-atFRtY@6~mGVl@WucHplsmC`S{$jJ|l#h4HM;)GmnG=rq$D|9M z^eSsvyVd=9jAd5sB`;ZqgpzkFk8ce2+Inn;Z@uC6S?aqE>{|ETH8AAH+kN|f@;ATz zy>DmS`qlUTY@esT3MBRX^Uu_ftZQK|*4OX8|9*X|*OVFRuj&&5MXugSWK0No6(Zs) z`g!Kgv%)b8wtjojf6{38+>O`N?yGo%M2gqBJW#utK6%nC^&JNSUq8q#@G0xfK9&xx_Hw<$E6|N2Yff3 zS5r0hFK9eAgS{>Br&^`N8>#b0gFd_b?f6oAKjV2?{ok#{vG>3Go=JaZ#y_2N(&?7( k|6W;j!{QK$uA5)a|JlH;>qaavMKLgVy85}Sb4q9e0J^p{rT_o{ literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00001.png b/res/drawable/launcheranimatedarrow_00001.png new file mode 100644 index 0000000000000000000000000000000000000000..f3707e07c69917229ebe75c72dc8c3c917b22b18 GIT binary patch literal 749 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUDc{q@F{EP7 z+namyFFQ!EeRwW&%KfF#6PAs0?)It%Hy=47f4-$>I$cbs zS6Q)s;5v|b{Djzn*Dhg*=6N%h6_>E zlefH$(hNyRviZ>#JE`(^d$nKI5$7^SNAdtu%rmW9qCWnO598({esnh-dUj?!NuR zVsT(vkL8!+7vqil%NwuVlBtVlJe69lY6|4#PWthuUvQ4>qdAI^@0q^$v(;N!AF*uS zw9oO@$NjBqzuxoWI>}dfZo-=Xx_7o^cfJvL&U5M)+teV{U2pIIJ+Uii{gP)N^$bpL z;+=I&Mm9&v_{gq^ta%IMCjwpFxpl7nKcE|dOvURxxpH}G9(igt@9XcLQ^Ev`>d%{d YZ?$6e+xQ?9m{b`&UHx3vIVCg!0K5`T9smFU literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00002.png b/res/drawable/launcheranimatedarrow_00002.png new file mode 100644 index 0000000000000000000000000000000000000000..3549389d002ea7277607d84f7a0681ce84e9d471 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gU$=TDzF{EP7 z+Z!AEW;=+mUa*eHGxOC+oPK~^XJw4a|9BPF&^P=E8oZM(zcCZsBVygBl`Ocf4_GrHFTwT$MpMq?kG>+y=IjJm(GLl!AUV;p**bbrf%BYvG73q|NW^G z-{>T17(AKh_qWcVjcGnaB23e|TY_uN!*Y?Msi8*$9*EYtUeR0awbx;Lq9yZ=JA%6z zERJuO8(1&0tML5ceu@1X_sQNa<~e`R!G)D+|Mv!Y!+qy|2_81@@3_};vyOS~R%=1c zHkF&|$7&cF9G_@Pwq275eqOonmG1GV&n)5*p;yg(FAL4~wJWl9ynlP$Yro@<3oq86 z-};%kswssnQZcoF;;n9-_m}tdam50*hjM*BkwOTT-Nqn>t1U5Qq%tO z#>#o|7r%ddmvH~K>b&l)&p3Yxzufh1N{7$CU+PM^*GrdAINy^irzdPOowL4b{c9a1 eOi)!_#~9UO?#z(<&;^+M7(8A5T-G@yGywpu-!d}* literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00003.png b/res/drawable/launcheranimatedarrow_00003.png new file mode 100644 index 0000000000000000000000000000000000000000..891e86c42ba3a661de22db3b7b888f3ac1718169 GIT binary patch literal 746 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUDaX^rF{EP7 z+nZb@Y%!6RIzK^}$|;?{?K^ zR!zui_ISF^mhWM#MaSWmt!p&O8qE(B=bcY$%s+74b5c$1o15>1LK+z&10}-J8x8l* zXPN|p2a*KD1v)pcy5p+BQm_W8xDq9l?xnfUte-WBfgd5d0w z*=*j&7W0fGmV9569;x?vlg@K?cJ}MJ?Htq1(%uIToXc?T8Fe%|wWa!UMX6TJ&j z)(daWXL!76?k7|K$4sj=ZdI%L;Y5vVNe<}$}*AGf{9KTKak&A4o0o4Rw&|E1@OW`6$|_a=K@n7mSISR z^SVFE+2TOv?K{R>DobWO_-ybn{Qv91X&sp0PjwyBihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUDc94*F{EP7 z+gk^-uQnnhYyF=eZ={$XZt4Jm6U$~^V2;_oBAeNUZ64F@cTbBSaDP~Q!bm!6+iaK6 z2aDE~H%vbuGb6ToH*45|-%6+V;+5t-6rC z;OSfD_B*k1d1nLwLq(eS1b?fYA>5H^~{p~8# zaj?K#)c_`O+iP(f2G-Vwne*pR~^%ygugj|9^AS;{XqHy=ZUHJ&24YzF47XH z%h$^({#^HOWNl9t3#M|f{t5(Hk=U!AjX=C_I&>{V8PV}-( zGq=v^B5mKx%xRnd=pWy_CHJby*^?J9UR+|WF?(KmKx1lT-rKit_rC0z`8{%e#O^4) z>G{jG43F3}%RRkhV*A2c$07ZUjql}V#pgY_a_^Z|uewz_{p8;>X_cX&yZ1fs1&OWQ z%38Ob;qj)$T&Dfyjj@xy>tD?R@O%i*;8wqH$AK8jDEeD*WLms z(4O*F|KYmm))PR5j*<5l7^X4)_;d5(t~V>3&OVM!nD>17mfX_!3Ae+7ZN%GWrC*rt zaoI$@bL(FFf1O)bFU$06n`6AKa?4DCBIAet|6i9)1E&U5;!kxQqi&k16G!lY3}8ZK N@O1TaS?83{1ONlWQAq#* literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00005.png b/res/drawable/launcheranimatedarrow_00005.png new file mode 100644 index 0000000000000000000000000000000000000000..121f4d516e6888915dac758c3e9a0ca04de04c9f GIT binary patch literal 664 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUNy^j3F{EP7 z+nam)UKxn6JlH5GDjKxlNM&P*$nSqw#a@15GF0t!aoy@w)BVFM`i*~*wdK9LKci+8 zm?#O{W)K8{i`S-nVR*lFyEKCg;}ixz2EKnw)f@O6xE}<@=`mfd)tSw7`*tq>0;Yry zKV$eBST9VOe)@LU=33QmrrJvV8T(3$OBmvcH3Gf)ALQTW`h2>%KL6?FMMt=ge3_lj zRCFxe^=Uvt=Z5)0K*1u7f|F}!8_jm%W0l)ie_HFNN|D9~_tOgxEDtd2l;L{QvdQyF zKtgwB>&7XcBLgi(ejM0h6MgE}8jX)ke~&pdr*2Pf&$Qih%EvopqZr$cnG=rq-#MoQ z1ez!1lGki?e;#9*ReQ-xmLZ|!-OA$|gT1yMo8cSI8+}$dHX-)9wOlpI!cTeyP2m@jR{m@7Chj``>-fq(3v`Hy2hh m7rys<_3cO(Oz`FRALjOJ25$Fvy^R8bVG7wVRUbD004NLb4y-7D@iRgF#Hx%_{fB#;psCydQ zkg`S&OG%;IB3ekn_Spm80}&a?Ns5hhJ}*4Pzxeux!S`N+bIv*EoO8~(dq&D0z@%Q1 z06YS|D1HK*0e=H$%{VS;WH+l+fxRTz0KNyl2BL|NQ{V{rwHe2!cRjXgDp6o@*VTbN zVC9y>FM!v;pbB?mQ1$={+d!M!%cD4^U1UmEdV#$piMYD+CjVsrPXz20*;18WAkYBT z4Y~CqTdFD)xKmPgDXLK5lajJaQH25@l$2eHDinBGQg$h-P~fPf>{3*rz&}gME=3gz zOn`Gk?!3sBstN^O0zU)!9GSex`2WCSJ$uB}`Zk>*k`~7vRbee$iOq!n^pfNW@B^>~ zgcBcUzzOh6Gmihc>#78zK?_pH$?pNVUJSv-4I&=9%X$OjU!!!^3B(NJCV=1RkntE_r?mybZ&!*Jv~> z6ZmTuDo|CW-|v5;s=FElQ+Gxb-hWzMef{0`_MN;8`tA65OnR) z5m~R*YELa$Q-P_-BJX6^em^;Ryg>d&#doe*^VVxJ6)0)?==P(gWJQ8#G%{c4G|_0Z zR!pBvtk+cfcGi7oDo~^7%z8pq&C|`zg(t9+(#_3tRW(o6t=D8KFj76Xou7Q91e%?RQ01Jt6|t%WadAOh^U5P&yrQukHg{coh553u-$H7W?{Gw z%vFTs`RVg|y=yt=ES}fv-D0gf56!}Gzuj(|-mjP+BCUG;Q1bkM*-IR!Y5Gb;W_j;n zA|h#;rmukUj1iSQKWNqKhvsuygW#J9;M-YCpA9(YoO8}O=bUrSIp>^n&N=6tbIv*E doOAB}`xh>#TU0?N%t`ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUIn&d{F{EP7 z+neY8g&jrMKfIrvxv`0BaB^0d!2^?>w-!3CJY4< z?i0h;M@L5+hHCLYD43g)o7PjganGJVC)4@XGnAK=rTvQhCbImGxqnhxuwvD{9JBJ; zXHucdFGork*efc%4ZOBKboKdHUw`e(ZoKZj7Jqw-pcBT#OfA@h{SwpUt~( z!oTa5>=|oiUp_y=wfXbx+>piRo))h?$0(Kj?ZmA$9?oZSdJToGEe(&=BtLW7f8*)v zgSOW{o@3k=wKo3DpM@U5Ja3K_{@Lr1D_AH~pT6M2uE{g)w*ckVJ>EO7ack6D-G+VX z&da>Mv~{R$`>3w8PH1mp+Wo(|_WRw^;~1~K{#rXbb+z1fqr)?tKo literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00008.png b/res/drawable/launcheranimatedarrow_00008.png new file mode 100644 index 0000000000000000000000000000000000000000..bd6f409819966731d7ed6b7c440d51fa2e841773 GIT binary patch literal 710 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gU$-~pdF{EP7 z+Z*SzuQ*5?`}kf(PfYuem)W-|M;5oool>&>^55Pg&$eRYj3-O-76jZ>x^lMS#g(L$ zH)n2n99CgBz4h+*yXE($|4CqT!36VUHos=Qv;3tM^Bd;scdO+O?9$@ey*u=-envy? z?Q?6lUY(%QePl*$!@*mnMm*ZdKEczT{&W_*Tyod=fo?#d?SUhIO&GElFxrK?e*COh zDz}+`J>#`f+tZugOfb=PFVdOqd;9y}mqk1Ge2QgoxLnGxzTx}3;xnut7dl7< zXAYGwY&PuHIn7u5c=kH)2Iai@<#yj1l&8N|4U=EYopHPDo!)nY%&2X-*57;V7&bkS zx^BsC*J$`#LUzH{ZArIg-`Wu)pUA(7b0%aCTY7W~)d%6gdzziYF2@86kG+jIZW;SIB*EEK+PxC}UMovquJePKvZTj7uTMd7osD7BZfo<*SonJWn zZXUlj-Q)iE<(*saZFfj|yXyV#;QJNZ=i4&dJv4WDopj#m+o6rWDumiOKm6uc&2W!V z|DRA<@z%27zHLcgJ}&9o%rI-+Y{vYCIkz3-nC>^M4-NFGjQjU6{)?g#CiuTTzHWoo V`iJM#Yk_Hy!PC{xWt~$(696&gMj-$I literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00009.png b/res/drawable/launcheranimatedarrow_00009.png new file mode 100644 index 0000000000000000000000000000000000000000..c7cb60daf3a6ea6e990a25a2ba4463fa28322192 GIT binary patch literal 968 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gU*~`<#F{EP7 z+neY8uQ-UXe|SH8;SR4D&L7H;qPR}JVia~2^8fd9p3Mq&k14(hIpLdEw4>ORX55MXro2%6l_K z>KsG$;fEjI-nwr8Z&#U*NL6U4DIK3)=f)g6}&kk+ud}- z{rCD&+47r%!p}>X1)W)c$>eVL%D|~M=cmhDRAyMazgO|OUFO>O605oW3$OF%Jvevn zobKAM!eLq;CC=3peJV;#J=$_@<)L@)^4yG#Uw_!)UzC@#wszmHUBB#>v~fw=Nbo!h znUqsu#?QbJWv|nfq`1USv};jB!!`bgKPPMm>hzk_doaO3JV<>-AYx z%CFqGW~=%Pm%Z(-XOwbq=$tx@bL)UJ1KWYPY0K`tj(*H1thUPToKa<+%xJjvo=l5aAID6eac!FVJV3chv>v@LcSRfOsuDH z-@g0DS6J%D&Sg2OK$U+XD$5y<$k^^Qie9Z|@sw|20=eN#}oN*D-`whVj2MeQOBJg$$mqelF{r5}E+bx2}Bv literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00010.png b/res/drawable/launcheranimatedarrow_00010.png new file mode 100644 index 0000000000000000000000000000000000000000..1bf30dcc301decac5ad9f26f17bf7b722a3890b4 GIT binary patch literal 990 zcmV<410np0P)bVG7wVRUbD004NLb4y-7D@iR^n&N=sgBXyi)Fc_@OX0r^~)exSGNMJU*CrRFPRExd6z1JcV>*!ULs)ozU%iWns z%5=I(r?aA}QDS@xyzX>5dB5MkTei;~@*K;2^S|=lKgu&nogfe-T917c7%WjEB|_))e4iFxU~1XO>!$BvaK| z{=EK9RfQzUEVUx?Y%mz?SZYlHPD9wX)By;ro!#6lx=NmOc5|~Xux72d-jgZ70AE__ zN|M~(ns0QT@|@e zaG4*n#(xCc5TIrC)lplEHUzlRP;D*R5a2RXn*Vgd^D%7*@VAC)Yte=Pe{HC?7HtSH z2P|8&Nm<*dHUxMFEc${<%bLa~%8dtD?q@H6sxPRtJocgud*N0L6X4GgRuA}&`Fr9K z@Dng>%V?`L0e+AdMFMOCJN$DVcm-T%Y5K`ti#q3=bIv*Eock>N0TW|RmODcWwg3PC M07*qoM6N<$f{nY+g8%>k literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00011.png b/res/drawable/launcheranimatedarrow_00011.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb598806b5626e1e1d0211606272424e994dec4 GIT binary patch literal 671 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUNzv2AF{EP7 z+nam)9vO(RK1fzxwRQrJ>PhY;i)VaazszI0c!y@Es@AH|qU9a#w~|b|>W#j=nYaGg zt#Ee$=mmTa!DsbYDwfdqtli!X>UMQf=v#8zyIsug95woh*}Z|GRs# zPy6A8Ka*?^+C)FUQ?UDP-ST&l!SeR0Ey z!Qfa)^la7!*Oi+MmwDgZysd5#^OkG#{upu=TbNwC75rx3)~~{{3p%!R{N@bUXSO=% zuM7K*jJ-csJXv!$`RtTiFBt6QdH3xu>o=X8mCT(fTHoRtr<(OW!g#6KKMwuVA&a0g zQ-hQlIyFS&XNRr?+K@BrS!d<8gRP%)zE|k$UG={-zoR(sg3|2$|DsZg&hctiU%SSy zFYFbk_UC2&-g8PyN{fOv*&cghaPK`s0n-I03+5kt(yAC|F!V6Yd;2wN!S4O5)ed~D zvuAq2Q1h38CxXGbdhfem4*Nny8Gn7ax1sL!wiyh2t_vi60Cy58-88=JR5c|97XW0uTDboEtQS zcfGm1H^7u-hS9>)c6Yf$og0*umS^tU2Gm@WRBOwfTALeql=+J|GxMRo$1PuLE2htL s3i+tV^T9~5dw#`SR~HcY^4E?b;go@!pGK!GFmW+>y85}Sb4q9e0C$ZjIRF3v literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00012.png b/res/drawable/launcheranimatedarrow_00012.png new file mode 100644 index 0000000000000000000000000000000000000000..58070de06d1d0d789afb15f8bab7aaf824a2d9c8 GIT binary patch literal 744 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUDa+HvF{EP7 z+nXEnFFVK_{rKKeq*F;zYt9CR+h?{Utz`NCpCw34%J6YaHxw?GcMNxrUc*&ins*W4yJpiF?a6xu2I)>?eC>uC?X5 zo1gWWMf}B_)f~S$4eYE}L;!g=Qg5DHerm!^!)tFQ^Xe58XBS)tv8VF#o{IXR`Q}b( z&l`#CZztR4ABx@HGf_7AY~0NY3k;XB#W-B~`DQ`VS>c$3$Z6qg!ph!1$Vxr_BVHzZ zUYz*EZMotzi>>AV%`83*WY&J0dCR71hQQ~V0pDlW1oxE+wGGyH{{K#KEVCJvVPye_YM39ey`G~yK^Q*loVa|KNaQF1~``>P)FYby{_%P#Z{=I!#^Lw>gnY0f*JHeq{7oaI((LF`h zg^N}0U+v_;P0bz)57?D6U3{+;r13#5s7ydpN>%MbLIf1B#mIPJi6M?2|IF4j7> zbIe80fy^&Vs}~gl0PQ=pz;4J?bnOfvE35s+ta&*>&^#$PNTf%#SY=3u3KihfWf7Y!q0sz Vf=xHKL;_POgQu&X%Q~loCIE|?PZ0nB literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00013.png b/res/drawable/launcheranimatedarrow_00013.png new file mode 100644 index 0000000000000000000000000000000000000000..810d0a229cc9bc6c9c56c8565da8bc2ec67a766f GIT binary patch literal 750 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUsle04F{EP7 z+gk_yuQdmyYGsz9-ZcSeEI_=nuRXV2^ z2R!%6oOh4aZ~5iIdCzb6xILA99r^6@uH4&y@7}%Jz3;hC=DawN?8j@@uH8O2dBBuR0>}Ic1+?gU$Kv;<923jMI$Ie|~HC+$UG=J=5!5apkvDEEfkx0xf=D z89e*=Zino*Ec^Qz9&d6yI_voE1A3>$?rAA*c3yK+{dMevulw2f=gKCXjf=d$Am-!! zL)YdVIl;FvZT_17V()fE_Pi0u{&wmYTd$#Lg6*Pfsm)!c{pF3Rk#dIfuY0pU|6JAa z`%%Q_FTa#8ZF^gn_V&>9`n0!efIa{_#@46fr@_PU|F0kG2>x8n`kRMmX_i!j=mX6S zuj*>J{xFM2E!SOdceM&BJubGv`@}{8p^u1J`H#>4dE^@QPF=fl` zG@ah7HI*qjrP*WQ0lWMcP5bXghjFp4b2-H}(W%j1ITa-S*yE-`kj4k^B{>3`A`!)< zop0>d`R;UL*O{LR!n%r8jqmEdG5laC;dZeqf8)sY{g9`P&yFzDvuq!v zDvq7Ksq;bhhlyraKELhl+(}vj3$mn`Z5WFl{Ib?oci@)s?>hAS?)nZ0U@79fJJ~d& UPD8N;m{u7)UHx3vIVCg!0Jx=DF#rGn literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00014.png b/res/drawable/launcheranimatedarrow_00014.png new file mode 100644 index 0000000000000000000000000000000000000000..3f9e5186141b843095b2053efa862bd8275f3a9e GIT binary patch literal 747 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUDc94*F{EP7 z+nZvM&8#PzS+s(r(ZXHF@N&HsJR zd~4+%?k(5+KK>R>h%s9c0aS4#{pO|hUMJp2Wb>V@60i8Y{$OqT@rdGpYvvl)|HLO| zpHEH!ay_p77k&5JwFji^)GxMPL(v4=Mb}cByG;Ab8&f0aT`K*0>fpN7$8U%qpOt<= zej?D>M%mNr{upPA=iZj%1v%!0f#S2oL%IK77f$ntIT-gaVAV3l3YHwk{=3unGsZV| zX9l{=dh>mf)`qNEybn}A@I?H7c{}`o%nuvgt_RkV@tz_t=IvU3K>EQ$+3EG{_n6~@ zDx`ufeNHqwztiI{KfCa>i}>CHt1{INWGCdd-jMomD|K;KoWcjD8(&W3?#c<#6!~Ej zWY!_Vb*F0zuTm?Myvr%JiB65qW}g?!`RFLOGM!gSZT47rpmIr$fTlihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUDaq5tF{EP7 z+ZzY7g&k#%ete(Pvp`gFLub>D-R(^QFC14q_+PIjv0=jG2Z`lJ7oBu^xsdDHn~E1M zdn6{^^i03--E!IQw0aw(yJs6ZFu|W}pB1gMmd%XFUTt@J*-S%shP_+OBk!;8F)WRo z7QXP^)Jrza+;h%9e{6H!U$Xao0Hgz3g+x?B@B55Hnxn zk9+g8K39lmtX=!zw|GK~*@_4t??(E~bJ40P-vqMxPF9KUs5IU2{*B@_nJHg+Ps#q* z&3WtF1L98ldjC-E{;r9(M`kr|+UJ<;!yD1RqV(p1q_dA>6M$}CbN%eSgI{-99(n&L z;xkWGM@j7V6Kn4Nd_6yM_p4obhEL|LJiGXC%Zzr;`d{(=n_V7pZEJO2dHWkj1*cEr z@pq5&4{-em5q14A{e$8p%?)|Cc`8_Cm>=(tDdVnTu?um%c>j9NZI!?U+V6QkBppAI z{vei1)y>{##ci<%Eq6~Jj;(&QDbQ74pyJfsZFjG4Jnc5ssZq49I7Lr7l#BJ8Qfjlu z!UO6PH#sf|crd}d@kPF%rpOQ5AhQk;t~U_LzxPzyC`2F#5J*z`$1Lz z4by^$K%?usaVGlDFq(c) zZFO$xUBY#zpq(Z8LF4Zi>IKoFu3KihKta>nld^}y80;OXk;vd$@?2>``H BSX}@B literal 0 HcmV?d00001 diff --git a/res/drawable/launcheranimatedarrow_00016.png b/res/drawable/launcheranimatedarrow_00016.png new file mode 100644 index 0000000000000000000000000000000000000000..5b0b28649f684d27610f6bba3c94ef39222427bb GIT binary patch literal 668 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#Xy=7gc&WAzBm9GDpes7 zCC){ui6!|(3=9=>ihWM{dxaMGXnX1g75I94`|4@3F*sJSoqK+^IE{gUNzT*7F{EP7 z+Z%iP78{7LK1g<7xvlHT5}rK_v0at_v$(9{7?$%)3tAd=a4u8-mZMoM^%B)(=fj`h z3U`-y#4R9*0WR)$dm42js#<7b*~MRLrv74O=sw}Ce|pIjsoBdapWRx$tZowXo8SL3 zdR`V-n8dG9pOOB0j&$Dk+wZT~X1`L`Jh1P5|Mb&Oa|2)PdHOvnrOHO`eRXTZB6-

~Rd?OpzO8N%v&clT!u1SKvo~v9 z19Y zEDXwGQ4n1E2x@}wWgEluW;+Vzy_Ng^DCnKPX=%RXO=PsajM@|# zwLdTGWzz+xPJPSq+~D4Oh61JwOcu;P_9X3P@L_OcIRC9m_rl%#p~4R;_RBMtFzl=Q z$t-r@Lfrb@bv@fwDLKgBlfJe*ubcBgHLs$WyxNcC+lkc`ANIek$qhWpeB{?T?)fU~ zO>e5-UYNzP!S#vC&Ff;J&JCKEU%r|AHo%l+hSA(}b~pKeLiSO-jLUqMZ}#8jd{rRf zREgE^6zdRP#=ed@D!*?n%HsIY`L#;y)UR8My4YW^GczCRd))HXZjZOW;;J3o%ra^z jABB%cxlK~|Oj!(`u6{1-oD!M + + + + \ No newline at end of file diff --git a/res/drawable/listitem_text.xml b/res/drawable/listitem_text.xml new file mode 100644 index 000000000..9637fd308 --- /dev/null +++ b/res/drawable/listitem_text.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/res/drawable/transition_arrow.xml b/res/drawable/transition_arrow.xml new file mode 100644 index 000000000..540db93d8 --- /dev/null +++ b/res/drawable/transition_arrow.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/drawable/transition_arrow_reverse.xml b/res/drawable/transition_arrow_reverse.xml new file mode 100644 index 000000000..855f08fb4 --- /dev/null +++ b/res/drawable/transition_arrow_reverse.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml index 1f02dce3c..5698c86a9 100644 --- a/res/layout/overview_panel.xml +++ b/res/layout/overview_panel.xml @@ -14,53 +14,130 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - - + + - - \ No newline at end of file + android:layout_gravity="center_horizontal" + android:background="@color/slideup_panel_bg_color" + android:paddingTop="@dimen/overview_panel_top_padding" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/settings_pane_list_header.xml b/res/layout/settings_pane_list_header.xml new file mode 100644 index 000000000..2429b9b81 --- /dev/null +++ b/res/layout/settings_pane_list_header.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/res/layout/settings_pane_list_item.xml b/res/layout/settings_pane_list_item.xml new file mode 100644 index 000000000..58b2de862 --- /dev/null +++ b/res/layout/settings_pane_list_item.xml @@ -0,0 +1,38 @@ + + + + + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index d7f9ef4fa..787e5ca23 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -134,4 +134,14 @@ + + + + + + + + + + diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index 4ec60147f..e31fed119 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -18,9 +18,45 @@ Trebuchet + HOME SCREEN SETTINGS + DRAWER SETTINGS + APP SETTINGS + + + ON + OFF + DISABLED + Google Play + + Scroll wallpaper + + + Grid size + Comfortable + Cozy + Condensed + Custom (%1$d \u00d7 %2$d) + Select custom size + + + Search bar + + + Larger icons + + + Icon labels + Show + Hide + + + Protected apps + + + A search activity could not be found! diff --git a/res/values/colors.xml b/res/values/colors.xml index b70e1f895..0cad1cefc 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -36,6 +36,7 @@ #FFF #FFF5F5F5 #76000000 + #FF374248 #FFFFFFFF @@ -53,6 +54,9 @@ #C4C4C4 #263238 + + #FF6cd2ea + @android:color/white @android:color/darker_gray #CC14191E diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 799ea9803..8c64ef0c5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -145,7 +145,7 @@ 4dp 2dp - 20dp + 8dp 2dp @@ -158,4 +158,13 @@ 300 100 + + 175dp + 20dp + 50dp + 30dp + 16dp + 60dp + 50dp + diff --git a/res/values/preferences_defaults.xml b/res/values/preferences_defaults.xml new file mode 100644 index 000000000..9eb5ca58f --- /dev/null +++ b/res/values/preferences_defaults.xml @@ -0,0 +1,9 @@ + + + true + true + false + false + false + false + diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 205c113a7..c15f07de9 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -43,6 +43,7 @@ import android.widget.TextView; import com.android.launcher3.IconCache.IconLoadRequest; import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.settings.SettingsProvider; /** * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan @@ -86,7 +87,7 @@ public class BubbleTextView extends TextView private final boolean mDeferShadowGenerationOnTouch; private final boolean mCustomShadowsEnabled; private final boolean mLayoutHorizontal; - private int mIconSize; + private final int mIconSize; private int mTextColor; private boolean mStayPressed; @@ -131,6 +132,13 @@ public class BubbleTextView extends TextView setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx); defaultIconSize = grid.allAppsIconSizePx; } + boolean useCompactDrawer = SettingsProvider.getBoolean(context, + SettingsProvider.SETTINGS_UI_DRAWER_COMPACT, + R.bool.preferences_interface_drawer_compact_default); + if (!useCompactDrawer) { + defaultIconSize = getResources() + .getDimensionPixelSize(R.dimen.all_apps_icon_size_ragged); + } mIconSize = a.getDimensionPixelSize(R.styleable.BubbleTextView_iconSizeOverride, defaultIconSize); @@ -435,10 +443,6 @@ public class BubbleTextView extends TextView if (mBackground != null) mBackground.setCallback(null); } - public void setIconSize(int iconSize) { - mIconSize = iconSize; - } - @Override public void setTextColor(int color) { mTextColor = color; diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index a99d791bd..daf26411d 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -615,9 +615,8 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { final LayoutParams lp = params; // Hotseat icons - remove text - if (child instanceof BubbleTextView) { - BubbleTextView bubbleChild = (BubbleTextView) child; - bubbleChild.setTextVisibility(!mIsHotseat); + if (mIsHotseat && child instanceof BubbleTextView) { + ((BubbleTextView) child).setTextVisibility(false); } child.setScaleX(getChildrenScale()); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index f77b34862..84b6835f8 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -468,7 +468,7 @@ public class DeviceProfile { } // Layout the Overview Mode - ViewGroup overviewMode = launcher.getOverviewPanel(); + /*ViewGroup overviewMode = launcher.getOverviewPanel(); if (overviewMode != null) { int overviewButtonBarHeight = getOverviewModeButtonBarHeight(); lp = (FrameLayout.LayoutParams) overviewMode.getLayoutParams(); @@ -505,7 +505,7 @@ public class DeviceProfile { } } } - } + }*/ } private int getCurrentWidth() { diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index 994d7d30d..1e0827e54 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -63,6 +63,7 @@ import com.android.launcher3.FolderInfo.FolderListener; import com.android.launcher3.UninstallDropTarget.UninstallSource; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; +import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.UiThreadCircularReveal; @@ -229,6 +230,13 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mFolderName.setInputType(mFolderName.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); + boolean hideLabels = SettingsProvider.getBoolean(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default); + if (hideLabels) { + mFolderName.setVisibility(View.GONE); + } + mFooter = findViewById(R.id.folder_footer); // We find out how tall footer wants to be (it is set to wrap_content), so that @@ -326,7 +334,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // Convert to a string here to ensure that no other state associated with the text field // gets saved. String newTitle = mFolderName.getText().toString(); - mInfo.setTitle(newTitle); + if (!SettingsProvider.getBoolean(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default)) { + mInfo.setTitle(newTitle); + } LauncherModel.updateItemInDatabase(mLauncher, mInfo); if (commit) { diff --git a/src/com/android/launcher3/FolderPagedView.java b/src/com/android/launcher3/FolderPagedView.java index cc9c5738a..a7940d552 100644 --- a/src/com/android/launcher3/FolderPagedView.java +++ b/src/com/android/launcher3/FolderPagedView.java @@ -30,6 +30,7 @@ import android.view.animation.OvershootInterpolator; import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener; import com.android.launcher3.PageIndicator.PageMarkerResources; import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -214,6 +215,11 @@ public class FolderPagedView extends PagedView { textView.setOnClickListener(mFolder); textView.setOnLongClickListener(mFolder); textView.setOnFocusChangeListener(mFocusIndicatorView); + if (SettingsProvider.getBoolean(mFolder.mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default)) { + textView.setTextVisibility(false); + } textView.setOnKeyListener(mKeyListener); textView.setLayoutParams(new CellLayout.LayoutParams( diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index ae204c40c..6d4d95292 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -24,6 +24,7 @@ import android.util.DisplayMetrics; import android.view.Display; import android.view.WindowManager; +import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -132,7 +133,8 @@ public class InvariantDeviceProfile { minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm); ArrayList closestProfiles = - findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles()); + findClosestDeviceProfiles(minWidthDps, minHeightDps, + getPredefinedDeviceProfiles(context)); InvariantDeviceProfile interpolatedDeviceProfileOut = invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles); @@ -169,35 +171,49 @@ public class InvariantDeviceProfile { smallSide, largeSide, false /* isLandscape */); } - ArrayList getPredefinedDeviceProfiles() { + ArrayList getPredefinedDeviceProfiles(Context context) { + boolean useLargeIcons = SettingsProvider.getBoolean(context, + SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE, + R.bool.preferences_interface_general_icons_large_default); ArrayList predefinedDeviceProfiles = new ArrayList<>(); // width, height, #rows, #columns, #folder rows, #folder columns, // iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId. predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby", - 255, 300, 2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4)); + 255, 300, 2, 3, 2, 3, 3, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 3, + (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby", - 255, 400, 3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_4x4)); + 255, 400, 3, 3, 3, 3, 3, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 3, + (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby", - 275, 420, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); + 275, 420, 3, 4, 3, 4, 4, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 5, + (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby", - 255, 450, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); + 255, 450, 3, 4, 3, 4, 4, (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), 13, 5, + (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S", - 296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4)); + 296, 491.33f, 4, 4, 4, 4, 4, (useLargeIcons? DEFAULT_ICON_SIZE_DP : 48), 13, 5, + (useLargeIcons? DEFAULT_ICON_SIZE_DP: 48), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4", - 335, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4)); + 335, 567, 4, 4, 4, 4, 4, (useLargeIcons ? 70 : DEFAULT_ICON_SIZE_DP), 13, 5, + (useLargeIcons? 68 : 56), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5", - 359, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4)); + 359, 567, 4, 4, 4, 4, 4, (useLargeIcons ? 70 : DEFAULT_ICON_SIZE_DP), 13, 5, + (useLargeIcons? 68 : 56), R.xml.default_workspace_4x4)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone", - 406, 694, 5, 5, 4, 4, 4, 64, 14.4f, 5, 56, R.xml.default_workspace_5x5)); + 406, 694, 5, 5, 4, 4, 4, (useLargeIcons ? 76 : 64), 14.4f, 5, + (useLargeIcons ? 68 : 56), R.xml.default_workspace_5x5)); // The tablet profile is odd in that the landscape orientation // also includes the nav bar on the side predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7", - 575, 904, 5, 6, 4, 5, 4, 72, 14.4f, 7, 60, R.xml.default_workspace_5x6)); + 575, 904, 5, 6, 4, 5, 4, (useLargeIcons ? 88 : 72), 14.4f, 7, + (useLargeIcons ? 72 : 60), R.xml.default_workspace_5x6)); // Larger tablet profiles always have system bars on the top & bottom predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10", - 727, 1207, 5, 6, 4, 5, 4, 76, 14.4f, 7, 64, R.xml.default_workspace_5x6)); + 727, 1207, 5, 6, 4, 5, 4, (useLargeIcons ? 92 : 76), 14.4f, 7, + (useLargeIcons ? 76 : 64), R.xml.default_workspace_5x6)); predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet", - 1527, 2527, 7, 7, 6, 6, 4, 100, 20, 7, 72, R.xml.default_workspace_4x4)); + 1527, 2527, 7, 7, 6, 6, 4, (useLargeIcons ? 124 : 100), 20, 7, + (useLargeIcons ? 84 : 72), R.xml.default_workspace_4x4)); return predefinedDeviceProfiles; } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1976ca982..759ca041b 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -54,6 +54,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.Rect; +import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -105,6 +106,7 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; @@ -261,6 +263,7 @@ public class Launcher extends Activity @Thunk Hotseat mHotseat; private ViewGroup mOverviewPanel; + OverviewSettingsPanel mOverviewSettingsPanel; private View mAllAppsButton; private View mWidgetsButton; @@ -355,6 +358,9 @@ public class Launcher extends Activity // the press state and keep this reference to reset the press state when we return to launcher. private BubbleTextView mWaitingForResume; + // Preferences + private boolean mHideIconLabels; + protected static HashMap sCustomAppWidgets = new HashMap(); @@ -430,17 +436,11 @@ public class Launcher extends Activity LauncherAppState.setApplicationContext(getApplicationContext()); LauncherAppState app = LauncherAppState.getInstance(); - // Load configuration-specific DeviceProfile - mDeviceProfile = getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE ? - app.getInvariantDeviceProfile().landscapeProfile - : app.getInvariantDeviceProfile().portraitProfile; + initializeDeviceProfile(app); mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); mIsSafeModeEnabled = getPackageManager().isSafeMode(); - mModel = app.setLauncher(this); - mIconCache = app.getIconCache(); mDragController = new DragController(this); mInflater = getLayoutInflater(); @@ -1391,7 +1391,11 @@ public class Launcher extends Activity mHotseat.setOnLongClickListener(this); } + // Setup the overview panel mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel); + mOverviewSettingsPanel = new OverviewSettingsPanel(this); + mOverviewSettingsPanel.initializeAdapter(); + mWidgetsButton = findViewById(R.id.widget_button); mWidgetsButton.setOnClickListener(new OnClickListener() { @Override @@ -1429,6 +1433,36 @@ public class Launcher extends Activity settingsButton.setVisibility(View.GONE); } + View defaultScreenButton = findViewById(R.id.default_screen_button); + defaultScreenButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View arg0) { + if (!mWorkspace.isSwitchingState()) { + onClickDefaultScreenButton(arg0); + } + } + }); + defaultScreenButton.setOnTouchListener(getHapticFeedbackTouchListener()); + + final VerticalSlidingPanel verticalSlidingPanel = ((VerticalSlidingPanel) mOverviewPanel); + verticalSlidingPanel.setPanelSlideListener(new SettingsPanelSlideListener()); + verticalSlidingPanel.setEnableDragViewTouchEvents(true); + + View settingsPaneHeader = mOverviewPanel.findViewById(R.id.settings_pane_header); + if (settingsPaneHeader != null) { + verticalSlidingPanel.setDragView(settingsPaneHeader); + settingsPaneHeader.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (verticalSlidingPanel.isExpanded()) { + verticalSlidingPanel.collapsePane(); + } else { + verticalSlidingPanel.expandPane(); + } + } + }); + } + mOverviewPanel.setAlpha(0f); // Setup the workspace @@ -1517,6 +1551,7 @@ public class Launcher extends Activity BubbleTextView favorite = (BubbleTextView) mInflater.inflate(R.layout.app_icon, parent, false); favorite.applyFromShortcutInfo(info, mIconCache); + favorite.setTextVisibility(!mHideIconLabels); favorite.setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx); favorite.setOnClickListener(this); favorite.setOnFocusChangeListener(mFocusHandler); @@ -1660,6 +1695,38 @@ public class Launcher extends Activity } }; + public void initializeDeviceProfile(LauncherAppState app) { + // Load configuration-specific DeviceProfile + mDeviceProfile = getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE ? + app.getInvariantDeviceProfile().landscapeProfile + : app.getInvariantDeviceProfile().portraitProfile; + + mModel = app.setLauncher(this); + mIconCache = app.getIconCache(); + + mHideIconLabels = SettingsProvider.getBoolean(this, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default); + } + + public void reloadLauncher() + { + // Re-initialize device profile + LauncherAppState app = LauncherAppState.getInstance(); + app.initInvariantDeviceProfile(); + initializeDeviceProfile(app); + + mDeviceProfile.layout(this); + + // Reload + mModel.resetLoadedState(true, true); + mModel.startLoader(mWorkspace.getRestorePage(), LauncherModel.LOADER_FLAG_NONE); + mWorkspace.updateCustomContentVisibility(); + + mAppsView.reset(); + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); @@ -2405,6 +2472,9 @@ public class Launcher extends Activity // Create the view FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache); + if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + newFolder.setTextVisible(!mHideIconLabels); + } mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1, isWorkspaceLocked()); // Force measure the new folder icon @@ -2802,6 +2872,11 @@ public class Launcher extends Activity } } + protected void onClickDefaultScreenButton(View v) { + if (LOGD) Log.d(TAG, "onClickDefaultScreenButton"); + // TODO + } + public View.OnTouchListener getHapticFeedbackTouchListener() { if (mHapticFeedbackTouchListener == null) { mHapticFeedbackTouchListener = new View.OnTouchListener() { @@ -3855,6 +3930,7 @@ public class Launcher extends Activity view = FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (FolderInfo) item, mIconCache); + ((FolderIcon) view).setTextVisible(!mHideIconLabels); break; default: throw new RuntimeException("Invalid Item Type"); @@ -4781,6 +4857,39 @@ public class Launcher extends Activity }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } } + + class SettingsPanelSlideListener extends VerticalSlidingPanel.SimplePanelSlideListener { + ImageView mAnimatedArrow; + + public SettingsPanelSlideListener() { + super(); + mAnimatedArrow = (ImageView) mOverviewPanel.findViewById(R.id.settings_drag_arrow); + } + + @Override + public void onPanelCollapsed(View panel) { + mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow_reverse); + + AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground(); + frameAnimation.start(); + + /*if (mLauncher.updateGridIfNeeded()) { + Workspace workspace = mLauncher.getWorkspace(); + if (workspace.isInOverviewMode()) { + workspace.setChildrenOutlineAlpha(1.0f); + mLauncher.mSearchDropTargetBar.hideSearchBar(false); + } + }*/ + } + + @Override + public void onPanelExpanded(View panel) { + mAnimatedArrow.setBackgroundResource(R.drawable.transition_arrow); + + AnimationDrawable frameAnimation = (AnimationDrawable) mAnimatedArrow.getBackground(); + frameAnimation.start(); + } + } } interface DebugIntents { diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index d87ad67e5..d515f05e5 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -174,6 +174,10 @@ public class LauncherAppState { return mInvariantDeviceProfile; } + public void initInvariantDeviceProfile() { + mInvariantDeviceProfile = new InvariantDeviceProfile(sContext); + } + public static boolean isDogfoodBuild() { return getInstance().mBuildInfo.isDogfoodBuild(); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index b5922c6a3..e3170e93b 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -94,6 +94,7 @@ public class LauncherModel extends BroadcastReceiver public static final int LOADER_FLAG_NONE = 0; public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0; public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1; + public static final int LOADER_FLAG_RESIZE_GRID = 1 << 2; private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons private static final long INVALID_SCREEN_ID = -1L; diff --git a/src/com/android/launcher3/OverviewPanel.java b/src/com/android/launcher3/OverviewPanel.java new file mode 100644 index 000000000..2fdca6330 --- /dev/null +++ b/src/com/android/launcher3/OverviewPanel.java @@ -0,0 +1,32 @@ +package com.android.launcher3; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class OverviewPanel extends VerticalSlidingPanel implements Insettable { + public OverviewPanel(Context context) { + super(context); + } + + public OverviewPanel(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public OverviewPanel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setInsets(Rect insets) { + LinearLayout layout = (LinearLayout) + findViewById(R.id.settings_container); + FrameLayout.LayoutParams lp = + (FrameLayout.LayoutParams) layout.getLayoutParams(); + lp.bottomMargin = insets.bottom; + layout.setLayoutParams(lp); + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/OverviewSettingsPanel.java b/src/com/android/launcher3/OverviewSettingsPanel.java new file mode 100644 index 000000000..b4ba06a4d --- /dev/null +++ b/src/com/android/launcher3/OverviewSettingsPanel.java @@ -0,0 +1,74 @@ +package com.android.launcher3; + +import android.content.res.Resources; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.widget.ListView; +import com.android.launcher3.list.PinnedHeaderListView; +import com.android.launcher3.list.SettingsPinnedHeaderAdapter; + +public class OverviewSettingsPanel { + public static final int HOME_SETTINGS_POSITION = 0; + public static final int DRAWER_SETTINGS_POSITION = 1; + public static final int APP_SETTINGS_POSITION = 2; + + private Launcher mLauncher; + private SettingsPinnedHeaderAdapter mSettingsAdapter; + private PinnedHeaderListView mListView; + + OverviewSettingsPanel(Launcher launcher) { + mLauncher = launcher; + } + + // One time initialization of the SettingsPinnedHeaderAdapter + public void initializeAdapter() { + // Settings pane Listview + mListView = (PinnedHeaderListView) mLauncher + .findViewById(R.id.settings_home_screen_listview); + mListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER); + Resources res = mLauncher.getResources(); + String[] headers = new String[] { + res.getString(R.string.home_screen_settings), + res.getString(R.string.drawer_settings), + res.getString(R.string.app_settings)}; + + String[] values = new String[]{ + res.getString(R.string.home_screen_search_text), + res.getString(R.string.icon_labels), + res.getString(R.string.scrolling_wallpaper), + res.getString(R.string.grid_size_text)}; + + String[] valuesDrawer = new String[] { + res.getString(R.string.icon_labels)}; + + String[] valuesApp = new String[] { + res.getString(R.string.larger_icons_text), + res.getString(R.string.protected_app_settings)}; + + mSettingsAdapter = new SettingsPinnedHeaderAdapter(mLauncher); + mSettingsAdapter.setHeaders(headers); + mSettingsAdapter.addPartition(false, true); + mSettingsAdapter.addPartition(false, true); + mSettingsAdapter.addPartition(false, true); + mSettingsAdapter.mPinnedHeaderCount = headers.length; + + mSettingsAdapter.changeCursor(HOME_SETTINGS_POSITION, createCursor(headers[0], values)); + mSettingsAdapter.changeCursor(DRAWER_SETTINGS_POSITION, createCursor(headers[1], + valuesDrawer)); + mSettingsAdapter.changeCursor(APP_SETTINGS_POSITION, createCursor(headers[2], valuesApp)); + mListView.setAdapter(mSettingsAdapter); + } + + private Cursor createCursor(String header, String[] values) { + MatrixCursor cursor = new MatrixCursor(new String[]{"_id", header}); + int count = values.length; + for (int i = 0; i < count; i++) { + cursor.addRow(new Object[]{i, values[i]}); + } + return cursor; + } + + public void notifyDataSetInvalidated() { + mSettingsAdapter.notifyDataSetInvalidated(); + } +} diff --git a/src/com/android/launcher3/VerticalSlidingPanel.java b/src/com/android/launcher3/VerticalSlidingPanel.java new file mode 100644 index 000000000..0ebbebc72 --- /dev/null +++ b/src/com/android/launcher3/VerticalSlidingPanel.java @@ -0,0 +1,1317 @@ +package com.android.launcher3; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ViewDragHelper; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; + +public class VerticalSlidingPanel extends ViewGroup { + private static final String TAG = VerticalSlidingPanel.class.getSimpleName(); + + /** + * Default peeking out panel height + */ + private static final int DEFAULT_PANEL_HEIGHT = 68; // dp; + + /** + * Default height of the shadow above the peeking out panel + */ + private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp; + + /** + * If no fade color is given by default it will fade to 80% gray. + */ + private static final int DEFAULT_FADE_COLOR = 0x99000000; + + /** + * Default Minimum velocity that will be detected as a fling + */ + private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second + /** + * Default is set to false because that is how it was written + */ + private static final boolean DEFAULT_OVERLAY_FLAG = false; + /** + * Default attributes for layout + */ + private static final int[] DEFAULT_ATTRS = new int[] { + android.R.attr.gravity + }; + + /** + * Minimum velocity that will be detected as a fling + */ + private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY; + + /** + * The fade color used for the panel covered by the slider. 0 = no fading. + */ + private int mCoveredFadeColor = DEFAULT_FADE_COLOR; + + /** + * Default parallax length of the main view + */ + private static final int DEFAULT_PARALLAX_OFFSET = 0; + + /** + * The paint used to dim the main layout when sliding + */ + private final Paint mCoveredFadePaint = new Paint(); + + /** + * Drawable used to draw the shadow between panes. + */ + private final Drawable mShadowDrawable; + + /** + * The size of the overhang in pixels. + */ + private int mPanelHeight = -1; + + /** + * The size of the shadow in pixels. + */ + private int mShadowHeight = -1; + + /** + * Parallax offset + */ + private int mParallaxOffset = -1; + + /** + * True if the collapsed panel should be dragged up. + */ + private boolean mIsSlidingUp; + + /** + * True if a panel can slide with the current measurements + */ + private boolean mCanSlide; + + /** + * Panel overlays the windows instead of putting it underneath it. + */ + private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG; + + /** + * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be + * used for dragging. + */ + private View mDragView; + + /** + * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be + * used for dragging. + */ + private int mDragViewResId = -1; + + /** + * The child view that can slide, if any. + */ + private View mSlideableView; + + /** + * The main view + */ + private View mMainView; + + /** + * Current state of the slideable view. + */ + private enum SlideState { + EXPANDED, + COLLAPSED, + ANCHORED + } + private SlideState mSlideState = SlideState.COLLAPSED; + + /** + * How far the panel is offset from its expanded position. + * range [0, 1] where 0 = expanded, 1 = collapsed. + */ + private float mSlideOffset; + + /** + * How far in pixels the slideable panel may move. + */ + private int mSlideRange; + + /** + * A panel view is locked into internal scrolling or another condition that + * is preventing a drag. + */ + private boolean mIsUnableToDrag; + + /** + * Flag indicating that sliding feature is enabled\disabled + */ + private boolean mIsSlidingEnabled; + + /** + * Flag indicating if a drag view can have its own touch events. If set + * to true, a drag view can scroll horizontally and have its own click listener. + * + * Default is set to false. + */ + private boolean mIsUsingDragViewTouchEvents; + + /** + * Threshold to tell if there was a scroll touch event. + */ + private final int mScrollTouchSlop; + + private float mInitialMotionX; + private float mInitialMotionY; + private float mAnchorPoint = 0.f; + private TranslateAnimation mAnimation; + + private PanelSlideListener mPanelSlideListener; + + private final ViewDragHelper mDragHelper; + + /** + * Stores whether or not the pane was expanded the last time it was slideable. + * If expand/collapse operations are invoked this state is modified. Used by + * instance state save/restore. + */ + private boolean mFirstLayout = true; + + private final Rect mTmpRect = new Rect(); + + /** + * Listener for monitoring events about sliding panes. + */ + public interface PanelSlideListener { + /** + * Called when a sliding pane's position changes. + * @param panel The child view that was moved + * @param slideOffset The new offset of this sliding pane within its range, from 0-1 + */ + public void onPanelSlide(View panel, float slideOffset); + /** + * Called when a sliding pane becomes slid completely collapsed. The pane may or may not + * be interactive at this point depending on if it's shown or hidden + * @param panel The child view that was slid to an collapsed position, revealing other panes + */ + public void onPanelCollapsed(View panel); + + /** + * Called when a sliding pane becomes slid completely expanded. The pane is now guaranteed + * to be interactive. It may now obscure other views in the layout. + * @param panel The child view that was slid to a expanded position + */ + public void onPanelExpanded(View panel); + + public void onPanelAnchored(View panel); + } + + /** + * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset + * of the listener methods you can extend this instead of implement the full interface. + */ + public static class SimplePanelSlideListener implements PanelSlideListener { + @Override + public void onPanelSlide(View panel, float slideOffset) { + } + @Override + public void onPanelCollapsed(View panel) { + } + @Override + public void onPanelExpanded(View panel) { + } + @Override + public void onPanelAnchored(View panel) { + } + } + + public VerticalSlidingPanel(Context context) { + this(context, null); + } + + public VerticalSlidingPanel(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public VerticalSlidingPanel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + if (attrs != null) { + TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS); + + if (defAttrs != null) { + int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY); + if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) { + throw new IllegalArgumentException("gravity must be set to either top or bottom"); + } + mIsSlidingUp = gravity == Gravity.BOTTOM; + } + + defAttrs.recycle(); + + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.VerticalSlidingPanel); + + if (ta != null) { + mPanelHeight = ta.getDimensionPixelSize(R.styleable.VerticalSlidingPanel_panelHeight, -1); + mShadowHeight = ta.getDimensionPixelSize(R.styleable.VerticalSlidingPanel_shadowHeight, -1); + mParallaxOffset = ta.getDimensionPixelSize(R.styleable.VerticalSlidingPanel_parallaxOffset, -1); + + mMinFlingVelocity = ta.getInt(R.styleable.VerticalSlidingPanel_flingVelocity, DEFAULT_MIN_FLING_VELOCITY); + mCoveredFadeColor = ta.getColor(R.styleable.VerticalSlidingPanel_fadeColor, DEFAULT_FADE_COLOR); + + mDragViewResId = ta.getResourceId(R.styleable.VerticalSlidingPanel_dragView, -1); + + mOverlayContent = ta.getBoolean(R.styleable.VerticalSlidingPanel_overlay,DEFAULT_OVERLAY_FLAG); + } + + ta.recycle(); + } + + final float density = context.getResources().getDisplayMetrics().density; + if (mPanelHeight == -1) { + mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f); + } + if (mShadowHeight == -1) { + mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f); + } + if (mParallaxOffset == -1) { + mParallaxOffset = (int) (DEFAULT_PARALLAX_OFFSET * density); + } + // If the shadow height is zero, don't show the shadow + if (mShadowHeight > 0) { + if (mIsSlidingUp) { + mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow, + context.getTheme()); + } else { + mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow, + context.getTheme()); + } + + } else { + mShadowDrawable = null; + } + + setWillNotDraw(false); + + mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); + mDragHelper.setMinVelocity(mMinFlingVelocity * density); + + mCanSlide = true; + mIsSlidingEnabled = true; + + ViewConfiguration vc = ViewConfiguration.get(context); + mScrollTouchSlop = vc.getScaledTouchSlop(); + } + + /** + * Set the Drag View after the view is inflated + */ + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (mDragViewResId != -1) { + mDragView = findViewById(mDragViewResId); + } + } + + /** + * Set the color used to fade the pane covered by the sliding pane out when the pane + * will become fully covered in the expanded state. + * + * @param color An ARGB-packed color value + */ + public void setCoveredFadeColor(int color) { + mCoveredFadeColor = color; + invalidate(); + } + + /** + * @return The ARGB-packed color value used to fade the fixed pane + */ + public int getCoveredFadeColor() { + return mCoveredFadeColor; + } + + /** + * Set sliding enabled flag + * @param enabled flag value + */ + public void setSlidingEnabled(boolean enabled) { + mIsSlidingEnabled = enabled; + } + + /** + * Set the collapsed panel height in pixels + * + * @param val A height in pixels + */ + public void setPanelHeight(int val) { + mPanelHeight = val; + requestLayout(); + } + + /** + * @return The current collapsed panel height + */ + public int getPanelHeight() { + return mPanelHeight; + } + + /** + * @return The current parallax offset + */ + public int getCurrentParallaxOffset() { + int offset = (int)(mParallaxOffset * (1 - mSlideOffset)); + return mIsSlidingUp ? -offset : offset; + } + + /** + * Sets the panel slide listener + * @param listener + */ + public void setPanelSlideListener(PanelSlideListener listener) { + mPanelSlideListener = listener; + } + + /** + * Set the draggable view portion. Use to null, to allow the whole panel to be draggable + * + * @param dragView A view that will be used to drag the panel. + */ + public void setDragView(View dragView) { + mDragView = dragView; + } + + /** + * Set an anchor point where the panel can stop during sliding + * + * @param anchorPoint A value between 0 and 1, determining the position of the anchor point + * starting from the top of the layout. + */ + public void setAnchorPoint(float anchorPoint) { + if (anchorPoint > 0 && anchorPoint < 1) + mAnchorPoint = anchorPoint; + } + + /** + * Sets whether or not the panel overlays the content + * @param overlayed + */ + public void setOverlayed(boolean overlayed) { + mOverlayContent = overlayed; + } + + /** + * Check if the panel is set as an overlay. + */ + public boolean isOverlayed() { + return mOverlayContent; + } + + void dispatchOnPanelSlide(View panel) { + if (mPanelSlideListener != null) { + mPanelSlideListener.onPanelSlide(panel, mSlideOffset); + } + } + + void dispatchOnPanelExpanded(View panel) { + if (mPanelSlideListener != null) { + mPanelSlideListener.onPanelExpanded(panel); + } + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + + void dispatchOnPanelCollapsed(View panel) { + if (mPanelSlideListener != null) { + mPanelSlideListener.onPanelCollapsed(panel); + } + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + + void dispatchOnPanelAnchored(View panel) { + if (mPanelSlideListener != null) { + mPanelSlideListener.onPanelAnchored(panel); + } + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + } + + void updateObscuredViewVisibility() { + if (getChildCount() == 0) { + return; + } + final int leftBound = getPaddingLeft(); + final int rightBound = getWidth() - getPaddingRight(); + final int topBound = getPaddingTop(); + final int bottomBound = getHeight() - getPaddingBottom(); + final int left; + final int right; + final int top; + final int bottom; + if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) { + left = mSlideableView.getLeft(); + right = mSlideableView.getRight(); + top = mSlideableView.getTop(); + bottom = mSlideableView.getBottom(); + } else { + left = right = top = bottom = 0; + } + View child = getChildAt(0); + final int clampedChildLeft = Math.max(leftBound, child.getLeft()); + final int clampedChildTop = Math.max(topBound, child.getTop()); + final int clampedChildRight = Math.min(rightBound, child.getRight()); + final int clampedChildBottom = Math.min(bottomBound, child.getBottom()); + final int vis; + if (clampedChildLeft >= left && clampedChildTop >= top && + clampedChildRight <= right && clampedChildBottom <= bottom) { + vis = INVISIBLE; + } else { + vis = VISIBLE; + } + child.setVisibility(vis); + } + + void setAllChildrenVisible() { + for (int i = 0, childCount = getChildCount(); i < childCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == INVISIBLE) { + child.setVisibility(VISIBLE); + } + } + } + + private static boolean hasOpaqueBackground(View v) { + final Drawable bg = v.getBackground(); + if (bg != null) { + return bg.getOpacity() == PixelFormat.OPAQUE; + } + return false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mFirstLayout = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mFirstLayout = true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException("Width must have an exact value or MATCH_PARENT"); + } else if (heightMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException("Height must have an exact value or MATCH_PARENT"); + } + + int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); + int panelHeight = mPanelHeight; + + final int childCount = getChildCount(); + + if (childCount > 2) { + Log.e(TAG, "onMeasure: More than two child views are not supported."); + } else if (getChildAt(1) != null && getChildAt(1).getVisibility() == GONE) { + panelHeight = 0; + } + + // We'll find the current one below. + mSlideableView = null; + mCanSlide = false; + + // First pass. Measure based on child LayoutParams width/height. + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + int height = layoutHeight; + if (child.getVisibility() == GONE) { + lp.dimWhenOffset = false; + continue; + } + + if (i == 1) { + lp.slideable = true; + lp.dimWhenOffset = true; + mSlideableView = child; + mCanSlide = true; + } else { + if (!mOverlayContent) { + height -= panelHeight; + } + mMainView = child; + } + + int childWidthSpec; + if (lp.width == LayoutParams.WRAP_CONTENT) { + childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST); + } else if (lp.width == LayoutParams.MATCH_PARENT) { + childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); + } else { + childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); + } + + int childHeightSpec; + if (lp.height == LayoutParams.WRAP_CONTENT) { + childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); + } else if (lp.height == LayoutParams.MATCH_PARENT) { + childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + } else { + childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); + } + + child.measure(childWidthSpec, childHeightSpec); + } + + setMeasuredDimension(widthSize, heightSize); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int paddingLeft = getPaddingLeft(); + final int paddingTop = getPaddingTop(); + final int slidingTop = getSlidingTop(); + + final int childCount = getChildCount(); + + if (mFirstLayout) { + switch (mSlideState) { + case EXPANDED: + mSlideOffset = mCanSlide ? 0.f : 1.f; + break; + case ANCHORED: + mSlideOffset = mCanSlide ? mAnchorPoint : 1.f; + break; + default: + mSlideOffset = 1.f; + break; + } + } + + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + + if (child.getVisibility() == GONE) { + continue; + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int childHeight = child.getMeasuredHeight(); + + if (lp.slideable) { + mSlideRange = childHeight - mPanelHeight; + } + + int childTop; + if (mIsSlidingUp) { + childTop = lp.slideable ? slidingTop + (int) (mSlideRange * mSlideOffset) : paddingTop; + } else { + childTop = lp.slideable ? slidingTop - (int) (mSlideRange * mSlideOffset) : paddingTop; + if (!lp.slideable && !mOverlayContent) { + childTop += mPanelHeight; + } + } + final int childBottom = childTop + childHeight; + final int childLeft = paddingLeft; + final int childRight = childLeft + child.getMeasuredWidth(); + + child.layout(childLeft, childTop, childRight, childBottom); + } + + if (mFirstLayout) { + updateObscuredViewVisibility(); + } + + mFirstLayout = false; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + // Recalculate sliding panes and their details + if (h != oldh) { + mFirstLayout = true; + } + } + + /** + * Set if the drag view can have its own touch events. If set + * to true, a drag view can scroll horizontally and have its own click listener. + * + * Default is set to false. + */ + public void setEnableDragViewTouchEvents(boolean enabled) { + mIsUsingDragViewTouchEvents = enabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + final int action = MotionEventCompat.getActionMasked(ev); + + if (mAnimation != null || !mCanSlide || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { + mDragHelper.cancel(); + return super.onInterceptTouchEvent(ev); + } + + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mDragHelper.cancel(); + return false; + } + + final float x = ev.getX(); + final float y = ev.getY(); + boolean interceptTap = false; + + switch (action) { + case MotionEvent.ACTION_DOWN: { + mIsUnableToDrag = false; + mInitialMotionX = x; + mInitialMotionY = y; + if (isDragViewUnder((int) x, (int) y) && !mIsUsingDragViewTouchEvents) { + interceptTap = true; + } + break; + } + + case MotionEvent.ACTION_MOVE: { + final float adx = Math.abs(x - mInitialMotionX); + final float ady = Math.abs(y - mInitialMotionY); + final int dragSlop = mDragHelper.getTouchSlop(); + + // Handle any horizontal scrolling on the drag view. + if (mIsUsingDragViewTouchEvents) { + if (adx > mScrollTouchSlop && ady < mScrollTouchSlop) { + return super.onInterceptTouchEvent(ev); + } + // Intercept the touch if the drag view has any vertical scroll. + // onTouchEvent will determine if the view should drag vertically. + else if (ady > mScrollTouchSlop) { + interceptTap = isDragViewUnder((int) x, (int) y); + } + } + + if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) x, (int) y)) { + mDragHelper.cancel(); + mIsUnableToDrag = true; + return false; + } + break; + } + } + + final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev); + + return interceptForDrag; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mCanSlide || !mIsSlidingEnabled || mAnimation != null) { + return super.onTouchEvent(ev); + } + + mDragHelper.processTouchEvent(ev); + + final int action = ev.getAction(); + boolean wantTouchEvents = true; + + switch (action & MotionEventCompat.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + + //Fix to allow both SettingPanel Drag and Workspace Drag + if (mSlideState == SlideState.COLLAPSED) { + if (y < mSlideableView.getTop()) { + return false; + } + } + + mInitialMotionX = x; + mInitialMotionY = y; + break; + } + + case MotionEvent.ACTION_UP: { + final float x = ev.getX(); + final float y = ev.getY(); + final float dx = x - mInitialMotionX; + final float dy = y - mInitialMotionY; + final int slop = mDragHelper.getTouchSlop(); + View dragView = mDragView != null ? mDragView : mSlideableView; + if (dx * dx + dy * dy < slop * slop && + isDragViewUnder((int) x, (int) y)) { + dragView.playSoundEffect(SoundEffectConstants.CLICK); + if (!isExpanded() && !isAnchored()) { + expandPane(mAnchorPoint); + } else { + collapsePane(); + } + break; + } + break; + } + } + + return wantTouchEvents; + } + + private boolean isDragViewUnder(int x, int y) { + View dragView = mDragView != null ? mDragView : mSlideableView; + if (dragView == null) return false; + int[] viewLocation = new int[2]; + dragView.getLocationOnScreen(viewLocation); + int[] parentLocation = new int[2]; + this.getLocationOnScreen(parentLocation); + int screenX = parentLocation[0] + x; + int screenY = parentLocation[1] + y; + return screenX >= viewLocation[0] && screenX < viewLocation[0] + dragView.getWidth() && + screenY >= viewLocation[1] && screenY < viewLocation[1] + dragView.getHeight(); + } + + private boolean expandPane(View pane, int initialVelocity, float mSlideOffset) { + if (mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity)) { + return true; + } + return false; + } + + private boolean collapsePane(View pane, int initialVelocity) { + if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) { + return true; + } + return false; + } + + private int getSlidingTop() { + if (mSlideableView != null) { + return mIsSlidingUp + ? getMeasuredHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() + : getPaddingTop(); + } + + return getMeasuredHeight() - getPaddingBottom(); + } + + /** + * Collapse the sliding pane if it is currently slideable. If first layout + * has already completed this will animate. + * + * @return true if the pane was slideable and is now collapsed/in the process of collapsing + */ + public boolean collapsePane() { + return collapsePane(mSlideableView, 0); + } + + /** + * Expand the sliding pane if it is currently slideable. If first layout + * has already completed this will animate. + * + * @return true if the pane was slideable and is now expanded/in the process of expading + */ + public boolean expandPane() { + return expandPane(0); + } + + /** + * Partially expand the sliding pane up to a specific offset + * + * @param mSlideOffset Value between 0 and 1, where 0 is completely expanded. + * @return true if the pane was slideable and is now expanded/in the process of expading + */ + public boolean expandPane(float mSlideOffset) { + if (!isPaneVisible()) { + showPane(); + } + return expandPane(mSlideableView, 0, mSlideOffset); + } + + /** + * Check if the layout is completely expanded. + * + * @return true if sliding panels are completely expanded + */ + public boolean isExpanded() { + return mSlideState == SlideState.EXPANDED; + } + + /** + * Check if the layout is anchored in an intermediate point. + * + * @return true if sliding panels are anchored + */ + public boolean isAnchored() { + return mSlideState == SlideState.ANCHORED; + } + + /** + * Check if the content in this layout cannot fully fit side by side and therefore + * the content pane can be slid back and forth. + * + * @return true if content in this layout can be expanded + */ + public boolean isSlideable() { + return mCanSlide; + } + + public boolean isPaneVisible() { + if (getChildCount() < 2) { + return false; + } + View slidingPane = getChildAt(1); + return slidingPane.getVisibility() == View.VISIBLE; + } + + public void showPane() { + if (getChildCount() < 2) { + return; + } + final View slidingPane = getChildAt(1); + mAnimation = new TranslateAnimation(0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight(), 0); + mAnimation.setDuration(400); + mAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + slidingPane.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animation animation) { + requestLayout(); + mAnimation = null; + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + slidingPane.startAnimation(mAnimation); + } + + public void hidePane() { + if (mSlideableView == null) { + return; + } + mAnimation = new TranslateAnimation(0, 0, 0, (mIsSlidingUp ? 1 : -1) * getPanelHeight()); + mAnimation.setDuration(500); + mAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + mSlideableView.setVisibility(View.GONE); + requestLayout(); + mAnimation = null; + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + mSlideableView.startAnimation(mAnimation); + } + + private void onPanelDragged(int newTop) { + final int topBound = getSlidingTop(); + mSlideOffset = mIsSlidingUp + ? (float) (newTop - topBound) / mSlideRange + : (float) (topBound - newTop) / mSlideRange; + dispatchOnPanelSlide(mSlideableView); + + if (mParallaxOffset > 0) { + int mainViewOffset = getCurrentParallaxOffset(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + mMainView.setTranslationY(mainViewOffset); + } else { + mMainView.animate().translationY(mainViewOffset); + } + } + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + boolean result; + final int save = canvas.save(Canvas.CLIP_SAVE_FLAG); + + boolean drawScrim = false; + + if (mCanSlide && !lp.slideable && mSlideableView != null) { + // Clip against the slider; no sense drawing what will immediately be covered, + // Unless the panel is set to overlay content + if (!mOverlayContent) { + canvas.getClipBounds(mTmpRect); + if (mIsSlidingUp) { + mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop()); + } else { + mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom()); + } + canvas.clipRect(mTmpRect); + } + if (mSlideOffset < 1) { + drawScrim = true; + } + } + + result = super.drawChild(canvas, child, drawingTime); + canvas.restoreToCount(save); + + if (drawScrim) { + final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24; + final int imag = (int) (baseAlpha * (1 - mSlideOffset)); + final int color = imag << 24 | (mCoveredFadeColor & 0xffffff); + mCoveredFadePaint.setColor(color); + canvas.drawRect(mTmpRect, mCoveredFadePaint); + } + + return result; + } + + /** + * Smoothly animate mDraggingPane to the target X position within its range. + * + * @param slideOffset position to animate to + * @param velocity initial velocity in case of fling, or 0. + */ + boolean smoothSlideTo(float slideOffset, int velocity) { + if (!mCanSlide) { + // Nothing to do. + return false; + } + + final int topBound = getSlidingTop(); + int y = mIsSlidingUp + ? (int) (topBound + slideOffset * mSlideRange) + : (int) (topBound - slideOffset * mSlideRange); + + if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) { + setAllChildrenVisible(); + ViewCompat.postInvalidateOnAnimation(this); + return true; + } + return false; + } + + @Override + public void computeScroll() { + if (mDragHelper.continueSettling(true)) { + if (!mCanSlide) { + mDragHelper.abort(); + return; + } + + ViewCompat.postInvalidateOnAnimation(this); + } + } + + @Override + public void draw(Canvas c) { + super.draw(c); + + if (mSlideableView == null) { + // No need to draw a shadow if we don't have one. + return; + } + + final int right = mSlideableView.getRight(); + final int top; + final int bottom; + if (mIsSlidingUp) { + top = mSlideableView.getTop() - mShadowHeight; + bottom = mSlideableView.getTop(); + } else { + top = mSlideableView.getBottom(); + bottom = mSlideableView.getBottom() + mShadowHeight; + } + final int left = mSlideableView.getLeft(); + + if (mShadowDrawable != null) { + mShadowDrawable.setBounds(left, top, right, bottom); + mShadowDrawable.draw(c); + } + } + + /** + * Tests scrollability within child views of v given a delta of dx. + * + * @param v View to test for horizontal scrollability + * @param checkV Whether the view v passed should itself be checked for scrollability (true), + * or just its children (false). + * @param dx Delta scrolled in pixels + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point + * @return true if child views of v can be scrolled by delta of dx. + */ + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (v instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) v; + final int scrollX = v.getScrollX(); + final int scrollY = v.getScrollY(); + final int count = group.getChildCount(); + // Count backwards - let topmost views consume scroll distance first. + for (int i = count - 1; i >= 0; i--) { + final View child = group.getChildAt(i); + if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && + y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && + canScroll(child, true, dx, x + scrollX - child.getLeft(), + y + scrollY - child.getTop())) { + return true; + } + } + } + return checkV && ViewCompat.canScrollHorizontally(v, -dx); + } + + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof MarginLayoutParams + ? new LayoutParams((MarginLayoutParams) p) + : new LayoutParams(p); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams && super.checkLayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + + SavedState ss = new SavedState(superState); + ss.mSlideState = mSlideState; + + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mSlideState = ss.mSlideState; + } + + private class DragHelperCallback extends ViewDragHelper.Callback { + + @Override + public boolean tryCaptureView(View child, int pointerId) { + if (mIsUnableToDrag) { + return false; + } + + return ((LayoutParams) child.getLayoutParams()).slideable; + } + + @Override + public void onViewDragStateChanged(int state) { + int anchoredTop = (int)(mAnchorPoint*mSlideRange); + + if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) { + if (mSlideOffset == 0) { + if (mSlideState != SlideState.EXPANDED) { + updateObscuredViewVisibility(); + dispatchOnPanelExpanded(mSlideableView); + mSlideState = SlideState.EXPANDED; + } + } else if (mSlideOffset == (float)anchoredTop/(float)mSlideRange) { + if (mSlideState != SlideState.ANCHORED) { + updateObscuredViewVisibility(); + dispatchOnPanelAnchored(mSlideableView); + mSlideState = SlideState.ANCHORED; + } + } else if (mSlideState != SlideState.COLLAPSED) { + dispatchOnPanelCollapsed(mSlideableView); + mSlideState = SlideState.COLLAPSED; + } + } + } + + @Override + public void onViewCaptured(View capturedChild, int activePointerId) { + // Make all child views visible in preparation for sliding things around + setAllChildrenVisible(); + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + onPanelDragged(top); + invalidate(); + } + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + int top = mIsSlidingUp + ? getSlidingTop() + : getSlidingTop() - mSlideRange; + + if (mAnchorPoint != 0) { + int anchoredTop; + float anchorOffset; + if (mIsSlidingUp) { + anchoredTop = (int)(mAnchorPoint*mSlideRange); + anchorOffset = (float)anchoredTop/(float)mSlideRange; + } else { + anchoredTop = mPanelHeight - (int)(mAnchorPoint*mSlideRange); + anchorOffset = (float)(mPanelHeight - anchoredTop)/(float)mSlideRange; + } + + if (yvel > 0 || (yvel == 0 && mSlideOffset >= (1f+anchorOffset)/2)) { + top += mSlideRange; + } else if (yvel == 0 && mSlideOffset < (1f+anchorOffset)/2 + && mSlideOffset >= anchorOffset/2) { + top += mSlideRange * mAnchorPoint; + } + + } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) { + top += mSlideRange; + } + + mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top); + invalidate(); + } + + @Override + public int getViewVerticalDragRange(View child) { + return mSlideRange; + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + final int topBound; + final int bottomBound; + if (mIsSlidingUp) { + topBound = getSlidingTop(); + bottomBound = topBound + mSlideRange; + } else { + bottomBound = getPaddingTop(); + topBound = bottomBound - mSlideRange; + } + + return Math.min(Math.max(top, topBound), bottomBound); + } + } + + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + private static final int[] ATTRS = new int[] { + android.R.attr.layout_weight + }; + + /** + * True if this pane is the slideable pane in the layout. + */ + boolean slideable; + + /** + * True if this view should be drawn dimmed + * when it's been offset from its default position. + */ + boolean dimWhenOffset; + + Paint dimPaint; + + public LayoutParams() { + super(MATCH_PARENT, MATCH_PARENT); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(android.view.ViewGroup.LayoutParams source) { + super(source); + } + + public LayoutParams(MarginLayoutParams source) { + super(source); + } + + public LayoutParams(LayoutParams source) { + super(source); + } + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + + final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS); + a.recycle(); + } + + } + + static class SavedState extends BaseSavedState { + SlideState mSlideState; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + try { + mSlideState = Enum.valueOf(SlideState.class, in.readString()); + } catch (IllegalArgumentException e) { + mSlideState = SlideState.COLLAPSED; + } + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeString(mSlideState.toString()); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 856e3b88a..59c870a8d 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -68,6 +68,7 @@ import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.AccessibilityDragSource; import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.WallpaperUtils; @@ -217,6 +218,7 @@ public class Workspace extends PagedView private boolean mWorkspaceFadeInAdjacentScreens; WallpaperOffsetInterpolator mWallpaperOffset; + private boolean mScrollWallpaper; @Thunk boolean mWallpaperIsLiveWallpaper; @Thunk int mNumPagesForWallpaperParallax; @Thunk float mLastSetWallpaperOffsetSteps = 0; @@ -290,6 +292,8 @@ public class Workspace extends PagedView } }; + private boolean mHideIconLabels; + /** * Used to inflate the Workspace from XML. * @@ -312,6 +316,10 @@ public class Workspace extends PagedView mOutlineHelper = HolographicOutlineHelper.obtain(context); + mHideIconLabels = SettingsProvider.getBoolean(context, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default); + mLauncher = (Launcher) context; mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); final Resources res = getResources(); @@ -964,6 +972,8 @@ public class Workspace extends PagedView */ void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boolean insert, boolean computeXYFromRank) { + reloadSettings(); + if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { if (getScreenWithId(screenId) == null) { Log.e(TAG, "Skipping child, screenId " + screenId + " not found"); @@ -994,9 +1004,10 @@ public class Workspace extends PagedView screenId = mLauncher.getHotseat().getOrderInHotseat(x, y); } } else { - // Show folder title if not in the hotseat if (child instanceof FolderIcon) { - ((FolderIcon) child).setTextVisible(true); + ((FolderIcon) child).setTextVisible(!mHideIconLabels); + } else if (child instanceof BubbleTextView) { + ((BubbleTextView) child).setTextVisibility(!mHideIconLabels); } layout = getScreenWithId(screenId); child.setOnKeyListener(new IconKeyEventListener()); @@ -1533,7 +1544,8 @@ public class Workspace extends PagedView @Override public void computeScroll() { super.computeScroll(); - mWallpaperOffset.syncWithScroll(); + + if (mScrollWallpaper) mWallpaperOffset.syncWithScroll(); } @Override @@ -1702,6 +1714,8 @@ public class Workspace extends PagedView } } + setScrollingWallpaper(); + // Update wallpaper dimensions if they were changed since last onResume // (we also always set the wallpaper dimensions in the constructor) if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) { @@ -1715,7 +1729,8 @@ public class Workspace extends PagedView @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { + if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount() + && mScrollWallpaper) { mWallpaperOffset.syncWithScroll(); mWallpaperOffset.jumpToFinal(); } @@ -3523,6 +3538,7 @@ public class Workspace extends PagedView case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, (FolderInfo) info, mIconCache); + ((FolderIcon) view).setTextVisible(!mHideIconLabels); break; default: throw new IllegalStateException("Unknown item type: " + info.itemType); @@ -4482,6 +4498,29 @@ public class Workspace extends PagedView sourceData.putInt(Stats.SOURCE_EXTRA_CONTAINER_PAGE, getCurrentPage()); } + private void reloadSettings() { + mHideIconLabels = SettingsProvider.getBoolean(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default); + + setScrollingWallpaper(); + } + + /** + * Gets the preference for whether to apply scrolling wallpaper effect or not and applies the + * preference. + */ + private void setScrollingWallpaper() { + mScrollWallpaper = SettingsProvider.getBoolean(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL, + R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default); + if (!mScrollWallpaper) { + if (mWindowToken != null) mWallpaperManager.setWallpaperOffsets(mWindowToken, 0f, 0.5f); + } else { + mWallpaperOffset.syncWithScroll(); + } + } + /** * Used as a workaround to ensure that the AppWidgetService receives the * PACKAGE_ADDED broadcast before updating widgets. diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index bff7752af..76f47c572 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -229,6 +229,11 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc updateScrubber(); } + public void reset() { + List apps = mApps.getApps(); + updateApps(apps); + } + /** * Updates existing apps in the list */ diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index a48390732..17d106731 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -41,6 +41,7 @@ import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.settings.SettingsProvider; import com.android.launcher3.util.Thunk; import java.util.HashMap; @@ -356,7 +357,6 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter= firstPosition && position <= lastPosition) { + return; // Already on screen + } + + final int offset = (int) (getHeight() * PREFERRED_SELECTION_OFFSET_FROM_TOP); + if (!mSmoothScrollRequested) { + setSelectionFromTop(position, offset); + + // Since we have changed the scrolling position, we need to redo child layout + // Calling "requestLayout" in the middle of a layout pass has no effect, + // so we call layoutChildren explicitly + super.layoutChildren(); + + } else { + // We will first position the list a couple of screens before or after + // the new selection and then scroll smoothly to it. + int twoScreens = (lastPosition - firstPosition) * 2; + int preliminaryPosition; + if (position < firstPosition) { + preliminaryPosition = position + twoScreens; + if (preliminaryPosition >= getCount()) { + preliminaryPosition = getCount() - 1; + } + if (preliminaryPosition < firstPosition) { + setSelection(preliminaryPosition); + super.layoutChildren(); + } + } else { + preliminaryPosition = position - twoScreens; + if (preliminaryPosition < 0) { + preliminaryPosition = 0; + } + if (preliminaryPosition > lastPosition) { + setSelection(preliminaryPosition); + super.layoutChildren(); + } + } + + + smoothScrollToPositionFromTop(position, offset); + } + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/list/CompositeCursorAdapter.java b/src/com/android/launcher3/list/CompositeCursorAdapter.java new file mode 100644 index 000000000..b163c501c --- /dev/null +++ b/src/com/android/launcher3/list/CompositeCursorAdapter.java @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.list; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import java.util.ArrayList; + +/** + * A general purpose adapter that is composed of multiple cursors. It just + * appends them in the order they are added. + */ +public abstract class CompositeCursorAdapter extends BaseAdapter { + + private static final int INITIAL_CAPACITY = 2; + + public static class Partition { + boolean showIfEmpty; + boolean hasHeader; + + Cursor cursor; + int idColumnIndex; + int count; + + public Partition(boolean showIfEmpty, boolean hasHeader) { + this.showIfEmpty = showIfEmpty; + this.hasHeader = hasHeader; + } + + /** + * True if the directory should be shown even if no contacts are found. + */ + public boolean getShowIfEmpty() { + return showIfEmpty; + } + + public boolean getHasHeader() { + return hasHeader; + } + } + + private final Context mContext; + private ArrayList mPartitions; + private int mCount = 0; + private boolean mCacheValid = true; + private boolean mNotificationsEnabled = true; + private boolean mNotificationNeeded; + + public CompositeCursorAdapter(Context context) { + this(context, INITIAL_CAPACITY); + } + + public CompositeCursorAdapter(Context context, int initialCapacity) { + mContext = context; + mPartitions = new ArrayList(); + } + + public Context getContext() { + return mContext; + } + + /** + * Registers a partition. The cursor for that partition can be set later. + * Partitions should be added in the order they are supposed to appear in the + * list. + */ + public void addPartition(boolean showIfEmpty, boolean hasHeader) { + addPartition(new Partition(showIfEmpty, hasHeader)); + } + + public void addPartition(Partition partition) { + mPartitions.add(partition); + invalidate(); + notifyDataSetChanged(); + } + + public void addPartition(int location, Partition partition) { + mPartitions.add(location, partition); + invalidate(); + notifyDataSetChanged(); + } + + public void removePartition(int partitionIndex) { + Cursor cursor = mPartitions.get(partitionIndex).cursor; + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + mPartitions.remove(partitionIndex); + invalidate(); + notifyDataSetChanged(); + } + + /** + * Removes cursors for all partitions. + */ + // TODO: Is this really what this is supposed to do? Just remove the cursors? Not close them? + // Not remove the partitions themselves? Isn't this leaking? + + public void clearPartitions() { + for (Partition partition : mPartitions) { + partition.cursor = null; + } + invalidate(); + notifyDataSetChanged(); + } + + /** + * Closes all cursors and removes all partitions. + */ + public void close() { + for (Partition partition : mPartitions) { + Cursor cursor = partition.cursor; + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + mPartitions.clear(); + invalidate(); + notifyDataSetChanged(); + } + + public void setHasHeader(int partitionIndex, boolean flag) { + mPartitions.get(partitionIndex).hasHeader = flag; + invalidate(); + } + + public void setShowIfEmpty(int partitionIndex, boolean flag) { + mPartitions.get(partitionIndex).showIfEmpty = flag; + invalidate(); + } + + public Partition getPartition(int partitionIndex) { + return mPartitions.get(partitionIndex); + } + + protected void invalidate() { + mCacheValid = false; + } + + public int getPartitionCount() { + return mPartitions.size(); + } + + protected void ensureCacheValid() { + if (mCacheValid) { + return; + } + + mCount = 0; + for (Partition partition : mPartitions) { + Cursor cursor = partition.cursor; + int count = cursor != null ? cursor.getCount() : 0; + if (partition.hasHeader) { + if (count != 0 || partition.showIfEmpty) { + count++; + } + } + partition.count = count; + mCount += count; + } + + mCacheValid = true; + } + + /** + * Returns true if the specified partition was configured to have a header. + */ + public boolean hasHeader(int partition) { + return mPartitions.get(partition).hasHeader; + } + + /** + * Returns the total number of list items in all partitions. + */ + public int getCount() { + ensureCacheValid(); + return mCount; + } + + /** + * Returns the cursor for the given partition + */ + public Cursor getCursor(int partition) { + return mPartitions.get(partition).cursor; + } + + /** + * Changes the cursor for an individual partition. + */ + public void changeCursor(int partition, Cursor cursor) { + Cursor prevCursor = mPartitions.get(partition).cursor; + if (prevCursor != cursor) { + if (prevCursor != null && !prevCursor.isClosed()) { + prevCursor.close(); + } + mPartitions.get(partition).cursor = cursor; + if (cursor != null) { + mPartitions.get(partition).idColumnIndex = cursor.getColumnIndex("_id"); + } + invalidate(); + notifyDataSetChanged(); + } + } + + /** + * Returns true if the specified partition has no cursor or an empty cursor. + */ + public boolean isPartitionEmpty(int partition) { + Cursor cursor = mPartitions.get(partition).cursor; + return cursor == null || cursor.getCount() == 0; + } + + /** + * Given a list position, returns the index of the corresponding partition. + */ + public int getPartitionForPosition(int position) { + ensureCacheValid(); + int start = 0; + for (int i = 0, n = mPartitions.size(); i < n; i++) { + int end = start + mPartitions.get(i).count; + if (position >= start && position < end) { + return i; + } + start = end; + } + return -1; + } + + /** + * Given a list position, return the offset of the corresponding item in its + * partition. The header, if any, will have offset -1. + */ + public int getOffsetInPartition(int position) { + ensureCacheValid(); + int start = 0; + for (Partition partition : mPartitions) { + int end = start + partition.count; + if (position >= start && position < end) { + int offset = position - start; + if (partition.hasHeader) { + offset--; + } + return offset; + } + start = end; + } + return -1; + } + + /** + * Returns the first list position for the specified partition. + */ + public int getPositionForPartition(int partition) { + ensureCacheValid(); + int position = 0; + for (int i = 0; i < partition; i++) { + position += mPartitions.get(i).count; + } + return position; + } + + @Override + public int getViewTypeCount() { + return getItemViewTypeCount() + 1; + } + + /** + * Returns the overall number of item view types across all partitions. An + * implementation of this method needs to ensure that the returned count is + * consistent with the values returned by {@link #getItemViewType(int,int)}. + */ + public int getItemViewTypeCount() { + return 1; + } + + /** + * Returns the view type for the list item at the specified position in the + * specified partition. + */ + protected int getItemViewType(int partition, int position) { + return 1; + } + + @Override + public int getItemViewType(int position) { + ensureCacheValid(); + int start = 0; + for (int i = 0, n = mPartitions.size(); i < n; i++) { + int end = start + mPartitions.get(i).count; + if (position >= start && position < end) { + int offset = position - start; + if (mPartitions.get(i).hasHeader) { + offset--; + } + if (offset == -1) { + return IGNORE_ITEM_VIEW_TYPE; + } else { + return getItemViewType(i, offset); + } + } + start = end; + } + + throw new ArrayIndexOutOfBoundsException(position); + } + + public View getView(int position, View convertView, ViewGroup parent) { + ensureCacheValid(); + int start = 0; + for (int i = 0, n = mPartitions.size(); i < n; i++) { + int end = start + mPartitions.get(i).count; + if (position >= start && position < end) { + int offset = position - start; + if (mPartitions.get(i).hasHeader) { + offset--; + } + View view; + if (offset == -1) { + view = getHeaderView(i, mPartitions.get(i).cursor, convertView, parent); + } else { + if (!mPartitions.get(i).cursor.moveToPosition(offset)) { + throw new IllegalStateException("Couldn't move cursor to position " + + offset); + } + view = getView(i, mPartitions.get(i).cursor, offset, convertView, parent); + } + if (view == null) { + throw new NullPointerException("View should not be null, partition: " + i + + " position: " + offset); + } + return view; + } + start = end; + } + + throw new ArrayIndexOutOfBoundsException(position); + } + + /** + * Returns the header view for the specified partition, creating one if needed. + */ + protected View getHeaderView(int partition, Cursor cursor, View convertView, + ViewGroup parent) { + View view = convertView != null + ? convertView + : newHeaderView(mContext, partition, cursor, parent); + bindHeaderView(view, partition, cursor); + return view; + } + + /** + * Creates the header view for the specified partition. + */ + protected View newHeaderView(Context context, int partition, Cursor cursor, + ViewGroup parent) { + return null; + } + + /** + * Binds the header view for the specified partition. + */ + protected void bindHeaderView(View view, int partition, Cursor cursor) { + } + + /** + * Returns an item view for the specified partition, creating one if needed. + */ + protected View getView(int partition, Cursor cursor, int position, View convertView, + ViewGroup parent) { + View view; + if (convertView != null) { + view = convertView; + } else { + view = newView(mContext, partition, cursor, position, parent); + } + bindView(view, partition, cursor, position); + return view; + } + + /** + * Creates an item view for the specified partition and position. Position + * corresponds directly to the current cursor position. + */ + protected abstract View newView(Context context, int partition, Cursor cursor, int position, + ViewGroup parent); + + /** + * Binds an item view for the specified partition and position. Position + * corresponds directly to the current cursor position. + */ + protected abstract void bindView(View v, int partition, Cursor cursor, int position); + + /** + * Returns a pre-positioned cursor for the specified list position. + */ + public Object getItem(int position) { + ensureCacheValid(); + int start = 0; + for (Partition mPartition : mPartitions) { + int end = start + mPartition.count; + if (position >= start && position < end) { + int offset = position - start; + if (mPartition.hasHeader) { + offset--; + } + if (offset == -1) { + return null; + } + Cursor cursor = mPartition.cursor; + cursor.moveToPosition(offset); + return cursor; + } + start = end; + } + + return null; + } + + /** + * Returns the item ID for the specified list position. + */ + public long getItemId(int position) { + ensureCacheValid(); + int start = 0; + for (Partition mPartition : mPartitions) { + int end = start + mPartition.count; + if (position >= start && position < end) { + int offset = position - start; + if (mPartition.hasHeader) { + offset--; + } + if (offset == -1) { + return 0; + } + if (mPartition.idColumnIndex == -1) { + return 0; + } + + Cursor cursor = mPartition.cursor; + if (cursor == null || cursor.isClosed() || !cursor.moveToPosition(offset)) { + return 0; + } + return cursor.getLong(mPartition.idColumnIndex); + } + start = end; + } + + return 0; + } + + /** + * Returns false if any partition has a header. + */ + @Override + public boolean areAllItemsEnabled() { + for (Partition mPartition : mPartitions) { + if (mPartition.hasHeader) { + return false; + } + } + return true; + } + + /** + * Returns true for all items except headers. + */ + @Override + public boolean isEnabled(int position) { + ensureCacheValid(); + int start = 0; + for (int i = 0, n = mPartitions.size(); i < n; i++) { + int end = start + mPartitions.get(i).count; + if (position >= start && position < end) { + int offset = position - start; + if (mPartitions.get(i).hasHeader && offset == 0) { + return false; + } else { + return isEnabled(i, offset); + } + } + start = end; + } + + return false; + } + + /** + * Returns true if the item at the specified offset of the specified + * partition is selectable and clickable. + */ + protected boolean isEnabled(int partition, int position) { + return true; + } + + /** + * Enable or disable data change notifications. It may be a good idea to + * disable notifications before making changes to several partitions at once. + */ + public void setNotificationsEnabled(boolean flag) { + mNotificationsEnabled = flag; + if (flag && mNotificationNeeded) { + notifyDataSetChanged(); + } + } + + @Override + public void notifyDataSetChanged() { + if (mNotificationsEnabled) { + mNotificationNeeded = false; + super.notifyDataSetChanged(); + } else { + mNotificationNeeded = true; + } + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/list/PinnedHeaderListAdapter.java b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java new file mode 100644 index 000000000..cc053e18f --- /dev/null +++ b/src/com/android/launcher3/list/PinnedHeaderListAdapter.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.list; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; + +/** + * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers. + */ +public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter + implements PinnedHeaderListView.PinnedHeaderAdapter { + + public static final int PARTITION_HEADER_TYPE = 0; + + private boolean mPinnedPartitionHeadersEnabled; + private boolean mHeaderVisibility[]; + + public PinnedHeaderListAdapter(Context context) { + super(context); + } + + public PinnedHeaderListAdapter(Context context, int initialCapacity) { + super(context, initialCapacity); + } + + public boolean getPinnedPartitionHeadersEnabled() { + return mPinnedPartitionHeadersEnabled; + } + + public void setPinnedPartitionHeadersEnabled(boolean flag) { + this.mPinnedPartitionHeadersEnabled = flag; + } + + @Override + public int getPinnedHeaderCount() { + if (mPinnedPartitionHeadersEnabled) { + return getPartitionCount(); + } else { + return 0; + } + } + + protected boolean isPinnedPartitionHeaderVisible(int partition) { + return getPinnedPartitionHeadersEnabled() && hasHeader(partition) + && !isPartitionEmpty(partition); + } + + /** + * The default implementation creates the same type of view as a normal + * partition header. + */ + @Override + public View getPinnedHeaderView(int partition, View convertView, ViewGroup parent) { + if (hasHeader(partition)) { + View view = null; + if (convertView != null) { + Integer headerType = (Integer)convertView.getTag(); + if (headerType != null && headerType == PARTITION_HEADER_TYPE) { + view = convertView; + } + } + if (view == null) { + view = newHeaderView(getContext(), partition, null, parent); + view.setTag(PARTITION_HEADER_TYPE); + view.setFocusable(false); + view.setEnabled(false); + } + bindHeaderView(view, partition, getCursor(partition)); + view.setLayoutDirection(parent.getLayoutDirection()); + return view; + } else { + return null; + } + } + + @Override + public void configurePinnedHeaders(PinnedHeaderListView listView) { + if (!getPinnedPartitionHeadersEnabled()) { + return; + } + + int size = getPartitionCount(); + boolean unCached = false; + // Cache visibility bits, because we will need them several times later on + if (mHeaderVisibility == null || mHeaderVisibility.length != size) { + mHeaderVisibility = new boolean[size]; + unCached = true; + } + for (int i = 0; i < size; i++) { + boolean visible = isPinnedPartitionHeaderVisible(i); + mHeaderVisibility[i] = visible; + if (!visible) { + listView.setHeaderInvisible(i, true); + } + } + + int headerViewsCount = listView.getHeaderViewsCount(); + + // Starting at the top, find and pin headers for partitions preceding the visible one(s) + int topHeaderHeight = 0; + for (int i = 0; i < size; i++) { + if (mHeaderVisibility[i]) { + int position = listView.getPositionAt(topHeaderHeight) - headerViewsCount; + int partition = getPartitionForPosition(position); + if (i > partition) { + break; + } + + if (!unCached){ + listView.setHeaderPinnedAtTop(i, topHeaderHeight, false); + topHeaderHeight += listView.getPinnedHeaderHeight(i); + } + + } + } + } + + @Override + public int getScrollPositionForHeader(int viewIndex) { + return getPositionForPartition(viewIndex); + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/list/PinnedHeaderListView.java b/src/com/android/launcher3/list/PinnedHeaderListView.java new file mode 100644 index 000000000..58e8791ab --- /dev/null +++ b/src/com/android/launcher3/list/PinnedHeaderListView.java @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.list; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ListAdapter; + +/** + * A ListView that maintains a header pinned at the top of the list. The + * pinned header can be pushed up and dissolved as needed. + */ +public class PinnedHeaderListView extends AutoScrollListView + implements OnScrollListener, OnItemSelectedListener { + + /** + * Adapter interface. The list adapter must implement this interface. + */ + public interface PinnedHeaderAdapter { + + /** + * Returns the overall number of pinned headers, visible or not. + */ + int getPinnedHeaderCount(); + + /** + * Creates or updates the pinned header view. + */ + View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent); + + /** + * Configures the pinned headers to match the visible list items. The + * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop}, + * {@link PinnedHeaderListView#setHeaderPinnedAtBottom}, + * {@link PinnedHeaderListView#setFadingHeader} or + * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that + * needs to change its position or visibility. + */ + void configurePinnedHeaders(PinnedHeaderListView listView); + + /** + * Returns the list position to scroll to if the pinned header is touched. + * Return -1 if the list does not need to be scrolled. + */ + int getScrollPositionForHeader(int viewIndex); + } + + private static final int MAX_ALPHA = 255; + private static final int TOP = 0; + private static final int BOTTOM = 1; + private static final int FADING = 2; + + private static final int DEFAULT_ANIMATION_DURATION = 20; + + private static final int DEFAULT_SMOOTH_SCROLL_DURATION = 100; + + private static final class PinnedHeader { + View view; + boolean visible; + int y; + int height; + int alpha; + int state; + + boolean animating; + boolean targetVisible; + int sourceY; + int targetY; + long targetTime; + } + + private PinnedHeaderAdapter mAdapter; + private int mSize; + private PinnedHeader[] mHeaders; + private RectF mBounds = new RectF(); + private Rect mClipRect = new Rect(); + private OnScrollListener mOnScrollListener; + private OnItemSelectedListener mOnItemSelectedListener; + private int mScrollState; + + private boolean mScrollToSectionOnHeaderTouch = false; + private boolean mHeaderTouched = false; + + private int mAnimationDuration = DEFAULT_ANIMATION_DURATION; + private boolean mAnimating; + private long mAnimationTargetTime; + private int mHeaderPaddingStart; + private int mHeaderWidth; + + public PinnedHeaderListView(Context context) { + this(context, null, android.R.attr.listViewStyle); + } + + public PinnedHeaderListView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.listViewStyle); + } + + public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + super.setOnScrollListener(this); + super.setOnItemSelectedListener(this); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mHeaderPaddingStart = getPaddingStart(); + mHeaderWidth = r - l - mHeaderPaddingStart - getPaddingEnd(); + } + + public void setPinnedHeaderAnimationDuration(int duration) { + mAnimationDuration = duration; + } + + @Override + public void setAdapter(ListAdapter adapter) { + mAdapter = (PinnedHeaderAdapter)adapter; + super.setAdapter(adapter); + } + + @Override + public void setOnScrollListener(OnScrollListener onScrollListener) { + mOnScrollListener = onScrollListener; + super.setOnScrollListener(this); + } + + @Override + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + mOnItemSelectedListener = listener; + super.setOnItemSelectedListener(this); + } + + public void setScrollToSectionOnHeaderTouch(boolean value) { + mScrollToSectionOnHeaderTouch = value; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + if (mAdapter != null) { + int count = mAdapter.getPinnedHeaderCount(); + if (count != mSize) { + mSize = count; + if (mHeaders == null) { + mHeaders = new PinnedHeader[mSize]; + } else if (mHeaders.length < mSize) { + PinnedHeader[] headers = mHeaders; + mHeaders = new PinnedHeader[mSize]; + System.arraycopy(headers, 0, mHeaders, 0, headers.length); + } + } + + for (int i = 0; i < mSize; i++) { + if (mHeaders[i] == null) { + mHeaders[i] = new PinnedHeader(); + } + mHeaders[i].view = mAdapter.getPinnedHeaderView(i, mHeaders[i].view, this); + } + + mAnimationTargetTime = System.currentTimeMillis() + mAnimationDuration; + mAdapter.configurePinnedHeaders(this); + invalidateIfAnimating(); + + } + if (mOnScrollListener != null) { + mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount); + } + } + + @Override + protected float getTopFadingEdgeStrength() { + // Disable vertical fading at the top when the pinned header is present + return mSize > 0 ? 0 : super.getTopFadingEdgeStrength(); + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + mScrollState = scrollState; + if (mOnScrollListener != null) { + mOnScrollListener.onScrollStateChanged(this, scrollState); + } + } + + /** + * Ensures that the selected item is positioned below the top-pinned headers + * and above the bottom-pinned ones. + */ + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + int height = getHeight(); + + int windowTop = 0; + int windowBottom = height; + + for (int i = 0; i < mSize; i++) { + PinnedHeader header = mHeaders[i]; + if (header.visible) { + if (header.state == TOP) { + windowTop = header.y + header.height; + } else if (header.state == BOTTOM) { + windowBottom = header.y; + break; + } + } + } + + View selectedView = getSelectedView(); + if (selectedView != null) { + if (selectedView.getTop() < windowTop) { + setSelectionFromTop(position, windowTop); + } else if (selectedView.getBottom() > windowBottom) { + setSelectionFromTop(position, windowBottom - selectedView.getHeight()); + } + } + + if (mOnItemSelectedListener != null) { + mOnItemSelectedListener.onItemSelected(parent, view, position, id); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + if (mOnItemSelectedListener != null) { + mOnItemSelectedListener.onNothingSelected(parent); + } + } + + public int getPinnedHeaderHeight(int viewIndex) { + ensurePinnedHeaderLayout(viewIndex); + return mHeaders[viewIndex].view.getHeight(); + } + + /** + * Set header to be pinned at the top. + * + * @param viewIndex index of the header view + * @param y is position of the header in pixels. + * @param animate true if the transition to the new coordinate should be animated + */ + public void setHeaderPinnedAtTop(int viewIndex, int y, boolean animate) { + ensurePinnedHeaderLayout(viewIndex); + PinnedHeader header = mHeaders[viewIndex]; + header.visible = true; + header.y = y; + header.state = TOP; + + // TODO perhaps we should animate at the top as well + header.animating = false; + } + + /** + * Set header to be pinned at the bottom. + * + * @param viewIndex index of the header view + * @param y is position of the header in pixels. + * @param animate true if the transition to the new coordinate should be animated + */ + public void setHeaderPinnedAtBottom(int viewIndex, int y, boolean animate) { + ensurePinnedHeaderLayout(viewIndex); + PinnedHeader header = mHeaders[viewIndex]; + header.state = BOTTOM; + if (header.animating) { + header.targetTime = mAnimationTargetTime; + header.sourceY = header.y; + header.targetY = y; + } else if (animate && (header.y != y || !header.visible)) { + if (header.visible) { + header.sourceY = header.y; + } else { + header.visible = true; + header.sourceY = y + header.height; + } + header.animating = true; + header.targetVisible = true; + header.targetTime = mAnimationTargetTime; + header.targetY = y; + } else { + header.visible = true; + header.y = y; + } + } + + /** + * Set header to be pinned at the top of the first visible item. + * + * @param viewIndex index of the header view + * @param position is position of the header in pixels. + */ + public void setFadingHeader(int viewIndex, int position, boolean fade) { + ensurePinnedHeaderLayout(viewIndex); + + View child = getChildAt(position - getFirstVisiblePosition()); + if (child == null) return; + + PinnedHeader header = mHeaders[viewIndex]; + header.visible = true; + header.state = FADING; + header.alpha = MAX_ALPHA; + header.animating = false; + + int top = getTotalTopPinnedHeaderHeight(); + header.y = top; + if (fade) { + int bottom = child.getBottom() - top; + int headerHeight = header.height; + if (bottom < headerHeight) { + int portion = bottom - headerHeight; + header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight; + header.y = top + portion; + } + } + } + + /** + * Makes header invisible. + * + * @param viewIndex index of the header view + * @param animate true if the transition to the new coordinate should be animated + */ + public void setHeaderInvisible(int viewIndex, boolean animate) { + PinnedHeader header = mHeaders[viewIndex]; + if (header.visible && (animate || header.animating) && header.state == BOTTOM) { + header.sourceY = header.y; + if (!header.animating) { + header.visible = true; + header.targetY = getBottom() + header.height; + } + header.animating = true; + header.targetTime = mAnimationTargetTime; + header.targetVisible = false; + } else { + header.visible = false; + } + } + + private void ensurePinnedHeaderLayout(int viewIndex) { + View view = mHeaders[viewIndex].view; + if (view.isLayoutRequested()) { + int widthSpec = View.MeasureSpec.makeMeasureSpec(mHeaderWidth, View.MeasureSpec.EXACTLY); + int heightSpec; + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams != null && layoutParams.height > 0) { + heightSpec = View.MeasureSpec + .makeMeasureSpec(layoutParams.height, View.MeasureSpec.EXACTLY); + } else { + heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + } + view.measure(widthSpec, heightSpec); + int height = view.getMeasuredHeight(); + mHeaders[viewIndex].height = height; + view.layout(0, 0, mHeaderWidth, height); + } + } + + /** + * Returns the sum of heights of headers pinned to the top. + */ + public int getTotalTopPinnedHeaderHeight() { + for (int i = mSize; --i >= 0;) { + PinnedHeader header = mHeaders[i]; + if (header.visible && header.state == TOP) { + return header.y + header.height; + } + } + return 0; + } + + /** + * Returns the list item position at the specified y coordinate. + */ + public int getPositionAt(int y) { + do { + int position = pointToPosition(getPaddingLeft() + 1, y); + if (position != -1) { + return position; + } + // If position == -1, we must have hit a separator. Let's examine + // a nearby pixel + y--; + } while (y > 0); + return 0; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + mHeaderTouched = false; + if (super.onInterceptTouchEvent(ev)) { + return true; + } + + if (mScrollState == SCROLL_STATE_IDLE) { + final int y = (int)ev.getY(); + final int x = (int)ev.getX(); + for (int i = mSize; --i >= 0;) { + PinnedHeader header = mHeaders[i]; + // For RTL layouts, this also takes into account that the scrollbar is on the left + // side. + final int padding = getPaddingLeft(); + if (header.visible && header.y <= y && header.y + header.height > y && + x >= padding && padding + mHeaderWidth >= x) { + mHeaderTouched = true; + if (mScrollToSectionOnHeaderTouch && + ev.getAction() == MotionEvent.ACTION_DOWN) { + return smoothScrollToPartition(i); + } else { + return true; + } + } + } + } + + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mHeaderTouched) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + mHeaderTouched = false; + } + return true; + } + return super.onTouchEvent(ev); + }; + + private boolean smoothScrollToPartition(int partition) { + final int position = mAdapter.getScrollPositionForHeader(partition); + if (position == -1) { + return false; + } + + int offset = 0; + for (int i = 0; i < partition; i++) { + PinnedHeader header = mHeaders[i]; + if (header.visible) { + offset += header.height; + } + } + smoothScrollToPositionFromTop(position + getHeaderViewsCount(), offset, + DEFAULT_SMOOTH_SCROLL_DURATION); + return true; + } + + private void invalidateIfAnimating() { + mAnimating = false; + for (int i = 0; i < mSize; i++) { + if (mHeaders[i].animating) { + mAnimating = true; + invalidate(); + return; + } + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + long currentTime = mAnimating ? System.currentTimeMillis() : 0; + + int top = 0; + int bottom = getBottom(); + boolean hasVisibleHeaders = false; + for (int i = 0; i < mSize; i++) { + PinnedHeader header = mHeaders[i]; + if (header.visible) { + hasVisibleHeaders = true; + if (header.state == BOTTOM && header.y < bottom) { + bottom = header.y; + } else if (header.state == TOP || header.state == FADING) { + int newTop = header.y + header.height; + if (newTop > top) { + top = newTop; + } + } + } + } + + if (hasVisibleHeaders) { + canvas.save(); + mClipRect.set(0, top, getWidth(), bottom); + canvas.clipRect(mClipRect); + } + + super.dispatchDraw(canvas); + + if (hasVisibleHeaders) { + canvas.restore(); + + // First draw top headers, then the bottom ones to handle the Z axis correctly + for (int i = mSize; --i >= 0;) { + PinnedHeader header = mHeaders[i]; + if (header.visible && (header.state == TOP || header.state == FADING)) { + drawHeader(canvas, header, currentTime); + } + } + + for (int i = 0; i < mSize; i++) { + PinnedHeader header = mHeaders[i]; + if (header.visible && header.state == BOTTOM) { + drawHeader(canvas, header, currentTime); + } + } + } + + invalidateIfAnimating(); + } + + private void drawHeader(Canvas canvas, PinnedHeader header, long currentTime) { + if (header.animating) { + int timeLeft = (int)(header.targetTime - currentTime); + if (timeLeft <= 0) { + header.y = header.targetY; + header.visible = header.targetVisible; + header.animating = false; + } else { + header.y = header.targetY + (header.sourceY - header.targetY) * timeLeft + / mAnimationDuration; + } + } + if (header.visible) { + View view = header.view; + int saveCount = canvas.save(); + canvas.translate(isLayoutRtl() ? + getWidth() - mHeaderPaddingStart - mHeaderWidth : mHeaderPaddingStart, + header.y); + if (header.state == FADING) { + mBounds.set(0, 0, mHeaderWidth, view.getHeight()); + canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG); + } + view.draw(canvas); + canvas.restoreToCount(saveCount); + } + } + + /** + * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api. + */ + public boolean isLayoutRtl() { + return (getLayoutDirection() == LAYOUT_DIRECTION_RTL); + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java new file mode 100644 index 000000000..aa2f45171 --- /dev/null +++ b/src/com/android/launcher3/list/SettingsPinnedHeaderAdapter.java @@ -0,0 +1,307 @@ +package com.android.launcher3.list; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Typeface; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.TextView; +import com.android.launcher3.Launcher; +import com.android.launcher3.OverviewSettingsPanel; +import com.android.launcher3.R; +import com.android.launcher3.settings.SettingsProvider; + +public class SettingsPinnedHeaderAdapter extends PinnedHeaderListAdapter { + private Launcher mLauncher; + private Context mContext; + + class SettingsPosition { + int partition = 0; + int position = 0; + + SettingsPosition (int partition, int position) { + this.partition = partition; + this.position = position; + } + } + + public SettingsPinnedHeaderAdapter(Context context) { + super(context); + mLauncher = (Launcher) context; + mContext = context; + } + + private String[] mHeaders; + public int mPinnedHeaderCount; + + public void setHeaders(String[] headers) { + this.mHeaders = headers; + } + + @Override + protected View newHeaderView(Context context, int partition, Cursor cursor, + ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(R.layout.settings_pane_list_header, null); + } + + @Override + protected void bindHeaderView(View view, int partition, Cursor cursor) { + TextView textView = (TextView) view.findViewById(R.id.item_name); + textView.setText(mHeaders[partition]); + textView.setTypeface(textView.getTypeface(), Typeface.BOLD); + + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + } + + @Override + protected View newView(Context context, int partition, Cursor cursor, int position, + ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(R.layout.settings_pane_list_item, null); + } + + @Override + protected void bindView(View v, int partition, Cursor cursor, int position) { + TextView text = (TextView)v.findViewById(R.id.item_name); + // RTL + Configuration config = mLauncher.getResources().getConfiguration(); + if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + text.setGravity(Gravity.RIGHT); + } + + String title = cursor.getString(1); + text.setText(title); + + v.setTag(new SettingsPosition(partition, position)); + + Resources res = mLauncher.getResources(); + + + boolean current = false; + String state = ""; + + switch (partition) { + case OverviewSettingsPanel.HOME_SETTINGS_POSITION: + switch (position) { + /*case 0: + current = mLauncher.isSearchBarEnabled(); + state = current ? res.getString(R.string.setting_state_on) + : res.getString(R.string.setting_state_off); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + break;*/ + case 1: + current = SettingsProvider.getBoolean(mContext, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default); + state = current ? res.getString(R.string.icon_labels_hide) + : res.getString(R.string.icon_labels_show); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + break; + case 2: + current = SettingsProvider.getBoolean(mContext, + SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL, + R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default); + state = current ? res.getString(R.string.setting_state_on) + : res.getString(R.string.setting_state_off); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + break; + /*case 3: + updateDynamicGridSizeSettingsItem(v); + break;*/ + default: + ((TextView) v.findViewById(R.id.item_state)).setText(""); + } + break; + case OverviewSettingsPanel.DRAWER_SETTINGS_POSITION: + switch (position) { + case 0: + current = SettingsProvider.getBoolean(mContext, + SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS, + R.bool.preferences_interface_drawer_hide_icon_labels_default); + state = current ? res.getString(R.string.icon_labels_hide) + : res.getString(R.string.icon_labels_show); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + break; + default: + ((TextView) v.findViewById(R.id.item_state)).setText(""); + } + break; + default: + switch (position) { + case 0: + current = SettingsProvider.getBoolean(mContext, + SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE, + R.bool.preferences_interface_general_icons_large_default); + state = current ? res.getString(R.string.setting_state_on) + : res.getString(R.string.setting_state_off); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + break; + default: + ((TextView) v.findViewById(R.id.item_state)).setText(""); + } + } + + v.setOnClickListener(mSettingsItemListener); + } + + @Override + public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View view = inflater.inflate(R.layout.settings_pane_list_header, parent, false); + view.setFocusable(false); + view.setEnabled(false); + bindHeaderView(view, viewIndex, null); + return view; + } + + @Override + public int getPinnedHeaderCount() { + return mPinnedHeaderCount; + } + + /*public void updateDynamicGridSizeSettingsItem(View v) { + DeviceProfile.GridSize gridSize = DeviceProfile.GridSize.getModeForValue( + SettingsProvider.getIntCustomDefault(mLauncher, + SettingsProvider.SETTINGS_UI_DYNAMIC_GRID_SIZE, 0)); + String state = ""; + + switch (gridSize) { + case Comfortable: + state = mLauncher.getResources().getString(R.string.grid_size_comfortable); + break; + case Cozy: + state = mLauncher.getResources().getString(R.string.grid_size_cozy); + break; + case Condensed: + state = mLauncher.getResources().getString(R.string.grid_size_condensed); + break; + case Custom: + int rows = SettingsProvider.getIntCustomDefault(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_ROWS, 0); + int columns = SettingsProvider.getIntCustomDefault(mLauncher, + SettingsProvider.SETTINGS_UI_HOMESCREEN_COLUMNS, 0); + state = rows + " " + "\u00d7" + " " + columns; + break; + } + ((TextView) v.findViewById(R.id.item_state)).setText(state); + }*/ + + OnClickListener mSettingsItemListener = new OnClickListener() { + + @Override + public void onClick(View v) { + int partition = ((SettingsPosition) v.getTag()).partition; + int position = ((SettingsPosition) v.getTag()).position; + + switch (partition) { + case OverviewSettingsPanel.HOME_SETTINGS_POSITION: + switch (position) { + /*case 0: + updateSearchBarVisibility(v); + mLauncher.setUpdateDynamicGrid(false); + break;*/ + case 1: + onIconLabelsBooleanChanged(v, + SettingsProvider.SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS, + R.bool.preferences_interface_homescreen_hide_icon_labels_default); + mLauncher.reloadLauncher(); + break; + case 2: + onSettingsBooleanChanged(v, + SettingsProvider.SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL, + R.bool.preferences_interface_homescreen_scrolling_wallpaper_scroll_default); + mLauncher.reloadLauncher(); + break; + /*case 3: + mLauncher.onClickDynamicGridSizeButton(); + break;*/ + } + break; + case OverviewSettingsPanel.DRAWER_SETTINGS_POSITION: + switch (position) { + case 0: + onIconLabelsBooleanChanged(v, + SettingsProvider.SETTINGS_UI_DRAWER_HIDE_ICON_LABELS, + R.bool.preferences_interface_drawer_hide_icon_labels_default); + mLauncher.reloadLauncher(); + break; + } + break; + default: + switch (position) { + case 0: + onSettingsBooleanChanged(v, + SettingsProvider.SETTINGS_UI_GENERAL_ICONS_LARGE, + R.bool.preferences_interface_general_icons_large_default); + mLauncher.reloadLauncher(); + break; + /*case 1: + Intent intent = new Intent(); + intent.setClassName(OverviewSettingsPanel.ANDROID_SETTINGS, + OverviewSettingsPanel.ANDROID_PROTECTED_APPS); + mLauncher.startActivity(intent); + break;*/ + } + } + + View defaultHome = mLauncher.findViewById(R.id.default_home_screen_panel); + defaultHome.setVisibility(getCursor(0).getCount() > 1 ? View.VISIBLE : View.GONE); + } + }; + + /*private void updateSearchBarVisibility(View v) { + boolean isSearchEnabled = SettingsProvider.getBoolean(mContext, + SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH, + R.bool.preferences_interface_homescreen_search_default); + + if (!isSearchEnabled) { + if (!Utilities.searchActivityExists(mContext)) { + Toast.makeText(mContext, mContext.getString(R.string.search_activity_not_found), + Toast.LENGTH_SHORT).show(); + return; + } + } + + onSettingsBooleanChanged(v, + SettingsProvider.SETTINGS_UI_HOMESCREEN_SEARCH, + R.bool.preferences_interface_homescreen_search_default); + }*/ + + private void onSettingsBooleanChanged(View v, String key, int res) { + boolean current = SettingsProvider.getBoolean( + mContext, key, res); + + // Set new state + SettingsProvider.putBoolean(mContext, key, !current); + SettingsProvider.putBoolean(mContext, SettingsProvider.SETTINGS_CHANGED, true); + + String state = current ? mLauncher.getResources().getString( + R.string.setting_state_off) : mLauncher.getResources().getString( + R.string.setting_state_on); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + } + + private void onIconLabelsBooleanChanged(View v, String key, int res) { + boolean current = SettingsProvider.getBoolean( + mContext, key, res); + + // Set new state + SettingsProvider.putBoolean(mContext, key, !current); + SettingsProvider.putBoolean(mContext, SettingsProvider.SETTINGS_CHANGED, true); + + String state = current ? mLauncher.getResources().getString( + R.string.icon_labels_show) : mLauncher.getResources().getString( + R.string.icon_labels_hide); + ((TextView) v.findViewById(R.id.item_state)).setText(state); + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/settings/SettingsProvider.java b/src/com/android/launcher3/settings/SettingsProvider.java new file mode 100644 index 000000000..a90478cdf --- /dev/null +++ b/src/com/android/launcher3/settings/SettingsProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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.settings; + +import android.content.Context; +import android.content.SharedPreferences; + +public final class SettingsProvider { + public static final String SETTINGS_KEY = "trebuchet_preferences"; + public static final String SETTINGS_CHANGED = "settings_changed"; + + public static final String SETTINGS_UI_HOMESCREEN_DEFAULT_SCREEN_ID = "ui_homescreen_default_screen_id"; + public static final String SETTINGS_UI_HOMESCREEN_SEARCH = "ui_homescreen_search"; + public static final String SETTINGS_UI_HOMESCREEN_HIDE_ICON_LABELS = "ui_homescreen_general_hide_icon_labels"; + public static final String SETTINGS_UI_HOMESCREEN_SCROLLING_WALLPAPER_SCROLL = "ui_homescreen_scrolling_wallpaper_scroll"; + public static final String SETTINGS_UI_DYNAMIC_GRID_SIZE = "ui_dynamic_grid_size"; + public static final String SETTINGS_UI_HOMESCREEN_ROWS = "ui_homescreen_rows"; + public static final String SETTINGS_UI_HOMESCREEN_COLUMNS = "ui_homescreen_columns"; + public static final String SETTINGS_UI_DRAWER_HIDE_ICON_LABELS = "ui_drawer_hide_icon_labels"; + public static final String SETTINGS_UI_DRAWER_COMPACT = "ui_drawer_compact"; + public static final String SETTINGS_UI_GENERAL_ICONS_LARGE = "ui_general_icons_large"; + + public static SharedPreferences get(Context context) { + return context.getSharedPreferences(SETTINGS_KEY, Context.MODE_PRIVATE); + } + + public static int getIntCustomDefault(Context context, String key, int def) { + return get(context).getInt(key, def); + } + + public static int getInt(Context context, String key, int resource) { + return getIntCustomDefault(context, key, context.getResources().getInteger(resource)); + } + + public static long getLongCustomDefault(Context context, String key, long def) { + return get(context).getLong(key, def); + } + + public static long getLong(Context context, String key, int resource) { + return getLongCustomDefault(context, key, context.getResources().getInteger(resource)); + } + + public static boolean getBooleanCustomDefault(Context context, String key, boolean def) { + return get(context).getBoolean(key, def); + } + + public static boolean getBoolean(Context context, String key, int resource) { + return getBooleanCustomDefault(context, key, context.getResources().getBoolean(resource)); + } + + public static String getStringCustomDefault(Context context, String key, String def) { + return get(context).getString(key, def); + } + + public static String getString(Context context, String key, int resource) { + return getStringCustomDefault(context, key, context.getResources().getString(resource)); + } + + public static void putString(Context context, String key, String value) { + get(context).edit().putString(key, value).commit(); + } + + public static void putInt(Context context, String key, int value) { + get(context).edit().putInt(key, value).commit(); + } + + public static void putBoolean(Context context, String key, boolean value) { + get(context).edit().putBoolean(key, value).commit(); + } +} diff --git a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java index 1bc7c1190..dc3e05e9d 100644 --- a/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java +++ b/tests/src/com/android/launcher3/InvariantDeviceProfileTest.java @@ -42,7 +42,7 @@ public class InvariantDeviceProfileTest extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); mInvariantProfile = new InvariantDeviceProfile(getContext()); - mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles(); + mPredefinedDeviceProfiles = mInvariantProfile.getPredefinedDeviceProfiles(getContext()); } @Override -- 2.11.0