From e677d7113f6627161653f686b6381d2eef4d502f Mon Sep 17 00:00:00 2001 From: John Spurlock Date: Thu, 13 Feb 2014 12:52:19 -0500 Subject: [PATCH] Introduce zen mode. - New global zen mode setting with three values: ZEN_MODE_OFF, ZEN_MODE_LIMITED, ZEN_MODE_FULL - Status bar icon (on lhs) when in _LIMITED / _FULL - Rules are hardcoded for now: _LIMITED = intercept all notifications except calls/alarms _FULL = intercept everything - UI affordance in shade to set mode - Only one exit condition: "Until you delete this" - Intercepted notifications: - appear darker in the shade - do not cause sound/vibration - do not fire fullScreenIntents - do not contribute an icon to the status bar Change-Id: Ic36c95982b483432006bf9dab102a31370382aa9 --- core/java/android/provider/Settings.java | 18 + ...spinner_default_holo_dark_am_no_underline.9.png | Bin 0 -> 408 bytes .../res/drawable-hdpi/stat_sys_zen_full.png | Bin 0 -> 745 bytes .../res/drawable-hdpi/stat_sys_zen_limited.png | Bin 0 -> 614 bytes ...spinner_default_holo_dark_am_no_underline.9.png | Bin 0 -> 293 bytes .../res/drawable-mdpi/stat_sys_zen_full.png | Bin 0 -> 522 bytes .../res/drawable-mdpi/stat_sys_zen_limited.png | Bin 0 -> 444 bytes ...spinner_default_holo_dark_am_no_underline.9.png | Bin 0 -> 404 bytes .../res/drawable-xhdpi/stat_sys_zen_full.png | Bin 0 -> 989 bytes .../res/drawable-xhdpi/stat_sys_zen_limited.png | Bin 0 -> 765 bytes ...spinner_default_holo_dark_am_no_underline.9.png | Bin 0 -> 456 bytes .../res/drawable-xxhdpi/stat_sys_zen_full.png | Bin 0 -> 1639 bytes .../res/drawable-xxhdpi/stat_sys_zen_limited.png | Bin 0 -> 1144 bytes packages/SystemUI/res/layout/status_bar.xml | 6 +- .../SystemUI/res/layout/status_bar_expanded.xml | 6 + .../res/layout/status_bar_expanded_header.xml | 4 +- .../android/systemui/statusbar/BaseStatusBar.java | 47 +- .../statusbar/phone/NotificationPanelView.java | 4 + .../systemui/statusbar/phone/PanelHeaderView.java | 70 ++ .../systemui/statusbar/phone/PhoneStatusBar.java | 84 ++- .../systemui/statusbar/phone/ZenModeView.java | 764 +++++++++++++++++++++ .../statusbar/phone/ZenModeViewAdapter.java | 145 ++++ .../server/notification/NotificationDelegate.java | 3 + .../notification/NotificationManagerService.java | 131 +++- .../server/statusbar/StatusBarManagerService.java | 28 +- 25 files changed, 1239 insertions(+), 71 deletions(-) create mode 100644 packages/SystemUI/res/drawable-hdpi/spinner_default_holo_dark_am_no_underline.9.png create mode 100644 packages/SystemUI/res/drawable-hdpi/stat_sys_zen_full.png create mode 100644 packages/SystemUI/res/drawable-hdpi/stat_sys_zen_limited.png create mode 100644 packages/SystemUI/res/drawable-mdpi/spinner_default_holo_dark_am_no_underline.9.png create mode 100644 packages/SystemUI/res/drawable-mdpi/stat_sys_zen_full.png create mode 100644 packages/SystemUI/res/drawable-mdpi/stat_sys_zen_limited.png create mode 100644 packages/SystemUI/res/drawable-xhdpi/spinner_default_holo_dark_am_no_underline.9.png create mode 100644 packages/SystemUI/res/drawable-xhdpi/stat_sys_zen_full.png create mode 100644 packages/SystemUI/res/drawable-xhdpi/stat_sys_zen_limited.png create mode 100644 packages/SystemUI/res/drawable-xxhdpi/spinner_default_holo_dark_am_no_underline.9.png create mode 100644 packages/SystemUI/res/drawable-xxhdpi/stat_sys_zen_full.png create mode 100644 packages/SystemUI/res/drawable-xxhdpi/stat_sys_zen_limited.png create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHeaderView.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 18018e214dfe..40bbbd45ee2c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6082,6 +6082,24 @@ public final class Settings { "lock_screen_show_notifications"; /** + * Defines global zen mode. One of ZEN_MODE_OFF, ZEN_MODE_LIMITED, ZEN_MODE_FULL. + * + * @hide + */ + public static final String ZEN_MODE = "zen_mode"; + + /** @hide */ public static final int ZEN_MODE_OFF = 0; + /** @hide */ public static final int ZEN_MODE_LIMITED = 1; + /** @hide */ public static final int ZEN_MODE_FULL = 2; + + /** @hide */ public static String zenModeToString(int mode) { + if (mode == ZEN_MODE_OFF) return "ZEN_MODE_OFF"; + if (mode == ZEN_MODE_LIMITED) return "ZEN_MODE_LIMITED"; + if (mode == ZEN_MODE_FULL) return "ZEN_MODE_FULL"; + throw new IllegalArgumentException("Invalid zen mode: " + mode); + } + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * diff --git a/packages/SystemUI/res/drawable-hdpi/spinner_default_holo_dark_am_no_underline.9.png b/packages/SystemUI/res/drawable-hdpi/spinner_default_holo_dark_am_no_underline.9.png new file mode 100644 index 0000000000000000000000000000000000000000..267e7bac65e2f611cf946cc362ed90e93cf1df29 GIT binary patch literal 408 zcmV;J0cZY+P)C3<00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3yA;#3yA?Y$;+((000McNliru-U1sBCOQ|kNvZ$<0VPR9 zK~z}7?bu67#6S?n@qdp|$MJm@@hxgp6dimZ9@S%f=W*L^N*E+eNYY71se)dFEb^ls z-JyvwvLxcd>NRQ{!$yQSVMO5=$Fyu@&&>GCCmZ5yLlAYEn@#`O?z>0000jSJTkL-SAo>~ zxiRPx21#cg`Ezq-m zr&f`%uld>QfbiWPGyHX(PltZLHF2L{n1q`T*_50icWpesUq(%>G+!RbCbTk&A!efZTMze zVeqVuGw(Vlh zv(Vanl*{?mFZ;fOT+y;+K8IT-t(-nHCUYJ4syed=9og4re%4gmzwxa8t7}=6X$Q{v zbw7=mZ~M&Rb=NA{vS{1QOFI7UU(5Y=tD(60mnYI%Ny+P?Ra~#mIyBpT-Shf6N@lM* z47t-PvVL8@X%i-qcOf^-bARHcC7V?2{QomDYX8b#@!ID%FfFN;xJHzuB$lLFB^RXv zDF!10Lla#CBV8lY5JN*NLjx-#V_gGND+7b~A$hBeaNQPZT4I>6c)p^% z@G))NEYxsnI&svE^{Y~+nB0qrpWkh=_V0@RmUdm(#bf&NS$8Yz zfB&1AevLtnO~8rC`2+)FTVImYm7mVPm<}8dSk|Jr$hBL@Sm#5JXBR_ZtlCVFHdB(_Udg}RqNHABy|*2mb8fbW-4>$Ww>kqS|Yz`(h}{xL5_YC zl@29s*u?W$qT#;I<}-$hoA*4?G&rHW-BNx7$0RPR!#AJl-er%4B zI)0w=G}>u4J7$UenZ(LgmY2IuNeUFcW&2^PFu!tJMCfUS6(9SwO0RWPFB6z`ZKaB4 zm}~c}pao4mHmq8wcCaVJ^kmu@&9sU8+{^Vl#A1o#>=V4-c6v9T`8db>+|Q$oyK{=B zd_DPA>2uaAr~D`7-CuU}|B^UA@9y6XOTM-z{5k%Qb(-n)*J0fTGp=;Fn!B1s_u4Kh zSw7(p7h~O~=W%m#wrP1^nXw}@_(;0lj)-HInwb9D=Cx*AI>T!3QqOqf{uQNV*OgBL zBT}`*HKHUXu_Vl&C^85q0|$pgkVl7`&; al+3hB+#2R_URw{;z~JfX=d#Wzp$Py(g7v=u literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-mdpi/spinner_default_holo_dark_am_no_underline.9.png b/packages/SystemUI/res/drawable-mdpi/spinner_default_holo_dark_am_no_underline.9.png new file mode 100644 index 0000000000000000000000000000000000000000..db51f6b6b551e2b7199345cb9c93354bd863a867 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^5{XE)7O>#9+Nb`8qW=$J0P8#JY5_^JdVGe?#RVt$m1H{7P)J- z@QX`VV~(}HIQE;(Cg{$U)=P~lWfu4?bN<_lbn z_RAeycJ=fY=|8=VtGZUng&78fGWH!B1wCN#P$ z-(Y^uaVKT}})*0O_BPCl@i7R>anD?#srW_A3r*17h}zZ=zUdHj9Z`&Gfl%oC@7 jUm3D|pK<-~#uLn!BCL7S-`)@hx}L$))z4*}Q$iB}PWy4? literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_zen_full.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_zen_full.png new file mode 100644 index 0000000000000000000000000000000000000000..b0185a54d1da058036839e94dd0d28e350e34a97 GIT binary patch literal 522 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mn3BBRT^Rni_n+Ah4nJ za0`PlBg3pY5)2HCKAtX)AsXjF{q}ybyOC~I6EBvPMG4^@-oEHfltgkM_`%QB+l9`m! z&Nz$Npn*r}sL6GanTb3b>JkLLA5ixFa=BT#>uBL?do_zR=EGasciC)o z@moE0gXO@$RxNRjC`m~yNwrEYN(E93Mh1o^x&}tNMy4T#hE|3KRz}9U2Bua92Jb`i hfH8ukAvZrIGp!Q0hIyRV)&n&#c)I$ztaD0e0suRe%WePw literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_zen_limited.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_zen_limited.png new file mode 100644 index 0000000000000000000000000000000000000000..949ab10ca2e04ba6ed2fe096a6dbadf680f0a3ba GIT binary patch literal 444 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mn3BBRT^JZv^(q?yd7K3vk;M!Q z+`=Ht$S`Y;1W@pwr;B5V#`&WY&uX}87OqtDdqH@tu(d%=HqHPlItX%MX-czL?=R;O3x82`;?1y>auF^eM^AE~bdipX6 z?DRe=a(+%yYh~B1_?ydPb$(3t*HrI~*{ypf78nw$C9V-ADTyViR>?)FK#IZ0z|ch3 zz)07~G{n%*%Fw{d$XM6F)XKo%eMlZK43RYC=BH$)RpQn#kMr7kpaup{S3j3^P6{XE)7O>#9+Nb`s?G`j^%e{ajEbHvjv*QM-p(@2Vm1_To&Uzx z!AiVLmLs!hq6OpQ#XCRFd?vB;_>zsD1&J-?Zc#J;CrU4T^ihIWDr|?f=*QXXv$bYU z7iW(*%wW+gXymq8s(Wh3f%&_Ri7s6v{HlN}<^hxVjsvV9sh9^J4L47pReQOqfqMhv zw*!?bkHcT|EB5?1u4TB__*KKS&NPpl&w10kmw)J7KHKX)^If9N zkrLy_7nem#@te-gvpHSD{aAC^<(~A}=S=2hS+V{xVyTH+x9s&9VS84m{6^&u*A@g% z)!eXbf8+IuY3I~TWy7@A?s9MYsJAR#(tNY*hpXJXr0$%_n_kxteBLqi|EFlT_k3VS u{&^qw`dz@OyW88BW@&EozkmP1Q*jk$@7-w)wJU(($KdJe=d#Wzp$PyAu&E{h literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_zen_full.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_zen_full.png new file mode 100644 index 0000000000000000000000000000000000000000..7f7cb6380377f0fa27f1bd229f4e736dcd6d069f GIT binary patch literal 989 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8JTQNwKEA%Qy=u3FhD=Ffo|qRS z5_{#=lqRv*l~>ksI~MB3immQg7qcVaLhA}vQKh!5=37ESN2NTq&oVDERk<>&+}Qa0 zd!@;SGu_pG-aX6nzj)uB;(yP}^-`;XSNxo!?c^0Dv~kazITkM4ZogNo-nvTrnBMG? zqxx-U*Q=}x6)L?Hq_i>Bu})>>>A=``+r0nodLga$=iwgxo>n7~Ox?W8X|vT{7O%YX z??d7Y-m4<*ogb=RtO$t!W520DO3IcSZw zz5LF)t&WHNUSEB*VeYb+57Q*|#lz1mbG%&j<v zN@!2!3){E3x4L9~_?@N4)th?s{4Jy2)@n6}N#A-9SY0AH_411bX4h_)OI!Df=|ukB zpk8VbmlSoP%iHnk*|;y0S7-lQZklmr1K;KR4d0Sxf8W@XekZE5u(#4b{^coM=?x5T z%f#&OJY_6aJ2Q2e+f=3O)i$~tJJ|k0wldT1B8JTQ0A69tafpHJHyZF()s%Rtn1jkxOzM~#Y)56(8v z+Y;}vzv%~KmDrvioHiB0topW7lDhR(O}eBsy)LsbMe%Gkd4KYmB){kNTJPi|&F%lb zZ+dg)&yAVs&m=isG8%p1^kyv7@o~6(^q^yR(j-Ye!?P@g*SDV)i?29uwKZ-*xSGIW zr9P+2ZZDqsrtLhalRh))%=5j@o$GZz_cXNxBp9CKoFt>(xK*S)?a9WFlnGm=JlVHo z@g-wRm!`!s2ajd=y{OdOo!BRm8XERw?iAPEEBuSRdkV5<@x_WXxkYc=<*_M0cfwQ4 z>=2LQ-q&-EGl)F55I%FJMULUBHD7AN_gucnXOTwlYv@$WnHcB@;-D+89C zWsu|v>pCs-?aL-L|31Ee1p+?j?yyfu>l5NOxw9ze(}e3co>WL%s_#1!^y$jmMQu8# zV|OY{vrcV0qN-;Y=-9VYzqCi&XY&ar*Sbw-&2;0h{*z!)3qDggS+}U>8T0HnL5b7Z z?p}0YFf)5R=b2XNs+&pEilV7^4e3sD{Feyx>Y#UnsqaKc$g+6wEhzl zS;%~W@x^{7*R!+gyHp;nKGbz9n}un1h4}6TjIE#k)Uw;wWJvExJp4Q{N$7K;QA&G! zj_JIF;@Qc}`szm0Xkxq!^403{7+mjC74mLktbA3=OP|jCBo6tqcs_hvb2hCqhGR beoAIqC2kG#IIpb-YGCkm^>bP0l+XkKf_^xg literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-xxhdpi/spinner_default_holo_dark_am_no_underline.9.png b/packages/SystemUI/res/drawable-xxhdpi/spinner_default_holo_dark_am_no_underline.9.png new file mode 100644 index 0000000000000000000000000000000000000000..29fb50f820edc7430f03a8766447f86f48bca622 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^EJ}aFGq}Y|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8pdfpRr>`sfJth%;erfk#*E|^*7(+c>978H@y}fJ5*K8o*5|}^b zvFi4-rWTi%c}Oh@V99yk^VNM3W8jZW#`WC7GjIO$b@{VL(|GYr^>Yn6vpe=I+`zBU zz`(=-B|6UCv$vgDAMDl&7SpIpjWAs7#K8FE&F^N{G%XeZ2L?t|LJ^@md~@z?<_WgP zgeP=MoNkOc8o{-M{g{!$UV~{2FFOzopr0IoolnE(I) literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_zen_full.png b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_zen_full.png new file mode 100644 index 0000000000000000000000000000000000000000..afe85b4ce0bf60a894f717de4a249d69fc11ac8b GIT binary patch literal 1639 zcmV-t2AKJYP)^W;9Z|CdbCc z+FM&&pEI*vz&iq7Yq<^V0loxInAy!rO)=|BF2JVybk3luscD6o?F3!}T1vbl0ZakO zz}hjO9%v{suSM%m0oQLd9lLJr#zak{k3qquj6P(nc?Ez5uBEPLz3m9yqKZ z?X{xRNQLvJi)`YSHd}6B9g$hys%5OG6j(94pA@KxGS|8(Kb$OJdRECkD+OjHo>_^% z37X>+a9yFm@~j`7PmL9m$3z56W-%vgz9V@o;}2ya4-}XwMr{Mu&thIe)PFGGn^V9E z;DF*?)!BWu(gwT$+;n$IN~mdIRDpgt48S6)2)0UtNq0Xf z4J6DgRe<)dyZfNBmE3290azB?GGMz*@&vFSI0lTkyXD(>r6A@LSeDJ&G?oIbK)1X1 zi!AN} z_0R*0seH+nFb&mK&~qWdsyts}8!aQJoR;Q71#D8e-c_Yd*|tN2OztW5K+5W4K4@UB zEMpA#6Zkro-M>|9pqES|bqnR4(##vM zJK9{I%x{gD?~J-oQJLHuidCECQ@4Q6RcLmKOhN@LsoZW<6utv^6Zj1H5jZ4P%z|qn za|G2T&?7S6WY~NxtyncJKiMG0OGzt-fujP^47@5bS*+ML9f)$P0(XG> zCogZ+I)2Rsuxv(ACrTgF=IenT`QQwhLyF_lRGsRwl(g7Ts|HgofyGpYw8~#DtK{gG zKkeZD;o>3Sd*ENCTS-;f9a<+;z_NZbAT9KgIqCd>%u!KHKDR)cI|`gOv)k?-176hp z=CA=TOtigH4A}y_T%ge0P)7Qc{Sfm4EL~>mvp_lF#v-kO zQj!AV?jAL>nYjiwsnTOri8ZWBb5Dt6hGzq+8eoGe`J>WE{T%q}6q!T%y((+w#S{mC zkEOY0`ARMGJqIoVUy=EjqtST*rlX)Cci*qPaFs})7MXb@*gD7-t(CA_<)76(s*e+y l1C6DD)NFn0kvx)+$^VD=Th6?{wORlG002ovPDHLkV1h%&08an_ literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-xxhdpi/stat_sys_zen_limited.png b/packages/SystemUI/res/drawable-xxhdpi/stat_sys_zen_limited.png new file mode 100644 index 0000000000000000000000000000000000000000..5e5053fb00e05ac2fe7afbce4eeabb0ab67d3e3e GIT binary patch literal 1144 zcmV-;1c&>HP)flTf%vB-T1 zm;eeu&g)uU8nF^S2SCyp;CV0408avQz+?|&*ahwYKLg(bSAnZ$_Uj;E6A91MLpwRp zEbuh&EN~W>2TlP~ZTfCEhAD3c@Ca~H(sf|j%+?3h-X!oaFasO|X8k(j&nJN*kY89> z$W^P=qNGJhmn2mrwInsYwA7!(+g7~aMM=d$P4PjWy}X-ZC`~U7zt%T5H)qP_@(X6R z2)yCo!>AL_t037Mi z?x^=)1O5b3gEjrdaj+wX>$v{+{VU zf`=Tg6*I*UOoc*0^?E(@$Kzds%OAiuz~>IP3hcSbp8$RW)_@m*$7600mDw(u?FlTZ zz4$;?uh$i6r!JcM7-9viR%vT-kE8uP5tqvyL>@iYz^fB7ZT)g94hd#G~Yrq%4 zH8V>ZR0F6=`U^PYsp7ncdQzsKt^-s;>2((6+PDndXan`169x4*fX_ThYgi#Qm$Kb{ zhe2%nV0%rh6*tT6VdjdoV4duK&!KjE>}$*YaIcN&S(>iKTqulqq| z8h9Dlk_2$s%yuJ797~9nP2fx57Vv>Dq4t3dPxR~K0G9I=!K<#px}} zuWRAGq#ZM>k0V%IitdAR33b1@7FYa!8TdLO$BiXeCpqQ*c+OV=KRVQX4_5HyOSptN zl4b>+WBg^`HFxO1hPKc=6q12uU>R5jmVsqp8CV9Efn{JBShj3Aq#EmaH)RMiLHCmh ziH*`oi{N-eFD4Xj*eC6I<$IW*22hlgH?!tJgY6Ly7zdhGDese$I{=NffJVQ*l}RRJCjSDfXsF3Ux2pUA0000< KMNUMnLSTZzJqx!0 literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index eb6690839a30..ea6be1b96149 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -62,7 +62,11 @@ android:src="@drawable/stat_notify_more" android:visibility="gone" /> - + + + - - + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 7ff52de0c1b0..5f62554ef34f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -104,6 +104,9 @@ public abstract class BaseStatusBar extends SystemUI implements public static final int EXPANDED_LEAVE_ALONE = -10000; public static final int EXPANDED_FULL_OPEN = -10001; + private static final String EXTRA_INTERCEPT = "android.intercept"; + private static final float INTERCEPTED_ALPHA = .2f; + protected CommandQueue mCommandQueue; protected IStatusBarService mBarService; protected H mHandler = createHandler(); @@ -155,6 +158,8 @@ public abstract class BaseStatusBar extends SystemUI implements private RecentsComponent mRecents; + protected int mZenMode; + public IStatusBarService getStatusBarService() { return mBarService; } @@ -163,7 +168,7 @@ public abstract class BaseStatusBar extends SystemUI implements return mDeviceProvisioned; } - private final ContentObserver mProvisioningObserver = new ContentObserver(mHandler) { + protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { final boolean provisioned = 0 != Settings.Global.getInt( @@ -172,6 +177,9 @@ public abstract class BaseStatusBar extends SystemUI implements mDeviceProvisioned = provisioned; updateNotificationIcons(); } + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + setZenMode(mode); } }; @@ -239,10 +247,13 @@ public abstract class BaseStatusBar extends SystemUI implements ServiceManager.checkService(DreamService.DREAM_SERVICE)); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mProvisioningObserver.onChange(false); // set up + mSettingsObserver.onChange(false); // set up mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, - mProvisioningObserver); + mSettingsObserver); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, + mSettingsObserver); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), @@ -980,6 +991,7 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Log.d(TAG, "addNotificationViews: added at " + pos); } + updateInterceptedState(entry); updateExpansionStates(); updateNotificationIcons(); } @@ -1010,6 +1022,34 @@ public abstract class BaseStatusBar extends SystemUI implements } } + protected void setZenMode(int mode) { + final boolean change = mZenMode != mode; + mZenMode = mode; + final int N = mNotificationData.size(); + for (int i = 0; i < N; i++) { + final NotificationData.Entry entry = mNotificationData.get(i); + if (change && !shouldIntercept()) { + entry.notification.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false); + } + updateInterceptedState(entry); + } + updateNotificationIcons(); + } + + private boolean shouldIntercept() { + return mZenMode == Settings.Global.ZEN_MODE_LIMITED + || mZenMode == Settings.Global.ZEN_MODE_FULL; + } + + protected boolean shouldIntercept(Notification n) { + return shouldIntercept() && n.extras.getBoolean(EXTRA_INTERCEPT); + } + + private void updateInterceptedState(NotificationData.Entry entry) { + final boolean intercepted = shouldIntercept(entry.notification.getNotification()); + entry.row.findViewById(R.id.container).setAlpha(intercepted ? INTERCEPTED_ALPHA : 1); + } + protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); protected abstract void updateNotificationIcons(); @@ -1202,6 +1242,7 @@ public abstract class BaseStatusBar extends SystemUI implements } else { entry.content.setOnClickListener(null); } + updateInterceptedState(entry); } protected void notifyHeadsUpScreenOn(boolean screenOn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 6be6d4d43a19..ae7440744908 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -56,6 +56,10 @@ public class NotificationPanelView extends PanelView { mHandleBar = resources.getDrawable(R.drawable.status_bar_close); mHandleBarHeight = resources.getDimensionPixelSize(R.dimen.close_handle_height); mHandleView = findViewById(R.id.handle); + PanelHeaderView header = (PanelHeaderView) findViewById(R.id.header); + ZenModeView zenModeView = (ZenModeView) findViewById(R.id.zenmode); + zenModeView.setAdapter( new ZenModeViewAdapter(mContext)); + header.setZenModeView(zenModeView); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHeaderView.java new file mode 100644 index 000000000000..a28324d9cb4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHeaderView.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 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.systemui.statusbar.phone; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.widget.LinearLayout; + +public class PanelHeaderView extends LinearLayout { + private static final String TAG = "PanelHeaderView"; + private static final boolean DEBUG = false; + + private ZenModeView mZenModeView; + + public PanelHeaderView(Context context) { + super(context); + } + + public PanelHeaderView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setZenModeView(ZenModeView zmv) { + mZenModeView = zmv; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final boolean rt = super.dispatchTouchEvent(ev); + if (DEBUG) logTouchEvent("dispatchTouchEvent", rt, ev); + if (mZenModeView != null) { + mZenModeView.dispatchExternalTouchEvent(ev); + } + return rt; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + final boolean rt = super.onInterceptTouchEvent(ev); + if (DEBUG) logTouchEvent("onInterceptTouchEvent", rt, ev); + return rt; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean rt = super.onTouchEvent(event); + if (DEBUG) logTouchEvent("onTouchEvent", rt, event); + return true; + } + + private void logTouchEvent(String method, boolean rt, MotionEvent ev) { + Log.d(TAG, method + " " + (rt ? "TRUE" : "FALSE") + " " + ev); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 211499132741..631a1ae9b3d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -189,6 +189,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { IconMerger mNotificationIcons; // [+> View mMoreIcon; + // mode indicator icon + ImageView mModeIcon; // expanded notifications NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window @@ -341,6 +343,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { }}; @Override + public void setZenMode(int mode) { + super.setZenMode(mode); + if (mModeIcon == null) return; + final boolean limited = mode == Settings.Global.ZEN_MODE_LIMITED; + final boolean full = mode == Settings.Global.ZEN_MODE_FULL; + mModeIcon.setVisibility(full || limited ? View.VISIBLE : View.GONE); + final int icon = limited ? R.drawable.stat_sys_zen_limited : R.drawable.stat_sys_zen_full; + if (full || limited) { + mModeIcon.setImageResource(icon); + } + } + + @Override public void start() { mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); @@ -352,6 +367,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { // Lastly, call to the icon policy to install/update all the icons. mIconPolicy = new PhoneStatusBarPolicy(mContext); + mSettingsObserver.onChange(false); // set up mHeadsUpObserver.onChange(true); // set up if (ENABLE_HEADS_UP) { @@ -455,6 +471,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons); mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon); mNotificationIcons.setOverflowIndicator(mMoreIcon); + mModeIcon = (ImageView)mStatusBarView.findViewById(R.id.modeIcon); mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents); mTickerView = mStatusBarView.findViewById(R.id.ticker); @@ -909,41 +926,43 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { if (shadeEntry == null) { return; } - if (mUseHeadsUp && shouldInterrupt(notification)) { - if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); - Entry interruptionCandidate = new Entry(key, notification, null); - ViewGroup holder = mHeadsUpNotificationView.getHolder(); - if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { - mInterruptingNotificationTime = System.currentTimeMillis(); - mInterruptingNotificationEntry = interruptionCandidate; - shadeEntry.setInterruption(); + if (!shouldIntercept(notification.getNotification())) { + if (mUseHeadsUp && shouldInterrupt(notification)) { + if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); + Entry interruptionCandidate = new Entry(key, notification, null); + ViewGroup holder = mHeadsUpNotificationView.getHolder(); + if (inflateViewsForHeadsUp(interruptionCandidate, holder)) { + mInterruptingNotificationTime = System.currentTimeMillis(); + mInterruptingNotificationEntry = interruptionCandidate; + shadeEntry.setInterruption(); - // 1. Populate mHeadsUpNotificationView - mHeadsUpNotificationView.setNotification(mInterruptingNotificationEntry); + // 1. Populate mHeadsUpNotificationView + mHeadsUpNotificationView.setNotification(mInterruptingNotificationEntry); - // 2. Animate mHeadsUpNotificationView in - mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); + // 2. Animate mHeadsUpNotificationView in + mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); - // 3. Set alarm to age the notification off - resetHeadsUpDecayTimer(); - } - } else if (notification.getNotification().fullScreenIntent != null) { - // Stop screensaver if the notification has a full-screen intent. - // (like an incoming phone call) - awakenDreams(); + // 3. Set alarm to age the notification off + resetHeadsUpDecayTimer(); + } + } else if (notification.getNotification().fullScreenIntent != null) { + // Stop screensaver if the notification has a full-screen intent. + // (like an incoming phone call) + awakenDreams(); - // not immersive & a full-screen alert should be shown - if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); - try { - notification.getNotification().fullScreenIntent.send(); - } catch (PendingIntent.CanceledException e) { - } - } else { - // usual case: status bar visible & not immersive + // not immersive & a full-screen alert should be shown + if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); + try { + notification.getNotification().fullScreenIntent.send(); + } catch (PendingIntent.CanceledException e) { + } + } else { + // usual case: status bar visible & not immersive - // show the ticker if there isn't already a heads up - if (mInterruptingNotificationEntry == null) { - tick(null, notification, true); + // show the ticker if there isn't already a heads up + if (mInterruptingNotificationEntry == null) { + tick(null, notification, true); + } } } addNotificationViews(shadeEntry); @@ -1096,6 +1115,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { // in "public" mode (atop a secure keyguard), secret notifs are totally hidden continue; } + if (shouldIntercept(ent.notification.getNotification())) { + continue; + } toShow.add(ent.icon); } @@ -2183,6 +2205,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { pw.println(windowStateToString(mStatusBarWindowState)); pw.print(" mStatusBarMode="); pw.println(BarTransitions.modeToString(mStatusBarMode)); + pw.print(" mZenMode="); + pw.println(Settings.Global.zenModeToString(mZenMode)); dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); if (mNavigationBarView != null) { pw.print(" mNavigationBarWindowState="); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java new file mode 100644 index 000000000000..a89dacf451f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeView.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2014 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.systemui.statusbar.phone; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.method.LinkMovementMethod; +import android.text.style.RelativeSizeSpan; +import android.text.style.URLSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.ZenModeView.Adapter.ExitCondition; + +public class ZenModeView extends RelativeLayout { + private static final String TAG = ZenModeView.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final Typeface CONDENSED = + Typeface.create("sans-serif-condensed", Typeface.NORMAL); + private static final int GRAY = 0xff999999; //TextAppearance.StatusBar.Expanded.Network + private static final int BACKGROUND = 0xff1d3741; //0x3333b5e5; + private static final long DURATION = new ValueAnimator().getDuration(); + private static final long BOUNCE_DURATION = DURATION / 3; + private static final float BOUNCE_SCALE = 0.8f; + private static final float SETTINGS_ALPHA = 0.6f; + private static final int INFO_WINDOW_DELAY = 2000; + + private static final String LIMITED_TEXT = + "New notifications suppressed except calls, alarms & timers."; + private static final String FULL_TEXT = + "You won't hear any calls, alarms or timers."; + + private final Context mContext; + private final Paint mPathPaint; + private final TextView mHintText; + private final ModeSpinner mModeSpinner; + private final ImageView mCloseButton; + private final ImageView mSettingsButton; + private final InfoWindow mInfoWindow; + private final Rect mLayoutRect = new Rect(); + private final UntilPager mUntilPager; + private final AlarmWarning mAlarmWarning; + + private float mDownY; + private int mDownBottom; + private boolean mPeekable = true; + private boolean mClosing; + private int mBottom; + private int mWidthSpec; + private Adapter mAdapter; + + public ZenModeView(Context context) { + this(context, null); + } + + public ZenModeView(Context context, AttributeSet attrs) { + super(context, attrs); + if (DEBUG) log("new %s()", getClass().getSimpleName()); + mContext = context; + + mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPathPaint.setStyle(Paint.Style.STROKE); + mPathPaint.setColor(GRAY); + mPathPaint.setStrokeWidth(5); + + final int iconSize = mContext.getResources() + .getDimensionPixelSize(com.android.internal.R.dimen.notification_large_icon_width); + final int topRowSize = iconSize * 2 / 3; + + mCloseButton = new ImageView(mContext); + mCloseButton.setAlpha(0f); + mCloseButton.setImageDrawable(sd(closePath(topRowSize), topRowSize, mPathPaint)); + addView(mCloseButton, new LayoutParams(topRowSize, topRowSize)); + mCloseButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + bounce(v, null); + close(); + } + }); + + mSettingsButton = new ImageView(mContext); + mSettingsButton.setAlpha(0f); + final int p = topRowSize / 7; + mSettingsButton.setPadding(p, p, p, p); + mSettingsButton.setImageResource(R.drawable.ic_notify_settings_normal); + LayoutParams lp = new LayoutParams(topRowSize, topRowSize); + lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + addView(mSettingsButton, lp); + mSettingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mAdapter == null || mAdapter.getMode() != Adapter.MODE_LIMITED) { + return; + } + if (!mInfoWindow.isShowing()) { + mInfoWindow.show(mUntilPager); + } + bounce(mSettingsButton, null); + } + }); + mInfoWindow = new InfoWindow(mContext, LIMITED_TEXT); + + mModeSpinner = new ModeSpinner(mContext); + mModeSpinner.setAlpha(0); + mModeSpinner.setEnabled(false); + mModeSpinner.setId(android.R.id.title); + lp = new LayoutParams(LayoutParams.WRAP_CONTENT, topRowSize); + lp.addRule(RelativeLayout.CENTER_HORIZONTAL); + addView(mModeSpinner, lp); + + + mUntilPager = new UntilPager(mContext, mPathPaint, iconSize); + mUntilPager.setId(android.R.id.tabhost); + mUntilPager.setAlpha(0); + lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + lp.addRule(BELOW, mModeSpinner.getId()); + addView(mUntilPager, lp); + + mAlarmWarning = new AlarmWarning(mContext); + mAlarmWarning.setAlpha(0); + lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + lp.addRule(CENTER_HORIZONTAL); + lp.addRule(BELOW, mUntilPager.getId()); + addView(mAlarmWarning, lp); + + mHintText = new TextView(mContext); + mHintText.setTypeface(CONDENSED); + mHintText.setText("Swipe down for Limited Interruptions"); + mHintText.setGravity(Gravity.CENTER); + mHintText.setTextColor(GRAY); + addView(mHintText, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + } + + private void close() { + mClosing = true; + final int startBottom = mBottom; + final int max = mPeekable ? getExpandedBottom() : startBottom; + mHintText.animate().alpha(1).setUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float f = animation.getAnimatedFraction(); + final int hintBottom = mHintText.getBottom(); + setPeeked(hintBottom + (int)((1-f) * (startBottom - hintBottom)), max); + if (f == 1) { + mPeekable = true; + mClosing = false; + mModeSpinner.updateState(); + if (mAdapter != null) { + mAdapter.cancel(); + } + } + } + }).start(); + mUntilPager.animate().alpha(0).start(); + mAlarmWarning.animate().alpha(0).start(); + mInfoWindow.dismiss(); + } + + public void setAdapter(Adapter adapter) { + mAdapter = adapter; + mAdapter.setCallbacks(new Adapter.Callbacks() { + @Override + public void onChanged() { + post(new Runnable() { + @Override + public void run() { + updateState(true); + } + }); + } + }); + updateState(false); + } + + private void updateState(boolean animate) { + if (mAdapter != null && mAdapter.getMode() == Adapter.MODE_OFF && !mPeekable) { + close(); + } else { + mModeSpinner.updateState(); + mUntilPager.updateState(); + mAlarmWarning.updateState(animate); + final float settingsAlpha = getSettingsButtonAlpha(); + if (settingsAlpha != mSettingsButton.getAlpha()) { + if (animate) { + mSettingsButton.animate().alpha(settingsAlpha).start(); + } else { + mSettingsButton.setAlpha(settingsAlpha); + } + } + if (mPeekable && mAdapter != null && mAdapter.getMode() != Adapter.MODE_OFF) { + if (DEBUG) log("panic expand!"); + mPeekable = false; + mModeSpinner.setEnabled(true); + mBottom = getExpandedBottom(); + setExpanded(1); + } + mInfoWindow.dismiss(); + } + } + + private float getSettingsButtonAlpha() { + final boolean full = mAdapter != null && mAdapter.getMode() == Adapter.MODE_FULL; + final boolean collapsed = mHintText.getAlpha() == 1; + return full || collapsed ? 0 : SETTINGS_ALPHA; + } + + private static Path closePath(int size) { + final int pad = size / 4; + final Path p = new Path(); + p.moveTo(pad, pad); + p.lineTo(size - pad, size - pad); + p.moveTo(size - pad, pad); + p.lineTo(pad, size - pad); + return p; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (DEBUG) log("onMeasure %s %s", + MeasureSpec.toString(widthMeasureSpec), MeasureSpec.toString(heightMeasureSpec)); + if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { + throw new UnsupportedOperationException("Width must be exact"); + } + if (widthMeasureSpec != mWidthSpec) { + if (DEBUG) log(" super.onMeasure"); + final int hms = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + super.onMeasure(widthMeasureSpec, hms); + mBottom = mPeekable ? mHintText.getMeasuredHeight() : getExpandedBottom(); + mWidthSpec = widthMeasureSpec; + } + if (DEBUG) log("mBottom (OM) = " + mBottom); + setMeasuredDimension(getMeasuredWidth(), mBottom); + if (DEBUG) log(" mw=%s mh=%s", + toString(getMeasuredWidthAndState()), toString(getMeasuredHeightAndState())); + } + + private static String toString(int sizeAndState) { + final int size = sizeAndState & MEASURED_SIZE_MASK; + final boolean tooSmall = (sizeAndState & MEASURED_STATE_TOO_SMALL) != 0; + return size + (tooSmall ? "TOO SMALL" : ""); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mLayoutRect.set(left, top, right, bottom); + if (DEBUG) log("onLayout %s %s %dx%d", changed, + mLayoutRect.toShortString(), mLayoutRect.width(), mLayoutRect.height()); + super.onLayout(changed, left, top, right, bottom); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final boolean rt = super.dispatchTouchEvent(ev); + if (DEBUG) logTouchEvent("dispatchTouchEvent", rt, ev); + return rt; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + final boolean rt = super.onInterceptTouchEvent(ev); + if (DEBUG) logTouchEvent("onInterceptTouchEvent", rt, ev); + if (ev.getAction() == MotionEvent.ACTION_DOWN + && ev.getY() > mCloseButton.getBottom() + && mPeekable) { + return true; + } + return rt; + } + + private static void logTouchEvent(String method, boolean rt, MotionEvent event) { + final String action = MotionEvent.actionToString(event.getAction()); + Log.d(TAG, method + " " + (rt ? "TRUE" : "FALSE") + " " + action); + } + + private int getExpandedBottom() { + int b = mModeSpinner.getMeasuredHeight() + mUntilPager.getMeasuredHeight(); + if (mAlarmWarning.getAlpha() == 1) b += mAlarmWarning.getMeasuredHeight(); + return b; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean rt = super.onTouchEvent(event); + if (DEBUG) logTouchEvent("onTouchEvent", rt, event); + if (!mPeekable) { + return rt; + } + if (event.getAction() == MotionEvent.ACTION_DOWN) { + mDownY = event.getY(); + if (DEBUG) log(" mDownY=" + mDownY); + mDownBottom = mBottom; + return true; + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + final float dy = event.getY() - mDownY; + setPeeked(mDownBottom + (int)dy, getExpandedBottom()); + } else if (event.getAction() == MotionEvent.ACTION_UP + || event.getAction() == MotionEvent.ACTION_CANCEL) { + final float dy = event.getY() - mDownY; + setPeeked(mDownBottom + (int)dy, getExpandedBottom()); + if (mPeekable) { + close(); + } + } + return rt; + } + + private void setPeeked(int peeked, int max) { + if (DEBUG) log("setPeeked=" + peeked); + final int min = mHintText.getBottom(); + peeked = Math.max(min, Math.min(peeked, max)); + if (mBottom == peeked) { + return; + } + if (peeked == max) { + mPeekable = false; + mModeSpinner.setEnabled(true); + if (mAdapter != null) { + mAdapter.setMode(Adapter.MODE_LIMITED); + } + } + if (peeked == min) { + mPeekable = true; + mModeSpinner.setEnabled(false); + } + if (DEBUG) log(" mBottom=" + peeked); + mBottom = peeked; + final float f = (peeked - min) / (float)(max - min); + setExpanded(f); + requestLayout(); + } + + private void setExpanded(float f) { + if (DEBUG) log("setExpanded " + f); + final int a = (int)(Color.alpha(BACKGROUND) * f); + setBackgroundColor(Color.argb(a, + Color.red(BACKGROUND), Color.green(BACKGROUND), Color.blue(BACKGROUND))); + mHintText.setAlpha(1 - f); + mCloseButton.setAlpha(f); + mModeSpinner.setAlpha(f); + mUntilPager.setAlpha(f); + mSettingsButton.setAlpha(f * getSettingsButtonAlpha()); + } + + private static void log(String msg, Object... args) { + Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); + } + + private static ShapeDrawable sd(Path p, int size, Paint pt) { + final ShapeDrawable sd = new ShapeDrawable(new PathShape(p, size, size)); + sd.getPaint().set(pt); + sd.setIntrinsicHeight(size); + sd.setIntrinsicWidth(size); + return sd; + } + + public void dispatchExternalTouchEvent(MotionEvent ev) { + onTouchEvent(ev); + } + + private static void bounce(final View v, final Runnable midBounce) { + v.animate().scaleX(BOUNCE_SCALE).scaleY(BOUNCE_SCALE).setDuration(DURATION / 3) + .setListener(new AnimatorListenerAdapter() { + private boolean mFired; + @Override + public void onAnimationEnd(Animator animation) { + if (!mFired) { + mFired = true; + if (midBounce != null) { + midBounce.run(); + } + v.animate().scaleX(1).scaleY(1).setListener(null).start(); + } + } + }).start(); + } + + private final class UntilPager extends RelativeLayout { + private final ImageView mPrev; + private final ImageView mNext; + private final TextView mText1; + private final TextView mText2; + + private TextView mText; + + public UntilPager(Context context, Paint pathPaint, int iconSize) { + super(context); + mText1 = new TextView(mContext); + mText1.setTypeface(CONDENSED); + mText1.setTextColor(GRAY); + mText1.setGravity(Gravity.CENTER); + LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, iconSize); + addView(mText1, lp); + mText = mText1; + + mText2 = new TextView(mContext); + mText2.setTypeface(CONDENSED); + mText2.setTextColor(GRAY); + mText2.setAlpha(0); + mText2.setGravity(Gravity.CENTER); + addView(mText2, lp); + + lp = new LayoutParams(iconSize, iconSize); + final View v = new View(mContext); + v.setBackgroundColor(BACKGROUND); + addView(v, lp); + mPrev = new ImageView(mContext); + mPrev.setId(android.R.id.button1); + mPrev.setImageDrawable(sd(prevPath(iconSize), iconSize, pathPaint)); + addView(mPrev, lp); + mPrev.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onNav(v, -1); + } + }); + + lp = new LayoutParams(iconSize, iconSize); + lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + final View v2 = new View(mContext); + v2.setBackgroundColor(BACKGROUND); + addView(v2, lp); + mNext = new ImageView(mContext); + mNext.setId(android.R.id.button2); + mNext.setImageDrawable(sd(nextPath(iconSize), iconSize, pathPaint)); + addView(mNext, lp); + mNext.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onNav(v, 1); + } + }); + + updateState(); + } + + private void onNav(View v, int d) { + bounce(v, null); + if (mAdapter == null) { + return; + } + if (mAdapter.getExitConditionCount() == 1) { + horBounce(d); + return; + } + final int w = getWidth(); + final float s = Math.signum(d); + final TextView current = mText; + final TextView other = mText == mText1 ? mText2 : mText1; + final ExitCondition ec = mAdapter.getExitCondition(d); + setText(other, ec); + other.setTranslationX(-s * w); + other.animate().translationX(0).alpha(1).setDuration(DURATION).start(); + current.animate().translationX(s * w).alpha(0).setDuration(DURATION).start(); + mText = other; + mAdapter.select(ec); + } + + private void horBounce(int d) { + final int w = getWidth(); + mText.animate() + .setDuration(BOUNCE_DURATION) + .translationX(Math.signum(d) * w / 20) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mText.animate().translationX(0).setListener(null).start(); + } + }).start(); + } + + private void setText(final TextView textView, final ExitCondition ec) { + SpannableStringBuilder ss = new SpannableStringBuilder(ec.line1 + "\n" + ec.line2); + ss.setSpan(new RelativeSizeSpan(1.5f), (ec.line1 + "\n").length(), ss.length(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + if (ec.action != null) { + ss.setSpan(new CustomLinkSpan() { + @Override + public void onClick() { + // TODO wire up links + Toast.makeText(mContext, ec.action, Toast.LENGTH_SHORT).show(); + } + }, (ec.line1 + "\n").length(), ss.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + } else { + textView.setMovementMethod(null); + } + textView.setText(ss); + } + + private void updateState() { + if (mAdapter == null) { + return; + } + setText(mText, mAdapter.getExitCondition(0)); + } + + private Path prevPath(int size) { + final int hp = size / 3; + final int vp = size / 4; + final Path p = new Path(); + p.moveTo(size - hp, vp); + p.lineTo(hp, size / 2); + p.lineTo(size - hp, size - vp); + return p; + } + + private Path nextPath(int size) { + final int hp = size / 3; + final int vp = size / 4; + Path p = new Path(); + p.moveTo(hp, vp); + p.lineTo(size - hp, size / 2); + p.lineTo(hp, size - vp); + return p; + } + } + + private abstract static class CustomLinkSpan extends URLSpan { + abstract public void onClick(); + + public CustomLinkSpan() { + super("#"); + } + + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + ds.bgColor = BACKGROUND; + } + + @Override + public void onClick(View widget) { + onClick(); + } + } + + public interface Adapter { + public static final int MODE_OFF = 0; + public static final int MODE_LIMITED = 1; + public static final int MODE_FULL = 2; + + int getMode(); + void setMode(int mode); + void select(ExitCondition ec); + void cancel(); + void setCallbacks(Callbacks callbacks); + ExitCondition getExitCondition(int d); + int getExitConditionCount(); + + public static class ExitCondition { + public String summary; + public String line1; + public String line2; + public String action; + } + + public interface Callbacks { + void onChanged(); + } + } + + private final class ModeSpinner extends Spinner { + public ModeSpinner(final Context context) { + super(context); + setBackgroundResource(R.drawable.spinner_default_holo_dark_am_no_underline); + final ArrayAdapter adapter = new ArrayAdapter(mContext, 0) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (DEBUG) log("getView %s parent=%s", position, parent); + return getDropDownView(position, convertView, parent); + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + if (DEBUG) log("getDropDownView %s cv=%s parent=%s", + position, convertView, parent); + final TextView tv = convertView != null ? (TextView) convertView + : new TextView(context); + final int mode = getItem(position); + tv.setText(modeToString(mode)); + if (convertView == null) { + if (DEBUG) log(" setting up view"); + tv.setTextColor(GRAY); + tv.setTypeface(CONDENSED); + tv.setAllCaps(true); + tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, tv.getTextSize() * 1.5f); + final int p = (int) tv.getTextSize() / 2; + if (parent instanceof ListView) { + tv.setPadding(p, p, p, p); + } else { + tv.setGravity(Gravity.CENTER_HORIZONTAL); + tv.setPadding(p, 0, p, 0); + } + } + tv.setOnTouchListener(new OnTouchListener(){ + @Override + public boolean onTouch(View v, MotionEvent event) { + if (DEBUG) log("onTouch %s %s", tv.getText(), + MotionEvent.actionToString(event.getAction())); + if (mAdapter != null) { + mAdapter.setMode(mode); + } + return false; + } + + }); + return tv; + } + }; + adapter.add(Adapter.MODE_LIMITED); + adapter.add(Adapter.MODE_FULL); + setAdapter(adapter); + } + + public void updateState() { + int mode = mAdapter != null ? mAdapter.getMode() : Adapter.MODE_LIMITED; + if (mode == Adapter.MODE_OFF) { + mode = Adapter.MODE_LIMITED; + } + if (DEBUG) log("setSelectedMode " + mode); + for (int i = 0; i < getAdapter().getCount(); i++) { + if (getAdapter().getItem(i).equals(mode)) { + if (DEBUG) log(" setting selection = " + i); + setSelection(i, true); + return; + } + } + } + + private String modeToString(int mode) { + if (mode == Adapter.MODE_LIMITED) return "Limited interruptions"; + if (mode == Adapter.MODE_FULL) return "Zero interruptions"; + throw new UnsupportedOperationException("Unsupported mode: " + mode); + } + } + + private final class AlarmWarning extends LinearLayout { + public AlarmWarning(Context context) { + super(context); + setOrientation(HORIZONTAL); + + final TextView tv = new TextView(mContext); + tv.setTextColor(GRAY); + tv.setGravity(Gravity.TOP); + tv.setTypeface(CONDENSED); + tv.setText(FULL_TEXT); + addView(tv); + + final ImageView icon = new ImageView(mContext); + icon.setAlpha(.75f); + int size = (int)tv.getTextSize(); + icon.setImageResource(android.R.drawable.ic_dialog_alert); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(size, size); + final int p = size / 4; + lp.bottomMargin = lp.topMargin = lp.rightMargin = lp.leftMargin = p; + addView(icon, 0, lp); + setPadding(p, 0, p, p); + } + + public void updateState(boolean animate) { + final boolean visible = mAdapter != null && mAdapter.getMode() == Adapter.MODE_FULL; + final float alpha = visible ? 1 : 0; + if (alpha == getAlpha()) { + return; + } + if (animate) { + final boolean in = alpha == 1; + animate().alpha(alpha).setUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (mPeekable || mClosing) { + return; + } + float f = animation.getAnimatedFraction(); + if (!in) { + f = 1 - f; + } + ZenModeView.this.mBottom = mUntilPager.getBottom() + + (int)(mAlarmWarning.getMeasuredHeight() * f); + if (DEBUG) log("mBottom (AW) = " + mBottom); + requestLayout(); + } + }).start(); + } else { + setAlpha(alpha); + requestLayout(); + } + } + } + + private static class InfoWindow extends PopupWindow implements Runnable { + private final TextView mText; + + public InfoWindow(Context context, String text) { + mText = new TextView(context); + mText.setTypeface(CONDENSED); + mText.setBackgroundColor(0xbb000000); + mText.setTextColor(0xffffffff); + mText.setText(text); + mText.setGravity(Gravity.CENTER); + setAnimationStyle(android.R.style.Animation_Toast); + setContentView(mText); + } + + @Override + public void run() { + dismiss(); + } + + public void show(View over) { + setWidth(over.getMeasuredWidth()); + setHeight(over.getMeasuredHeight()); + final int[] loc = new int[2]; + over.getLocationInWindow(loc); + showAtLocation(over, Gravity.NO_GRAVITY, loc[0], loc[1]); + over.postDelayed(this, INFO_WINDOW_DELAY); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java new file mode 100644 index 000000000000..2078b3c7e879 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2014 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.systemui.statusbar.phone; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.AsyncTask; +import android.os.Handler; +import android.provider.Settings; +import android.util.Log; + +import java.util.Arrays; +import java.util.List; + +public class ZenModeViewAdapter implements ZenModeView.Adapter { + private static final String TAG = "ZenModeViewAdapter"; + + private final Context mContext; + private final ContentResolver mResolver; + private final Handler mHandler = new Handler(); + private final List mExits = Arrays.asList( + newExit("Until you delete this", "Until", "You delete this")); + + private Callbacks mCallbacks; + private int mExitIndex; + + public ZenModeViewAdapter(Context context) { + mContext = context; + mResolver = mContext.getContentResolver(); + mResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ZEN_MODE), + false, new SettingsObserver(mHandler)); + } + + @Override + public int getMode() { + final int v = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + if (v == Settings.Global.ZEN_MODE_LIMITED) return MODE_LIMITED; + if (v == Settings.Global.ZEN_MODE_FULL) return MODE_FULL; + return MODE_OFF; + } + + @Override + public void setMode(int mode) { + final int v = mode == MODE_LIMITED ? Settings.Global.ZEN_MODE_LIMITED + : mode == MODE_FULL ? Settings.Global.ZEN_MODE_FULL + : Settings.Global.ZEN_MODE_OFF; + AsyncTask.execute(new Runnable() { + @Override + public void run() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ZEN_MODE, v); + } + }); + } + + @Override + public void cancel() { + if (mExitIndex != 0) { + mExitIndex = 0; + mHandler.post(mChange); + } + setMode(MODE_OFF); + } + + @Override + public void setCallbacks(final Callbacks callbacks) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallbacks = callbacks; + } + }); + } + + @Override + public ExitCondition getExitCondition(int d) { + final int n = mExits.size(); + final int i = (n + (mExitIndex + (int)Math.signum(d))) % n; + return mExits.get(i); + } + + @Override + public int getExitConditionCount() { + return mExits.size(); + } + + @Override + public void select(ExitCondition ec) { + final int i = mExits.indexOf(ec); + if (i == -1 || i == mExitIndex) { + return; + } + mExitIndex = i; + mHandler.post(mChange); + } + + private static ExitCondition newExit(String summary, String line1, String line2) { + final ExitCondition rt = new ExitCondition(); + rt.summary = summary; + rt.line1 = line1; + rt.line2 = line2; + return rt; + } + + private final Runnable mChange = new Runnable() { + public void run() { + if (mCallbacks == null) { + return; + } + try { + mCallbacks.onChanged(); + } catch (Throwable t) { + Log.w(TAG, "Error dispatching onChanged to " + mCallbacks, t); + } + } + }; + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + mChange.run(); // already on handler + } + } +} diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index df2aaca49eca..243bd74c7617 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import android.os.IBinder; + public interface NotificationDelegate { void onSetDisabled(int status); void onClearAll(); @@ -24,4 +26,5 @@ public interface NotificationDelegate { void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message); void onPanelRevealed(); + boolean allowDisable(int what, IBinder token, String pkg); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ab46dfe0e63a..b791435d6b50 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -154,7 +154,7 @@ public class NotificationManagerService extends SystemService { private long[] mFallbackVibrationPattern; boolean mSystemReady; - int mDisabledNotifications; + private boolean mDisableNotificationAlerts; NotificationRecord mSoundNotification; NotificationRecord mVibrateNotification; @@ -202,6 +202,19 @@ public class NotificationManagerService extends SystemService { final ArrayList mScorers = new ArrayList(); + private int mZenMode; + private int mPreZenAlarmVolume = -1; + private int mPreZenRingerMode = -1; + // temporary, until we update apps to provide metadata + private static final Set CALL_PACKAGES = new HashSet(Arrays.asList( + "com.google.android.dialer", + "com.android.phone" + )); + private static final Set ALARM_PACKAGES = new HashSet(Arrays.asList( + "com.google.android.deskclock" + )); + private static final String EXTRA_INTERCEPT = "android.intercept"; + private class NotificationListenerInfo implements IBinder.DeathRecipient { INotificationListener listener; ComponentName component; @@ -869,8 +882,8 @@ public class NotificationManagerService extends SystemService { @Override public void onSetDisabled(int status) { synchronized (mNotificationList) { - mDisabledNotifications = status; - if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) { + mDisableNotificationAlerts = (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; + if (mDisableNotificationAlerts) { // cancel whatever's going on long identity = Binder.clearCallingIdentity(); try { @@ -968,6 +981,14 @@ public class NotificationManagerService extends SystemService { } Binder.restoreCallingIdentity(ident); } + + @Override + public boolean allowDisable(int what, IBinder token, String pkg) { + if (mZenMode == Settings.Global.ZEN_MODE_FULL && isCall(pkg, null)) { + return false; + } + return true; + } }; private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -1077,6 +1098,12 @@ public class NotificationManagerService extends SystemService { private final Uri ENABLED_NOTIFICATION_LISTENERS_URI = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + private final Uri ZEN_MODE + = Settings.Global.getUriFor(Settings.Global.ZEN_MODE); + + private final Uri MODE_RINGER + = Settings.Global.getUriFor(Settings.Global.MODE_RINGER); + SettingsObserver(Handler handler) { super(handler); } @@ -1087,6 +1114,10 @@ public class NotificationManagerService extends SystemService { false, this, UserHandle.USER_ALL); resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(ZEN_MODE, + false, this); + resolver.registerContentObserver(MODE_RINGER, + false, this); update(null); } @@ -1107,6 +1138,12 @@ public class NotificationManagerService extends SystemService { if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { rebindListenerServices(); } + if (ZEN_MODE.equals(uri)) { + updateZenMode(); + } + if (MODE_RINGER.equals(uri)) { + updateRingerMode(); + } } } @@ -1170,8 +1207,10 @@ public class NotificationManagerService extends SystemService { // flag at least once and we'll go back to 0 after that. if (0 == Settings.Global.getInt(getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0)) { - mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS; + mDisableNotificationAlerts = true; } + updateRingerMode(); + updateZenMode(); // register for various Intents IntentFilter filter = new IntentFilter(); @@ -1622,8 +1661,10 @@ public class NotificationManagerService extends SystemService { pw.println(" mSoundNotification=" + mSoundNotification); pw.println(" mVibrateNotification=" + mVibrateNotification); - pw.println(" mDisabledNotifications=0x" - + Integer.toHexString(mDisabledNotifications)); + pw.println(" mDisableNotificationAlerts=" + mDisableNotificationAlerts); + pw.println(" mZenMode=" + Settings.Global.zenModeToString(mZenMode)); + pw.println(" mPreZenAlarmVolume=" + mPreZenAlarmVolume); + pw.println(" mPreZenRingerMode=" + mPreZenRingerMode); pw.println(" mSystemReady=" + mSystemReady); pw.println(" mArchive=" + mArchive.toString()); Iterator iter = mArchive.descendingIterator(); @@ -1767,9 +1808,13 @@ public class NotificationManagerService extends SystemService { return; } - // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); + // Is this notification intercepted by zen mode? + final boolean intercept = shouldIntercept(pkg, notification); + notification.extras.putBoolean(EXTRA_INTERCEPT, intercept); + // Should this notification make noise, vibe, or use the LED? + final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD) && !intercept; + if (DBG) Slog.v(TAG, "canInterrupt=" + canInterrupt + " intercept=" + intercept); synchronized (mNotificationList) { final StatusBarNotification n = new StatusBarNotification( pkg, id, tag, callingUid, callingPid, score, notification, user); @@ -1851,8 +1896,7 @@ public class NotificationManagerService extends SystemService { } // If we're not supposed to beep, vibrate, etc. then don't. - if (((mDisabledNotifications - & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) + if (!mDisableNotificationAlerts && (!(old != null && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) && (r.getUserId() == UserHandle.USER_ALL || @@ -1860,7 +1904,7 @@ public class NotificationManagerService extends SystemService { && canInterrupt && mSystemReady && mAudioManager != null) { - + if (DBG) Slog.v(TAG, "Interrupting!"); // sound // should we use the default notification sound? (indicated either by @@ -1905,6 +1949,8 @@ public class NotificationManagerService extends SystemService { final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); if (player != null) { + if (DBG) Slog.v(TAG, "Playing sound " + soundUri + + " on stream " + audioStreamType); player.playAsync(soundUri, user, looping, audioStreamType); } } catch (RemoteException e) { @@ -2437,4 +2483,67 @@ public class NotificationManagerService extends SystemService { updateLightsLocked(); } } + + private void updateRingerMode() { + final int ringerMode = Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.MODE_RINGER, -1); + final boolean nonSilentRingerMode = ringerMode == AudioManager.RINGER_MODE_NORMAL + || ringerMode == AudioManager.RINGER_MODE_VIBRATE; + if (mZenMode == Settings.Global.ZEN_MODE_FULL && nonSilentRingerMode) { + Settings.Global.putInt(getContext().getContentResolver(), + Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + } + } + + private void updateZenMode() { + mZenMode = Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); + if (DBG) Slog.d(TAG, "updateZenMode " + Settings.Global.zenModeToString(mZenMode)); + if (mAudioManager != null) { + if (mZenMode == Settings.Global.ZEN_MODE_FULL) { + // calls vibrate if ringer mode = vibrate, so set the ringer mode as well + mPreZenRingerMode = mAudioManager.getRingerMode(); + if (DBG) Slog.d(TAG, "Muting calls mPreZenRingerMode=" + mPreZenRingerMode); + mAudioManager.setStreamMute(AudioManager.STREAM_RING, true); + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); + // alarms don't simply respect mute, so set the volume as well + mPreZenAlarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM); + if (DBG) Slog.d(TAG, "Muting alarms mPreZenAlarmVolume=" + mPreZenAlarmVolume); + mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, true); + mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0); + } else { + if (DBG) Slog.d(TAG, "Unmuting calls"); + mAudioManager.setStreamMute(AudioManager.STREAM_RING, false); + if (mPreZenRingerMode != -1) { + if (DBG) Slog.d(TAG, "Restoring ringer mode to " + mPreZenRingerMode); + mAudioManager.setRingerMode(mPreZenRingerMode); + mPreZenRingerMode = -1; + } + if (DBG) Slog.d(TAG, "Unmuting alarms"); + mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, false); + if (mPreZenAlarmVolume != -1) { + if (DBG) Slog.d(TAG, "Restoring STREAM_ALARM to " + mPreZenAlarmVolume); + mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mPreZenAlarmVolume, 0); + mPreZenAlarmVolume = -1; + } + } + } + } + + private boolean isCall(String pkg, Notification n) { + return CALL_PACKAGES.contains(pkg); + } + + private boolean isAlarm(String pkg, Notification n) { + return ALARM_PACKAGES.contains(pkg); + } + + private boolean shouldIntercept(String pkg, Notification n) { + if (mZenMode == Settings.Global.ZEN_MODE_LIMITED) { + return !isCall(pkg, n) && !isAlarm(pkg, n); + } else if (mZenMode == Settings.Global.ZEN_MODE_FULL) { + return true; + } + return false; + } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2ae467eabdd1..8219eb53e988 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -23,9 +23,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.util.Slog; @@ -207,6 +205,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub @Override public void disable(int what, IBinder token, String pkg) { + if (!mNotificationDelegate.allowDisable(what, token, pkg)) { + if (SPEW) Slog.d(TAG, "Blocking disable request from " + pkg); + return; + } disableInternal(mCurrentUserId, what, token, pkg); } @@ -676,26 +678,4 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } } - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - || Intent.ACTION_SCREEN_OFF.equals(action)) { - collapsePanels(); - } - /* - else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) { - updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false), - intent.getStringExtra(Telephony.Intents.EXTRA_SPN), - intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false), - intent.getStringExtra(Telephony.Intents.EXTRA_PLMN)); - } - else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - updateResources(); - } - */ - } - }; - } -- 2.11.0