From dc6bfa6ef57705c47b93caab44d39c76d4840ea2 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Thu, 6 Sep 2012 18:04:06 -0700 Subject: [PATCH] User management UI: take 2 Single screen user management, also visible on secondary users. Work in progress, especially with respect to synchronizing with the Me profile. Change-Id: Id6e94a85d53356847e4e019c52e3388de9ecb354 --- res/drawable/ic_default_user.png | Bin 0 -> 1091 bytes res/drawable/ic_user.png | Bin 0 -> 2070 bytes res/drawable/ic_user_cyan.png | Bin 0 -> 4959 bytes res/drawable/ic_user_green.png | Bin 0 -> 5132 bytes res/drawable/ic_user_purple.png | Bin 0 -> 4967 bytes res/drawable/ic_user_red.png | Bin 0 -> 5064 bytes res/drawable/ic_user_yellow.png | Bin 0 -> 4890 bytes res/layout/preference_user_delete_widget.xml | 41 ++ res/values/strings.xml | 41 +- res/xml/user_details.xml | 36 -- res/xml/user_settings.xml | 13 + src/com/android/settings/Settings.java | 11 +- .../settings/users/UserDetailsSettings.java | 288 ------------- .../settings/users/UserPreference.java | 85 ++++ .../android/settings/users/UserSettings.java | 390 ++++++++++++++++-- 15 files changed, 522 insertions(+), 383 deletions(-) create mode 100644 res/drawable/ic_default_user.png create mode 100644 res/drawable/ic_user.png create mode 100644 res/drawable/ic_user_cyan.png create mode 100644 res/drawable/ic_user_green.png create mode 100644 res/drawable/ic_user_purple.png create mode 100644 res/drawable/ic_user_red.png create mode 100644 res/drawable/ic_user_yellow.png create mode 100644 res/layout/preference_user_delete_widget.xml delete mode 100644 res/xml/user_details.xml delete mode 100644 src/com/android/settings/users/UserDetailsSettings.java create mode 100644 src/com/android/settings/users/UserPreference.java diff --git a/res/drawable/ic_default_user.png b/res/drawable/ic_default_user.png new file mode 100644 index 0000000000000000000000000000000000000000..ddf797fc9da176f949678611219eae2150766694 GIT binary patch literal 1091 zcmeAS@N?(olHy`uVBq!ia0vp^4Is<`Bp9BB+KB@x&H|6fVj%4S#%?FG?LdNM1&--J zx&e$09vaSKU|^2)ba4!+xb=3{>q$!tI9hAxojqIoe|q%WYkA2+77B*9L+8~8oATXf zP!yl>kLTJK1{((c1I!?otSyyC_UHF_CCE}BH*j7!O@LZW8-tXY_`!NnIA71a= zBmG4)_0{XjFrJ&=w>j~7DF`Xto^;glhW)bH;!W;ai2_Vd7~aj)Tgk~JVa=v+e*!^GJyu-FD{h+2rQy zOHzXcF5G>YTz`8-W~iL+t-G1}>*k(1mMVKNeSi9m9cyII2U!^IxE#}-`FdNOx5vfm z|Faf$)~pz4`olwHQeL3`L6YuZ%YfVKmS>my5J(~#hPnDTbX|R z<~XqJ>;sPXi)JdY$vxQcW<#jj?*{1v3sJeUl#hFyZXP-kb9UA1y-%aWSABJ?H~oC% zOaC!PG zT^=EZ;P3T^8kkEq-dC9MZgaf)3Wk!7|LZy!O18u+YcTly7FlY;o^WTq&QpnoJkA~3 z7a5+hZU|Yxu!}umsxrf_bq%o_c?-Ik{q$z!l^;0CdigKkiCBZX?;Gy0US?oV(X(K< z6Tn>7s^#!q=||`)#*%xjMYZ3T)Tf>mJJ)OOa2J@{R7+eVN>UO_QmvAUQh^kMk%6J1 zuAu=Cg&3MynHX9bSm+vDSr1<%~X^wgl##FWaylc_d9MGT&ht(u000LbNkl1zQ6hFg3e~n+#{oMv z+A>ak6{lmxj+Xjhv|@dvjx8;890nUO1_(*eG%&)54<3;~p8LA@*FO>%+HA5LH~phC z`JUOCox8ttzMu0u=bro93&R@Lu>QZ~>-~TrT8xMxhG89NrtM&A$t^eBEqB>E}#EQK< zLNuj3s!AQe;y&v^lfbT$X{Di=W*!D68X=DBo$shW^6u@`2U?fARnZ+h!H%H>m_#$M z+IiO8T6F!&;CupvaS=cOg+k-th3o3K&E6Y`RfVsT$utjb2Ilzp>Y8sC|HK$g2Ohdl zPY(zXIr-B37rQ%O?Vjsp(lImumvfXAzBiqtHx{f_CXoc@7vy3PXsw#C+3RZS@DWJ? zKqn74z<2F8&8DS=cPd2`V*qFap(C{7$PX;%CPy=2C;;YgUcYXv~g%PLfeSn_Ck7S%1f^_K;Iw}eo-0Hc|tcc{nbWz3=jBL({& zgoqx!w!B?eT3eEKN&@f;RvCe;87opYzc6YvyY$h5hgpW39>7Z4)Ke3DMMP6d|0Ovv z@~^ovf?v-{55Uo|U^+g$L1h3GGIko7rK%HV=Su*@GYazk)k)8p0Rbwx z2l3lk$RO=O;aRy8ZKWPc2LS#=j-wpB!928jCCZxOh`KL50O*u2G5-sFz2tq90NhKs z+Y4h18bVNB_u+^lKOF$YMpPtx`r;C#x)2J`k(0-xA`8;XK+uEJ%|uav;EJZ_ydSVh z9Kmis($ZBSHzxr|R+4!l+w1P{P<{mc>9*FxoqkvPON5{>R3uz-LtrM+;e-pfug zY5i)3MdZ8$r-JEuPyNQNv+h# z2Pf_(@S_Y-{WN1?#>)KgAEjOzK3b3OE#qf{e264aH7jjKv}}?l*(bHKzv(uDivpq` zvv&h2G-H4!g8-w>JudZfDDvu}@p(Q7q-#Hx*$BD*%E_^}q(Sz{eyKZg2SNPTS)!zO zK^Lr+{u5DT-y++h}& znAqgjUK4&<8l+YZHLWDL)PA14p|LMuB6nG2?7qtVha8soq(&OucTa8HF>lFuMiL-* zL(!hH&ZoTta!?M+{ts7?wbX&hDUF`u(Q+L@>jmd`MHN5xl@OCIWf72b(U0S8UepL! z+17Y89(MfWnkP&5%#75gQVfz@Wp1`ZBkPupF0%>|!YCXx8V?r_%RSlpPUS04 zow&u_7J(A>4<&#_Od+C`1NU5Z^Yn4WStUjg(0F)Wr0c`aYOCvRYsP&oTJEC8$mc0O zK2iX%iQ^%lL+Tl>*Gl0LEFDpvJvo$<)zN+I>|a{mXr~SDpK5Mgskj)P0&@5wmB9zr zO}#t$4;B^{IRr6x+G4IZF|n|)v9K^F8e)otg`ZLQFxT0lAdj4609gb6x*ULeP5k&D z{#yO_&Tsx~!}gg3aj-F`8mrA+W~Dj52gXO>k)orww>^1u?et}OWBicqQfRa^}3WM{+Y``@mAv865|E^!QuLhITQ6;lgmWnN)Tz{B!R zwb$&aT6&Q9bJ1tLb2RpE66_aX4tuH7)BX6HmlWJ$j3GuCg<#_&LO9+U`NZvxo-;Ge zfRk(HsQ`YWy)hO(X=|z4`10>hZ;Um@j?*we5)vxJS$?bT2+RvrtWlm;{SRdpnAW% z#EB6jM!dIZSC_;npxYcjtvKu8ug+iL{;l78G9f@FQJQ@R0`J{D;%eIZc^~kfRZt>n zB1T@x#O|7%-Jix^;%q-b3DnLSvsyPS9$7}`fbRp6$)Lk2-mvO@tJHbFpXx8cRTJ2& zHy2K+ApYe`@{0)VqsrcyU#-#064rkS0OG7O8?74`8{>!!>Mv^o+P^?Egz8D5WNc zl;I@U)Cnvc9SID<{_i`pu*Cb?^|`W+Ex<8T57G2{Od=GzN@NJf9M-UgHLM}izX6iL zzM*XQC6fRE03~!qSaf7zbY(hYa%Ew3WdJfTF)=MLIV~_bR53IKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000PzNkl7Cc9mhYvbM7`CkH_Pk-|HiwYpJ_F7jqV*R=T5I{8$ykE1n zZ|*u0B8V7_wL1nQ8n)rVRRmRhnPYv)bUWq1NX%$rXhw#@D~qM)R_?hvU$7JmQ8of7 zHcp2QVrT$F1FkIaMUT_t3FnTCaCTyXiOgc8c0NV~MezzjVc98HW()p)v&%m}D{vuT z;{=Qy-Xskp6qewr%A5m{=lSC;9-GWajZmo)bWQxLt3hPNF95L-f+|<$E8e`e%)4_H zCQh(P_J5)PS5mo(N5+Jop4!JmYRQAr^LmG&+~o*Yy+ZD5Dl_9T(_`bjb!~~)uPzZ( zv1w!AOJbk^#8;F>&NEX9KRnsS2z|qiY;a#*`CQ@azrQ!#qMcZNb7>aUfX$k_GY+U_ zUvOc6!jEU#wHQKegly%;WK9uI9~$AsnQ^qLkl=P700CT8awHABc(UDhYDTtyUJ=NH za`DIrPaH^bWeFN~7XbAR>npx{e2iu!_yHKtT~R^+zJH=kTS`Jz?j8UFc<(sVwmh^i zCHFco zN;uYvaRZe8m#JVfvz%-vC|h%`0|l^p1E(i4VzYC}%uFZ6^_I~AECaJ$S*ivOkH*AC z*7BSoE(5R1;n5i5D>U3=03if?k<&`-(2ehBl|Uzph>V~ExTx9g`WIjAsQgtS5=bKh z_Zk3jM{={1BdC5pnb>In#u!YJQVwD7|H9c{jSPLD^t&JqFveh$l-a@)c1Q*S-0C@8 z5aeDfVAberixp0_uSi{eZ-AcB2VGhJ!u7=p;si0<551!8N)m+kZguIoF;Xl2S$gx@ z64#dlM%}hJh(HzK;*n9#Oe8o}F3*+Nw1J4(w*T37J-}GQ?Ob^GcERHZ8uYx9SvcB? zdHwnV#zu(U+GSJ&B8GkRc%T(y1wLAGT$w8%i3j3*w+%o{giSMke|?chCo`)#p)7LLmHp3u)geID>r!-=_|eHxj*r!|eQn9{$B&l~5tPAfJAkV) z-nhENM@x>>z)ES4pPXv*ox>@vEO12+g8$N67eHN!s|r#)ubl1h_<;saYoM=Pp2Ni> zb)|T>mBdVvvY-*ax-`pT75Wf@!ViwOc=^F56H?;xF5XqBcZdcIiUyB*hp$S?r3Fqm z1HXKv%@b3#@mAoq%L{zC3^q;%Hv2H(vbboVDtgQ`;gz!;I;rVbc~c8ZmGb8sUHREEuSyZ>7(Ow=vI~5&?C3hB3W3ZT#uCF+GwLTikqDpm9KU^ko_FRwan^uM zI?{UrU{wjO$nm{JzCGRH$%7*tYSuGX2JJUMq@Pklpbm7b2)7H*-)`o-@!>pOlMy8e zYKGIq8a#lNBt@WhdO%YPX2ugvcT&zyWQ@e-j`UVnoZN@=1($9YT$!)fIF3OCGde~Hfmu~%E1+DPRcQ#R7_6<;QC0O$;qop~ zqlvK*Mg#^3!LaD1dt49bP z?@9=PwsD-BOgPz@;Dfo6%kw$^?kN(**eHVS`UF+fyX^s}`~M~0ImRfscyx?MCtFN4 zA_9s*xNxY+2Xh6NW((fG-D7syQF=wJMQnt6j|NX9(2NZyIvEe|%Q!KXaIhJnDChS# zm@5PC+$wnc<7KY)9AYD)B;9&UAcBeFt@{80s(cw=#`T~FZyi)Ek@30Y+5WR^~9$*hnV!QFKpEA`IN^UB*FcX{jDBDo~BlN7V_ zPSEAcM6TeanKn-!YT{IJD>uBpI;3kG1G4g-VfA(WfQ0H=4MEq{8NTQ|Mj*4orP-2S zzcxFSck=o&W57xBx;3 zbZo^7(=EKRBh8Sng3$BIQ-_+I9W!|6Ha!_;Q?p+cT$mc+z(~}8`r}>?)jGj?_Gk<1 zD|C}jkZc%$cNNW0aN%H+!s{@?@-3^N2+GWO%!4gM@L^Lbh&FnW0DN<*NjnkBu$IW| zhT&CdSmFHC2$$Yl#8|uGXA3t3AozfkJm!bEPK)zS8x}9le9kD`uk#`l9b~*ST4jDYs(7yz>YfE1Pc!fLP0tB)odH z8b~{8m?JoYO*URK-xvTfM2*p1Ci^eh6*l>R^`^4H@HPB_-QG`T_5kbw*aNT!U=P3^ dfctm+cL49=8Us)3>@WZT002ovPDHLkV1n8KQMdpA literal 0 HcmV?d00001 diff --git a/res/drawable/ic_user_green.png b/res/drawable/ic_user_green.png new file mode 100644 index 0000000000000000000000000000000000000000..ca09f8534e408f87063c2e230b097d01b8d19a6e GIT binary patch literal 5132 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 M07*qoM6N<$g895X&;S4c literal 0 HcmV?d00001 diff --git a/res/layout/preference_user_delete_widget.xml b/res/layout/preference_user_delete_widget.xml new file mode 100644 index 00000000000..994b77a0556 --- /dev/null +++ b/res/layout/preference_user_delete_widget.xml @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index ba3f3865fb5..36eb09d9e26 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4165,39 +4165,34 @@ Users - Users + Other users Add user + + Active + + Not active + + Owner + + Nickname + + Add new user + + Adding a user to this device will take them through guided setup.\n\nEach user will have their own apps and personal space on this device.\nUsers can accept permissions for updates on behalf of other users of this device.\nYou can switch between users on the lock screen. - - - - - Edit details - - User information - - Name - - Content restrictions - - Require PIN - - Content rating - - System apps - - Installed apps - - Discard Remove user - Pesky kid + New user Remove user? Are you sure you want to remove the user and all associated data from the device? + + Adding new user\u2026 + + Delete user Show notifications diff --git a/res/xml/user_details.xml b/res/xml/user_details.xml deleted file mode 100644 index f36fd4584f7..00000000000 --- a/res/xml/user_details.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - diff --git a/res/xml/user_settings.xml b/res/xml/user_settings.xml index 95bc7034bb4..acb7c0f7387 100644 --- a/res/xml/user_settings.xml +++ b/res/xml/user_settings.xml @@ -18,6 +18,19 @@ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings" android:title="@string/user_settings_title"> + + + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index d5a90f645d7..6f2002a9c3b 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -107,14 +107,13 @@ public class Settings extends PreferenceActivity R.id.application_settings, R.id.personal_section, R.id.security_settings, + R.id.user_settings, R.id.account_settings, R.id.account_add, R.id.system_section, R.id.about_settings }; - private boolean mEnableUserManagement = false; - // TODO: Update Call Settings based on airplane mode state. protected HashMap mHeaderIndexMap = new HashMap(); @@ -129,11 +128,6 @@ public class Settings extends PreferenceActivity getWindow().setUiOptions(0); } - if (android.provider.Settings.Secure.getInt(getContentResolver(), "multiuser_enabled", -1) - > 0) { - mEnableUserManagement = true; - } - mAuthenticatorHelper = new AuthenticatorHelper(); mAuthenticatorHelper.updateAuthDescriptions(this); mAuthenticatorHelper.onAccountsUpdated(this, null); @@ -418,8 +412,7 @@ public class Settings extends PreferenceActivity int headerIndex = i + 1; i = insertAccountsHeaders(target, headerIndex); } else if (id == R.id.user_settings) { - if (!mEnableUserManagement - || !UserHandle.MU_ENABLED || UserHandle.myUserId() != 0 + if (!UserHandle.MU_ENABLED || !getResources().getBoolean(R.bool.enable_user_management) || Utils.isMonkeyRunning()) { target.remove(header); diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java deleted file mode 100644 index 518c6b621c9..00000000000 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2012 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.settings.users; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageManager; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.UserInfo; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserManager; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.PreferenceGroup; -import android.text.TextUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import com.android.settings.DialogCreatable; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; - -import java.util.HashMap; -import java.util.List; - -public class UserDetailsSettings extends SettingsPreferenceFragment - implements Preference.OnPreferenceChangeListener, DialogCreatable, - Preference.OnPreferenceClickListener { - - private static final String TAG = "UserDetailsSettings"; - - private static final int MENU_REMOVE_USER = Menu.FIRST; - private static final int DIALOG_CONFIRM_REMOVE = 1; - - private static final String KEY_USER_NAME = "user_name"; - private static final String KEY_USER_PICTURE = "user_picture"; - - public static final String EXTRA_USER_ID = "user_id"; - - private static final int RESULT_PICK_IMAGE = 1; - private static final int RESULT_CROP_IMAGE = 2; - - private EditTextPreference mNamePref; - private Preference mPicturePref; - - private IPackageManager mIPm; - private PackageManager mPm; - private UserManager mUm; - private int mUserId; - private boolean mNewUser; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - addPreferencesFromResource(R.xml.user_details); - Bundle args = getArguments(); - mNewUser = args == null || args.getInt(EXTRA_USER_ID, -1) == -1; - mUserId = mNewUser ? -1 : args.getInt(EXTRA_USER_ID, -1); - mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); - mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); - - if (icicle != null && icicle.containsKey(EXTRA_USER_ID)) { - mUserId = icicle.getInt(EXTRA_USER_ID); - mNewUser = false; - } - - if (mUserId == -1) { - mUserId = mUm.createUser(getString(R.string.user_new_user_name), 0).id; - } - mNamePref = (EditTextPreference) findPreference(KEY_USER_NAME); - mNamePref.setOnPreferenceChangeListener(this); - mPicturePref = findPreference(KEY_USER_PICTURE); - mPicturePref.setOnPreferenceClickListener(this); - setHasOptionsMenu(true); - } - - @Override - public void onResume() { - super.onResume(); - mPm = getActivity().getPackageManager(); - if (mUserId >= 0) { - initExistingUser(); - } else { - initNewUser(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(EXTRA_USER_ID, mUserId); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (mUserId == 0) { - return; - } - MenuItem addAccountItem = menu.add(0, MENU_REMOVE_USER, 0, - mNewUser ? R.string.user_discard_user_menu : R.string.user_remove_user_menu); - addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final int itemId = item.getItemId(); - if (itemId == MENU_REMOVE_USER) { - onRemoveUserClicked(); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void initExistingUser() { - List users = mUm.getUsers(); - UserInfo foundUser = null; - for (UserInfo user : users) { - if (user.id == mUserId) { - foundUser = user; - break; - } - } - if (foundUser != null) { - mNamePref.setSummary(foundUser.name); - mNamePref.setText(foundUser.name); - if (foundUser.iconPath != null) { - setPhotoId(foundUser.iconPath); - } - } - } - - private void initNewUser() { - // TODO: Check if there's already a "New user" and localize - mNamePref.setText(getString(R.string.user_new_user_name)); - mNamePref.setSummary(getString(R.string.user_new_user_name)); - } - - private void onRemoveUserClicked() { - if (mNewUser) { - removeUserNow(); - } else { - showDialog(DIALOG_CONFIRM_REMOVE); - } - } - - private void removeUserNow() { - mUm.removeUser(mUserId); - finish(); - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference instanceof CheckBoxPreference) { - String packageName = preference.getKey(); - int newState = ((Boolean) newValue) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; - try { - mIPm.setApplicationEnabledSetting(packageName, newState, 0, mUserId); - } catch (RemoteException re) { - Log.e(TAG, "Unable to change enabled state of package " + packageName - + " for user " + mUserId); - } - } else if (preference == mNamePref) { - String name = (String) newValue; - if (TextUtils.isEmpty(name)) { - return false; - } - mUm.setUserName(mUserId, (String) newValue); - mNamePref.setSummary((String) newValue); - } - return true; - } - - @Override - public Dialog onCreateDialog(int dialogId) { - switch (dialogId) { - case DIALOG_CONFIRM_REMOVE: - return new AlertDialog.Builder(getActivity()) - .setTitle(R.string.user_confirm_remove_title) - .setMessage(R.string.user_confirm_remove_message) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - removeUserNow(); - } - }) - .setNegativeButton(android.R.string.cancel, null) - .create(); - default: - return null; - } - } - - @Override - public boolean onPreferenceClick(Preference preference) { - if (preference == mPicturePref) { - Intent intent = new Intent(); - intent.setType("image/*"); - intent.setAction(Intent.ACTION_GET_CONTENT); - - startActivityForResult(intent, RESULT_PICK_IMAGE); - } - return false; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - switch (requestCode) { - case RESULT_PICK_IMAGE: - if (data.getData() != null) { - Uri imageUri = data.getData(); - System.err.println("imageUri = " + imageUri); - cropImage(imageUri); - } - break; - case RESULT_CROP_IMAGE: - saveCroppedImage(data); - break; - } - } - - private void cropImage(Uri imageUri) { - final Uri inputPhotoUri = imageUri; - Intent intent = new Intent("com.android.camera.action.CROP"); - intent.setDataAndType(inputPhotoUri, "image/*"); - intent.putExtra("crop", "true"); - intent.putExtra("aspectX", 1); - intent.putExtra("aspectY", 1); - intent.putExtra("outputX", 96); - intent.putExtra("outputY", 96); - intent.putExtra("return-data", true); - startActivityForResult(intent, RESULT_CROP_IMAGE); - } - - private void saveCroppedImage(Intent data) { - if (data.hasExtra("data")) { - Bitmap bitmap = (Bitmap) data.getParcelableExtra("data"); - ParcelFileDescriptor fd = mUm.setUserIcon(mUserId); - if (fd != null) { - bitmap.compress(CompressFormat.PNG, 100, - new ParcelFileDescriptor.AutoCloseOutputStream(fd)); - setPhotoId(mUm.getUserInfo(mUserId).iconPath); - } - } - } - - private void setPhotoId(String realPath) { - Drawable d = Drawable.createFromPath(realPath); - if (d == null) return; - mPicturePref.setIcon(d); - } -} diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java new file mode 100644 index 00000000000..2ced7ae6639 --- /dev/null +++ b/src/com/android/settings/users/UserPreference.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012 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.settings.users; + +import com.android.internal.util.CharSequences; +import com.android.settings.R; + +import android.content.Context; +import android.os.UserManager; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; + +public class UserPreference extends Preference { + + public static final int USERID_UNKNOWN = -10; + + private OnClickListener mDeleteClickListener; + private int mSerialNumber = -1; + private int mUserId = USERID_UNKNOWN; + + public UserPreference(Context context, AttributeSet attrs) { + this(context, attrs, USERID_UNKNOWN, false, null); + } + + UserPreference(Context context, AttributeSet attrs, int userId, boolean showDelete, + OnClickListener deleteListener) { + super(context, attrs); + if (showDelete) { + setWidgetLayoutResource(R.layout.preference_user_delete_widget); + mDeleteClickListener = deleteListener; + } + mUserId = userId; + } + + @Override + protected void onBindView(View view) { + view.setClickable(true); + view.setFocusable(true); + View deleteView = view.findViewById(R.id.trash_user); + if (deleteView != null) { + deleteView.setOnClickListener(mDeleteClickListener); + deleteView.setTag(this); + } + super.onBindView(view); + } + + public int getSerialNumber() { + if (mSerialNumber < 0) { + // If the userId is unknown + if (mUserId == USERID_UNKNOWN) return Integer.MAX_VALUE; + mSerialNumber = ((UserManager) getContext().getSystemService(Context.USER_SERVICE)) + .getUserSerialNumber(mUserId); + if (mSerialNumber < 0) return mUserId; + } + return mSerialNumber; + } + + public int getUserId() { + return mUserId; + } + + public int compareTo(Preference another) { + if (another instanceof UserPreference) { + return getSerialNumber() > ((UserPreference) another).getSerialNumber() ? 1 : -1; + } else { + return 1; + } + } +} diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index e5304931040..7369a9255a0 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -16,52 +16,170 @@ package com.android.settings.users; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap.CompressFormat; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import android.os.UserManager; +import android.preference.EditTextPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceActivity; import android.preference.PreferenceGroup; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Profile; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Toast; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.List; public class UserSettings extends SettingsPreferenceFragment - implements OnPreferenceClickListener { + implements OnPreferenceClickListener, OnClickListener, DialogInterface.OnDismissListener, + Preference.OnPreferenceChangeListener { + private static final String TAG = "UserSettings"; + + private static final String KEY_USER_NICKNAME = "user_nickname"; private static final String KEY_USER_LIST = "user_list"; + private static final String KEY_USER_ME = "user_me"; + private static final int MENU_ADD_USER = Menu.FIRST; + private static final int MENU_REMOVE_USER = Menu.FIRST+1; + + private static final int DIALOG_CONFIRM_REMOVE = 1; + private static final int DIALOG_ADD_USER = 2; + + private static final int MESSAGE_UPDATE_LIST = 1; + + private static final int[] USER_DRAWABLES = { + R.drawable.ic_user, + R.drawable.ic_user_cyan, + R.drawable.ic_user_green, + R.drawable.ic_user_purple, + R.drawable.ic_user_red, + R.drawable.ic_user_yellow + }; + + private static final String[] CONTACT_PROJECTION = new String[] { + Phone._ID, // 0 + Phone.DISPLAY_NAME, // 1 + }; private PreferenceGroup mUserListCategory; + private Preference mMePreference; + private EditTextPreference mNicknamePreference; + private int mRemovingUserId = -1; + private boolean mAddingUser; + + private final Object mUserLock = new Object(); + private UserManager mUserManager; + private boolean mProfileChanged; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_UPDATE_LIST: + updateUserList(); + break; + } + } + }; + + private ContentObserver mProfileObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + mProfileChanged = true; + } + }; + + private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); + } + }; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + mUserManager = (UserManager) getActivity().getSystemService(Context.USER_SERVICE); addPreferencesFromResource(R.xml.user_settings); mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST); - + mMePreference = (Preference) findPreference(KEY_USER_ME); + mMePreference.setOnPreferenceClickListener(this); + if (UserHandle.myUserId() != UserHandle.USER_OWNER) { + mMePreference.setSummary(null); + } + mNicknamePreference = (EditTextPreference) findPreference(KEY_USER_NICKNAME); + mNicknamePreference.setOnPreferenceChangeListener(this); + mNicknamePreference.setSummary(mUserManager.getUserInfo(UserHandle.myUserId()).name); + loadProfile(false); setHasOptionsMenu(true); + // Register to watch for profile changes + getActivity().getContentResolver().registerContentObserver( + ContactsContract.Profile.CONTENT_URI, false, mProfileObserver); + getActivity().registerReceiver(mUserChangeReceiver, + new IntentFilter(Intent.ACTION_USER_REMOVED)); } @Override public void onResume() { super.onResume(); + if (mProfileChanged) { + loadProfile(true); + mProfileChanged = false; + } updateUserList(); } + @Override + public void onDestroy() { + super.onDestroy(); + getActivity().getContentResolver().unregisterContentObserver(mProfileObserver); + getActivity().unregisterReceiver(mUserChangeReceiver); + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - MenuItem addAccountItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu); - addAccountItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + if (UserHandle.myUserId() == UserHandle.USER_OWNER) { + MenuItem addUserItem = menu.add(0, MENU_ADD_USER, 0, R.string.user_add_user_menu); + addUserItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } else { + MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, 0, R.string.user_remove_user_menu); + removeThisUser.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM + | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } } @Override @@ -70,52 +188,270 @@ public class UserSettings extends SettingsPreferenceFragment if (itemId == MENU_ADD_USER) { onAddUserClicked(); return true; + } else if (itemId == MENU_REMOVE_USER) { + onRemoveUserClicked(UserHandle.myUserId()); + return true; } else { return super.onOptionsItemSelected(item); } } + private void loadProfile(boolean force) { + UserInfo user = mUserManager.getUserInfo(UserHandle.myUserId()); + if (force || user.iconPath == null || user.iconPath.equals("")) { + assignProfilePhoto(user); + } + String profileName = getProfileName(); + mMePreference.setTitle(profileName); + } + private void onAddUserClicked() { - ((PreferenceActivity) getActivity()).startPreferencePanel( - UserDetailsSettings.class.getName(), null, R.string.user_details_title, - null, this, 0); + synchronized (mUserLock) { + if (mRemovingUserId == -1 && !mAddingUser) { + showDialog(DIALOG_ADD_USER); + setOnDismissListener(this); + } + } + } + + private void onRemoveUserClicked(int userId) { + synchronized (mUserLock) { + if (mRemovingUserId == -1 && !mAddingUser) { + mRemovingUserId = userId; + showDialog(DIALOG_CONFIRM_REMOVE); + setOnDismissListener(this); + } + } + } + + @Override + public Dialog onCreateDialog(int dialogId) { + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.user_confirm_remove_title) + .setMessage(R.string.user_confirm_remove_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + removeUserNow(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + case DIALOG_ADD_USER: + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.user_add_user_title) + .setMessage(R.string.user_add_user_message) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + addUserNow(); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + default: + return null; + } + } + + private void removeUserNow() { + if (mRemovingUserId == UserHandle.myUserId()) { + removeThisUser(); + } else { + new Thread() { + public void run() { + synchronized (mUserLock) { + // TODO: Show some progress while removing the user + mUserManager.removeUser(mRemovingUserId); + mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); + mRemovingUserId = -1; + } + } + }.start(); + } + } + + private void removeThisUser() { + // TODO: + Toast.makeText(getActivity(), "Not implemented yet!", Toast.LENGTH_SHORT).show(); + + synchronized (mUserLock) { + mRemovingUserId = -1; + } + } + + private void addUserNow() { + synchronized (mUserLock) { + mAddingUser = true; + updateUserList(); + new Thread() { + public void run() { + // Could take a few seconds + UserInfo user = mUserManager.createUser( + getActivity().getResources().getString(R.string.user_new_user_name), 0); + if (user != null) { + assignDefaultPhoto(user); + } + synchronized (mUserLock) { + mAddingUser = false; + mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); + } + } + }.start(); + } } private void updateUserList() { - List users = ((UserManager) getActivity().getSystemService(Context.USER_SERVICE)) - .getUsers(); + List users = mUserManager.getUsers(); mUserListCategory.removeAll(); + mUserListCategory.setOrderingAsAdded(false); + for (UserInfo user : users) { - Preference pref = new Preference(getActivity()); - pref.setTitle(user.name); - pref.setOnPreferenceClickListener(this); - pref.setKey("id=" + user.id); - if (user.iconPath != null) { - setPhotoId(pref, user.iconPath); + Preference pref; + if (user.id == UserHandle.myUserId()) { + pref = mMePreference; + } else { + pref = new UserPreference(getActivity(), null, user.id, + UserHandle.myUserId() == UserHandle.USER_OWNER, this); + pref.setOnPreferenceClickListener(this); + pref.setKey("id=" + user.id); + mUserListCategory.addPreference(pref); + if (user.id == UserHandle.USER_OWNER) { + pref.setSummary(R.string.user_owner); + } + pref.setTitle(user.name); } + if (user.iconPath != null) { + setPhotoId(pref, user); + } + } + // Add a temporary entry for the user being created + if (mAddingUser) { + Preference pref = new UserPreference(getActivity(), null, UserPreference.USERID_UNKNOWN, + false, null); + pref.setEnabled(false); + pref.setTitle(R.string.user_new_user_name); + pref.setSummary(R.string.user_adding_new_user); + pref.setIcon(R.drawable.ic_user); mUserListCategory.addPreference(pref); } } - private void setPhotoId(Preference pref, String realPath) { - Drawable d = Drawable.createFromPath(realPath); + /* TODO: Put this in an AsyncTask */ + private void assignProfilePhoto(final UserInfo user) { + // If the contact is "me", then use my local profile photo. Otherwise, build a + // uri to get the avatar of the contact. + Uri contactUri = Profile.CONTENT_URI; + + InputStream avatarDataStream = Contacts.openContactPhotoInputStream( + getActivity().getContentResolver(), + contactUri, true); + // If there's no profile photo, assign a default avatar + if (avatarDataStream == null) { + assignDefaultPhoto(user); + setPhotoId(mMePreference, user); + return; + } + + ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id); + FileOutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + byte[] buffer = new byte[4096]; + int readSize; + try { + while ((readSize = avatarDataStream.read(buffer)) > 0) { + os.write(buffer, 0, readSize); + } + os.close(); + avatarDataStream.close(); + } catch (IOException ioe) { + Log.e(TAG, "Error copying profile photo " + ioe); + } + + setPhotoId(mMePreference, user); + } + + private String getProfileName() { + Cursor cursor = getActivity().getContentResolver().query( + Profile.CONTENT_URI, CONTACT_PROJECTION, null, null, null); + if (cursor == null) { + Log.w(TAG, "getProfileName() returned NULL cursor!" + + " contact uri used " + Profile.CONTENT_URI); + return null; + } + + try { + if (cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndex(Phone.DISPLAY_NAME)); + } + } finally { + cursor.close(); + } + return null; + } + + private void assignDefaultPhoto(UserInfo user) { + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), + USER_DRAWABLES[user.id % USER_DRAWABLES.length]); + ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id); + if (fd != null) { + bitmap.compress(CompressFormat.PNG, 100, + new ParcelFileDescriptor.AutoCloseOutputStream(fd)); + } + } + + private void setPhotoId(Preference pref, UserInfo user) { + ParcelFileDescriptor fd = mUserManager.setUserIcon(user.id); + Drawable d = Drawable.createFromStream(new ParcelFileDescriptor.AutoCloseInputStream(fd), + user.iconPath); if (d == null) return; pref.setIcon(d); } + private void setUserName(String name) { + mUserManager.setUserName(UserHandle.myUserId(), name); + mNicknamePreference.setSummary(name); + } + @Override public boolean onPreferenceClick(Preference pref) { - String sid = pref.getKey(); - if (sid != null && sid.startsWith("id=")) { - int id = Integer.parseInt(sid.substring(3)); - Bundle args = new Bundle(); - args.putInt(UserDetailsSettings.EXTRA_USER_ID, id); - ((PreferenceActivity) getActivity()).startPreferencePanel( - UserDetailsSettings.class.getName(), - args, 0, pref.getTitle(), this, 0); + if (pref == mMePreference) { + Intent editProfile = new Intent(Intent.ACTION_EDIT); + editProfile.setData(ContactsContract.Profile.CONTENT_URI); + startActivity(editProfile); + } + return false; + } + + @Override + public void onClick(View v) { + if (v.getTag() instanceof UserPreference) { + int userId = ((UserPreference) v.getTag()).getUserId(); + onRemoveUserClicked(userId); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + synchronized (mUserLock) { + mAddingUser = false; + mRemovingUserId = -1; + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mNicknamePreference) { + String value = (String) newValue; + if (preference == mNicknamePreference && value != null + && value.length() > 0) { + setUserName(value); + } return true; } return false; } + }