diff options
Diffstat (limited to 'packages/SystemUI')
47 files changed, 3237 insertions, 283 deletions
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index f8f064a..69c6159 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -6,6 +6,7 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-java-files-under, src) \ src/com/android/systemui/EventLogTags.logtags +LOCAL_STATIC_JAVA_LIBRARIES := Keyguard LOCAL_JAVA_LIBRARIES := telephony-common LOCAL_PACKAGE_NAME := SystemUI @@ -14,6 +15,11 @@ LOCAL_PRIVILEGED_MODULE := true LOCAL_PROGUARD_FLAG_FILES := proguard.flags +LOCAL_RESOURCE_DIR := \ + frameworks/base/packages/Keyguard/res \ + $(LOCAL_PATH)/res +LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.keyguard + include $(BUILD_PACKAGE) include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3424eed..d371d70 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -89,11 +89,20 @@ <!-- Keyguard --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> + <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> + <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> + <uses-permission android:name="android.permission.TRUST_LISTENER" /> <!-- Wifi Display --> <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" /> <application + android:name=".SystemUIApplication" android:persistent="true" android:allowClearUserData="false" android:allowBackup="false" @@ -101,7 +110,11 @@ android:label="@string/app_label" android:icon="@*android:drawable/platlogo" android:process="com.android.systemui" - android:supportsRtl="true"> + android:supportsRtl="true" + android:theme="@style/systemui_theme"> + <!-- Keep theme in sync with SystemUIApplication.onCreate(). + Setting the theme on the application does not affect views inflated by services. + The application theme is set again from onCreate to take effect for those views. --> <!-- Broadcast receiver that gets the broadcast at boot time and starts up everything else. @@ -258,6 +271,10 @@ </intent-filter> </service> + <service + android:name=".keyguard.KeyguardService" + android:exported="true" /> + <activity android:name=".Somnambulator" android:label="@string/start_dreams" android:icon="@mipmap/ic_launcher_dreams" diff --git a/packages/SystemUI/res/drawable/ic_notify_button_bg.xml b/packages/SystemUI/res/drawable/ic_notify_button_bg.xml index 85f1ea2..3a47261 100644 --- a/packages/SystemUI/res/drawable/ic_notify_button_bg.xml +++ b/packages/SystemUI/res/drawable/ic_notify_button_bg.xml @@ -15,6 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:drawable="@*android:drawable/list_selector_pressed_holo_dark" /> - <item android:drawable="@*android:drawable/list_selector_disabled_holo_dark" /> + <item android:state_pressed="true" + android:drawable="@*android:drawable/list_selector_pressed_holo_dark" /> </selector> diff --git a/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml b/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml deleted file mode 100644 index 0947c6f..0000000 --- a/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 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. -*/ ---> - -<!-- This is the combined status bar / notification panel window. --> -<com.android.systemui.statusbar.phone.StatusBarWindowView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" - android:focusable="true" - android:descendantFocusability="afterDescendants" - android:fitsSystemWindows="true" - > - - <include layout="@layout/status_bar" - android:layout_width="match_parent" - android:layout_height="@*android:dimen/status_bar_height" - /> - - - <com.android.systemui.statusbar.phone.PanelHolder - android:id="@+id/panel_holder" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginTop="@*android:dimen/status_bar_height" - > - <include layout="@layout/status_bar_expanded" - android:layout_width="@dimen/notification_panel_width" - android:layout_height="wrap_content" - android:layout_gravity="start|top" - /> - <include layout="@layout/quick_settings" - android:layout_width="@dimen/notification_panel_width" - android:layout_height="wrap_content" - android:layout_gravity="end|top" - /> - </com.android.systemui.statusbar.phone.PanelHolder> -</com.android.systemui.statusbar.phone.StatusBarWindowView> diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index 5488a87..a6fb443 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -169,6 +169,7 @@ android:scaleType="center" android:visibility="gone" android:contentDescription="@string/accessibility_camera_button" + systemui:glowBackground="@drawable/ic_sysbar_highlight_land" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index ea6be1b..1b35537 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -27,7 +27,6 @@ android:orientation="vertical" android:focusable="true" android:descendantFocusability="afterDescendants" - android:fitsSystemWindows="true" > <ImageView diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index a7ec064..8f4417e 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -36,6 +36,14 @@ android:layout_gravity="bottom" /> + <ViewStub android:id="@+id/keyguard_flip_stub" + android:layout="@layout/status_bar_flip_button" + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_gravity="right|top" + android:layout_marginTop="@*android:dimen/status_bar_height" + android:visibility="gone" /> + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -49,6 +57,10 @@ android:layout_height="@dimen/notification_panel_header_height" /> + <include + layout="@layout/keyguard_status_view" + android:visibility="gone" /> + <TextView android:id="@+id/emergency_calls_only" android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Network.EmergencyOnly" diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml index 9aa7cfd..56523db 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml @@ -81,29 +81,10 @@ android:src="@drawable/ic_notify_clear" android:background="@drawable/ic_notify_button_bg" android:contentDescription="@string/accessibility_clear_all" - /> + /> - <FrameLayout android:id="@+id/settings_button_holder" + <include layout="@layout/status_bar_flip_button" android:layout_width="50dp" android:layout_height="50dp" - android:layout_marginStart="12dp" - > - <ImageView android:id="@+id/settings_button" - android:layout_width="50dp" - android:layout_height="50dp" - android:scaleType="center" - android:src="@drawable/ic_notify_settings" - android:background="@drawable/ic_notify_button_bg" - android:contentDescription="@string/accessibility_desc_quick_settings" - /> - <ImageView android:id="@+id/notification_button" - android:layout_width="50dp" - android:layout_height="50dp" - android:scaleType="center" - android:src="@drawable/ic_notifications" - android:background="@drawable/ic_notify_button_bg" - android:visibility="gone" - android:contentDescription="@string/accessibility_notifications_button" - /> - </FrameLayout> + android:layout_marginStart="12dp" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_flip_button.xml b/packages/SystemUI/res/layout/status_bar_flip_button.xml new file mode 100644 index 0000000..db672ea --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_flip_button.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/settings_button_holder" + android:layout_width="50dp" + android:layout_height="50dp"> + <ImageView android:id="@+id/settings_button" + android:layout_width="50dp" + android:layout_height="50dp" + android:scaleType="center" + android:src="@drawable/ic_notify_settings" + android:background="@drawable/ic_notify_button_bg" + android:contentDescription="@string/accessibility_desc_quick_settings" /> + <ImageView android:id="@+id/notification_button" + android:layout_width="50dp" + android:layout_height="50dp" + android:scaleType="center" + android:src="@drawable/ic_notifications" + android:background="@drawable/ic_notify_button_bg" + android:visibility="gone" + android:contentDescription="@string/accessibility_notifications_button" /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml new file mode 100644 index 0000000..79b03ce --- /dev/null +++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml @@ -0,0 +1,51 @@ +<!-- + ~ 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 + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + > + <com.android.systemui.statusbar.LatestItemView + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="40dp" + android:layout_marginTop="@dimen/notification_divider_height" + android:focusable="true" + android:clickable="true" + android:background="@*android:drawable/notification_quantum_bg_dim" + > + <TextView + android:id="@+id/more_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:gravity="center_horizontal" + android:textColor="@color/keyguard_overflow_content_color" + android:textAllCaps="true" + android:textAppearance="?android:attr/textAppearanceMedium" + /> + <com.android.systemui.statusbar.NotificationOverflowIconsView + android:id="@+id/overflow_icons_view" + android:layout_gravity="end|center_vertical" + android:gravity="end" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:layout_width="120dp" + android:layout_height="wrap_content" + /> + </com.android.systemui.statusbar.LatestItemView> +</FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index e74e568..d61d8b9 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -32,6 +32,7 @@ android:layout_marginTop="@dimen/notification_divider_height" android:focusable="true" android:clickable="true" + android:background="@*android:drawable/notification_quantum_bg" > <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expanded" diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 2b56618..9176d24 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -20,32 +20,29 @@ <!-- This is the combined status bar / notification panel window. --> <com.android.systemui.statusbar.phone.StatusBarWindowView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" + android:layout_width="match_parent" + android:layout_height="match_parent" android:focusable="true" - android:descendantFocusability="afterDescendants" - android:fitsSystemWindows="true" - android:background="@android:color/transparent" - > + android:descendantFocusability="afterDescendants"> <include layout="@layout/status_bar" android:layout_width="match_parent" - android:layout_height="@*android:dimen/status_bar_height" - /> + android:layout_height="@*android:dimen/status_bar_height" /> <com.android.systemui.statusbar.phone.PanelHolder android:id="@+id/panel_holder" android:layout_width="match_parent" android:layout_height="match_parent" - > + android:layout_marginTop="@dimen/panel_holder_padding_top" + android:layout_marginBottom="@*android:dimen/navigation_bar_height"> <include layout="@layout/status_bar_expanded" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> + android:layout_width="@dimen/notification_panel_width" + android:layout_height="wrap_content" + android:layout_gravity="start|top" /> <ViewStub android:id="@+id/quick_settings_stub" android:layout="@layout/quick_settings" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> + android:layout_width="@dimen/notification_panel_width" + android:layout_height="match_parent" /> </com.android.systemui.statusbar.phone.PanelHolder> </com.android.systemui.statusbar.phone.StatusBarWindowView> diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml new file mode 100644 index 0000000..bc56cf6 --- /dev/null +++ b/packages/SystemUI/res/layout/user_switcher_host.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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 + --> + +<!-- FrameLayout --> +<com.android.systemui.settings.UserSwitcherHostView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#dd000000"> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@*android:dimen/volume_panel_top" + android:background="@*android:drawable/dialog_full_holo_dark"> + <ListView android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/user_switcher_item"/> + </FrameLayout> +</com.android.systemui.settings.UserSwitcherHostView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/user_switcher_item.xml b/packages/SystemUI/res/layout/user_switcher_item.xml new file mode 100644 index 0000000..43a85e7 --- /dev/null +++ b/packages/SystemUI/res/layout/user_switcher_item.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="64dp" + android:orientation="horizontal" + tools:context=".settings.UserSwitcherDialog"> + <ImageView + android:layout_width="64dp" + android:layout_height="match_parent" + android:id="@+id/user_picture" + tools:src="@drawable/dessert_zombiegingerbread"/> + <TextView + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:id="@+id/user_name" + android:textAppearance="?android:attr/textAppearanceLarge" + android:padding="8dp" + android:gravity="center_vertical" + tools:text="Hiroshi Lockheimer" + /> +</LinearLayout> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index b77f1e0..440ead6 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -30,5 +30,5 @@ <integer name="quick_settings_user_time_settings_tile_span">1</integer> <!-- Enable the "flip settings" panel --> - <bool name="config_hasFlipSettingsPanel">false</bool> + <bool name="config_hasFlipSettingsPanel">true</bool> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 45f6af3..b4fafec 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -24,8 +24,8 @@ <dimen name="notification_panel_margin_left">16dp</dimen> <!-- Gravity for the notification & quick settings panels --> - <!-- 0x800033 = start|top ; 0x800035 = end|top --> - <integer name="notification_panel_layout_gravity">0x800033</integer> + <!-- 0x31 = top|center_horizontal ; 0x800035 = end|top --> + <integer name="notification_panel_layout_gravity">0x31</integer> <integer name="settings_panel_layout_gravity">0x800035</integer> <!-- Diameter of outer shape drawable shown in navbar search--> @@ -41,9 +41,8 @@ <dimen name="status_bar_recents_thumbnail_width">200dp</dimen> <dimen name="status_bar_recents_thumbnail_height">177dp</dimen> - <!-- On tablet-sized devices, we allocate the rightmost third(ish) of the draggable status bar - to quick settings. --> - <item type="dimen" name="settings_panel_dragzone_fraction">35%</item> + <!-- On tablets, panels drop from the statusbar instead of overlapping it. --> + <dimen name="panel_holder_padding_top">@*android:dimen/status_bar_height</dimen> <!-- Minimum fraction of the screen that should be taken up by the notification panel. --> <item type="dimen" name="notification_panel_min_height_frac">40%</item> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 5cf0453..59e8360 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -31,7 +31,8 @@ <drawable name="notification_item_background_legacy_color">#ffaaaaaa</drawable> <drawable name="heads_up_notification_bg_pressed">#ff33B5E5</drawable> <drawable name="notification_header_bg">#FF000000</drawable> - <color name="notification_panel_scrim_color">#B0000000</color> + <color name="notification_panel_scrim_color">#A0000000</color> + <color name="notification_panel_scrim_color_keyguard">#80000000</color> <color name="batterymeter_frame_color">#66FFFFFF</color><!-- 40% white --> <color name="batterymeter_charge_color">#FFFFFFFF</color> <color name="batterymeter_bolt_color">#FFFFFFFF</color> @@ -45,4 +46,7 @@ <!-- Tint color for active Quick Settings icons. --> <color name="ic_qs_on">#ffffffff</color> + + <!-- Tint color for the content on the notification overflow card. --> + <color name="keyguard_overflow_content_color">#ff666666</color> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 672c1d0..e305d94 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -114,5 +114,9 @@ <integer name="recents_filter_animate_new_views_min_duration">125</integer> <!-- The min animation duration for animating views that are newly visible. --> <integer name="recents_animate_task_bar_enter_duration">200</integer> + + <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow + card. --> + <integer name="keyguard_max_notification_count">4</integer> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2c8f9a1..5e7db8b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -167,6 +167,9 @@ <!-- Extra space above the clock in the panel --> <dimen name="notification_panel_header_padding_top">0dp</dimen> + <!-- Extra space above the panel holder --> + <dimen name="panel_holder_padding_top">0dp</dimen> + <!-- Layout parameters for the notification panel --> <dimen name="notification_panel_margin_bottom">0dp</dimen> <dimen name="notification_panel_margin_left">0dp</dimen> @@ -257,4 +260,7 @@ <!-- Width of the zen mode interstitial dialog. --> <dimen name="zen_mode_dialog_width">320dp</dimen> + + <!-- Camera affordance drag distance --> + <dimen name="camera_drag_distance">100dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ad10545..d994a5b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -534,4 +534,9 @@ </plurals> <!-- Zen mode: Summary notification content text. [CHAR LIMIT=NONE] --> <string name="zen_mode_notification_text">Touch to show</string> + + <!-- Text for overflow card on Keyguard when there is not enough space for all notifications on Keyguard. [CHAR LIMIT=12] --> + <plurals name="keyguard_more_overflow_text"> + <item quantity="other">%d more</item> + </plurals> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 76cadd7..c2d584b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -169,5 +169,7 @@ <!-- Note: must be dp to fit in status bar --> <item name="android:textSize">14dp</item> </style> - + + <style name="systemui_theme" parent="@android:style/Theme.DeviceDefault" /> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java new file mode 100644 index 0000000..0f55683 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -0,0 +1,111 @@ +/* + * 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; + +import android.app.Application; +import android.content.res.Configuration; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; + +/** + * Application class for SystemUI. + */ +public class SystemUIApplication extends Application { + + private static final String TAG = "SystemUIService"; + private static final boolean DEBUG = false; + + /** + * The classes of the stuff to start. + */ + private final Class<?>[] SERVICES = new Class[] { + com.android.systemui.keyguard.KeyguardViewMediator.class, + com.android.systemui.recent.Recents.class, + com.android.systemui.statusbar.SystemBars.class, + com.android.systemui.usb.StorageNotification.class, + com.android.systemui.power.PowerUI.class, + com.android.systemui.media.RingtonePlayer.class, + com.android.systemui.settings.SettingsUI.class, + }; + + /** + * Hold a reference on the stuff we start. + */ + private final SystemUI[] mServices = new SystemUI[SERVICES.length]; + private boolean mServicesStarted; + private final Map<Class<?>, Object> mComponents = new HashMap<Class<?>, Object>(); + + @Override + public void onCreate() { + super.onCreate(); + // Set the application theme that is inherited by all services. Note that setting the + // application theme in the manifest does only work for activities. Keep this in sync with + // the theme set there. + setTheme(R.style.systemui_theme); + } + + /** + * Makes sure that all the SystemUI services are running. If they are already running, this is a + * no-op. This is needed to conditinally start all the services, as we only need to have it in + * the main process. + * + * <p>This method must only be called from the main thread.</p> + */ + public void startServicesIfNeeded() { + if (mServicesStarted) { + return; + } + final int N = SERVICES.length; + for (int i=0; i<N; i++) { + Class<?> cl = SERVICES[i]; + if (DEBUG) Log.d(TAG, "loading: " + cl); + try { + mServices[i] = (SystemUI)cl.newInstance(); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } catch (InstantiationException ex) { + throw new RuntimeException(ex); + } + mServices[i].mContext = this; + mServices[i].mComponents = mComponents; + if (DEBUG) Log.d(TAG, "running: " + mServices[i]); + mServices[i].start(); + } + mServicesStarted = true; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (mServicesStarted) { + int len = mServices.length; + for (int i = 0; i < len; i++) { + mServices[i].onConfigurationChanged(newConfig); + } + } + } + + @SuppressWarnings("unchecked") + public <T> T getComponent(Class<T> interfaceType) { + return (T) mComponents.get(interfaceType); + } + + public SystemUI[] getServices() { + return mServices; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index ca5f7d1..05e5f6b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -18,65 +18,19 @@ package com.android.systemui; import android.app.Service; import android.content.Intent; -import android.content.res.Configuration; import android.os.IBinder; -import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.HashMap; public class SystemUIService extends Service { - private static final String TAG = "SystemUIService"; - - /** - * The classes of the stuff to start. - */ - private final Class<?>[] SERVICES = new Class[] { - com.android.systemui.recent.Recents.class, - com.android.systemui.statusbar.SystemBars.class, - com.android.systemui.usb.StorageNotification.class, - com.android.systemui.power.PowerUI.class, - com.android.systemui.media.RingtonePlayer.class, - com.android.systemui.settings.SettingsUI.class, - }; - - /** - * Hold a reference on the stuff we start. - */ - private final SystemUI[] mServices = new SystemUI[SERVICES.length]; @Override public void onCreate() { - HashMap<Class<?>, Object> components = new HashMap<Class<?>, Object>(); - final int N = SERVICES.length; - for (int i=0; i<N; i++) { - Class<?> cl = SERVICES[i]; - Log.d(TAG, "loading: " + cl); - try { - mServices[i] = (SystemUI)cl.newInstance(); - } catch (IllegalAccessException ex) { - throw new RuntimeException(ex); - } catch (InstantiationException ex) { - throw new RuntimeException(ex); - } - mServices[i].mContext = this; - mServices[i].mComponents = components; - Log.d(TAG, "running: " + mServices[i]); - mServices[i].start(); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - for (SystemUI ui: mServices) { - ui.onConfigurationChanged(newConfig); - } + super.onCreate(); + ((SystemUIApplication) getApplication()).startServicesIfNeeded(); } - /** - * Nobody binds to us. - */ @Override public IBinder onBind(Intent intent) { return null; @@ -84,14 +38,15 @@ public class SystemUIService extends Service { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + SystemUI[] services = ((SystemUIApplication) getApplication()).getServices(); if (args == null || args.length == 0) { - for (SystemUI ui: mServices) { + for (SystemUI ui: services) { pw.println("dumping service: " + ui.getClass().getName()); ui.dump(fd, pw, args); } } else { String svc = args[0]; - for (SystemUI ui: mServices) { + for (SystemUI ui: services) { String name = ui.getClass().getName(); if (name.endsWith(svc)) { ui.dump(fd, pw, args); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java new file mode 100644 index 0000000..41c0e78 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -0,0 +1,207 @@ +/* + * 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.keyguard; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.Debug; +import android.os.IBinder; +import android.util.Log; +import android.view.MotionEvent; + +import com.android.internal.policy.IKeyguardExitCallback; +import com.android.internal.policy.IKeyguardService; +import com.android.internal.policy.IKeyguardServiceConstants; +import com.android.internal.policy.IKeyguardShowCallback; +import com.android.systemui.SystemUIApplication; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +public class KeyguardService extends Service { + static final String TAG = "KeyguardService"; + static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD; + + private KeyguardViewMediator mKeyguardViewMediator; + + @Override + public void onCreate() { + ((SystemUIApplication) getApplication()).startServicesIfNeeded(); + mKeyguardViewMediator = + ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + void checkPermission() { + if (getBaseContext().checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) { + Log.w(TAG, "Caller needs permission '" + PERMISSION + "' to call " + Debug.getCaller()); + throw new SecurityException("Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + PERMISSION); + } + } + + private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() { + + private boolean mIsOccluded; + + @Override + public boolean isShowing() { + return mKeyguardViewMediator.isShowing(); + } + + @Override + public boolean isSecure() { + return mKeyguardViewMediator.isSecure(); + } + + @Override + public boolean isShowingAndNotOccluded() { + return mKeyguardViewMediator.isShowingAndNotOccluded(); + } + + @Override + public boolean isInputRestricted() { + return mKeyguardViewMediator.isInputRestricted(); + } + + @Override + public void verifyUnlock(IKeyguardExitCallback callback) { + checkPermission(); + mKeyguardViewMediator.verifyUnlock(callback); + } + + @Override + public void keyguardDone(boolean authenticated, boolean wakeup) { + checkPermission(); + mKeyguardViewMediator.keyguardDone(authenticated, wakeup); + } + + @Override + public int setOccluded(boolean isOccluded) { + checkPermission(); + synchronized (this) { + int result; + if (isOccluded && mKeyguardViewMediator.isShowing() + && !mIsOccluded) { + result = IKeyguardServiceConstants + .KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_UNSET_FLAGS; + } else if (!isOccluded && mKeyguardViewMediator.isShowing() + && mIsOccluded) { + result = IKeyguardServiceConstants + .KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_SET_FLAGS; + } else { + result = IKeyguardServiceConstants.KEYGUARD_SERVICE_SET_OCCLUDED_RESULT_NONE; + } + if (mIsOccluded != isOccluded) { + mKeyguardViewMediator.setOccluded(isOccluded); + + // Cache the value so we always have a fresh view in whether Keyguard is occluded. + // If we would just call mKeyguardViewMediator.isOccluded(), this might be stale + // because that value gets updated in another thread. + mIsOccluded = isOccluded; + } + return result; + } + } + + @Override + public void dismiss() { + checkPermission(); + mKeyguardViewMediator.dismiss(); + } + + @Override + public void onDreamingStarted() { + checkPermission(); + mKeyguardViewMediator.onDreamingStarted(); + } + + @Override + public void onDreamingStopped() { + checkPermission(); + mKeyguardViewMediator.onDreamingStopped(); + } + + @Override + public void onScreenTurnedOff(int reason) { + checkPermission(); + mKeyguardViewMediator.onScreenTurnedOff(reason); + } + + @Override + public void onScreenTurnedOn(IKeyguardShowCallback callback) { + checkPermission(); + mKeyguardViewMediator.onScreenTurnedOn(callback); + } + + @Override + public void setKeyguardEnabled(boolean enabled) { + checkPermission(); + mKeyguardViewMediator.setKeyguardEnabled(enabled); + } + + @Override + public boolean isDismissable() { + return mKeyguardViewMediator.isDismissable(); + } + + @Override + public void onSystemReady() { + checkPermission(); + mKeyguardViewMediator.onSystemReady(); + } + + @Override + public void doKeyguardTimeout(Bundle options) { + checkPermission(); + mKeyguardViewMediator.doKeyguardTimeout(options); + } + + @Override + public void setCurrentUser(int userId) { + checkPermission(); + mKeyguardViewMediator.setCurrentUser(userId); + } + + @Override + public void showAssistant() { + checkPermission(); + } + + @Override + public void dispatch(MotionEvent event) { + checkPermission(); + } + + @Override + public void launchCamera() { + checkPermission(); + } + + @Override + public void onBootCompleted() { + checkPermission(); + mKeyguardViewMediator.onBootCompleted(); + } + }; +} + diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java new file mode 100644 index 0000000..bb39d36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -0,0 +1,1369 @@ +/* + * 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.keyguard; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.SearchManager; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.SoundPool; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; + +import com.android.internal.policy.IKeyguardExitCallback; +import com.android.internal.policy.IKeyguardShowCallback; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardDisplayManager; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.keyguard.MultiUserAvatarCache; +import com.android.keyguard.ViewMediatorCallback; +import com.android.keyguard.analytics.KeyguardAnalytics; +import com.android.keyguard.analytics.Session; +import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.phone.PhoneStatusBar; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarWindowManager; + +import java.io.File; + +import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; +import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapter; + + +/** + * Mediates requests related to the keyguard. This includes queries about the + * state of the keyguard, power management events that effect whether the keyguard + * should be shown or reset, callbacks to the phone window manager to notify + * it of when the keyguard is showing, and events from the keyguard view itself + * stating that the keyguard was succesfully unlocked. + * + * Note that the keyguard view is shown when the screen is off (as appropriate) + * so that once the screen comes on, it will be ready immediately. + * + * Example queries about the keyguard: + * - is {movement, key} one that should wake the keygaurd? + * - is the keyguard showing? + * - are input events restricted due to the state of the keyguard? + * + * Callbacks to the phone window manager: + * - the keyguard is showing + * + * Example external events that translate to keyguard view changes: + * - screen turned off -> reset the keyguard, and show it so it will be ready + * next time the screen turns on + * - keyboard is slid open -> if the keyguard is not secure, hide it + * + * Events from the keyguard view: + * - user succesfully unlocked keyguard -> hide keyguard view, and no longer + * restrict input events. + * + * Note: in addition to normal power managment events that effect the state of + * whether the keyguard should be showing, external apps and services may request + * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When + * false, this will override all other conditions for turning on the keyguard. + * + * Threading and synchronization: + * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy}, + * and runs on its thread. The keyguard UI is created from that thread in the + * constructor of this class. The apis may be called from other threads, including the + * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s. + * Therefore, methods on this class are synchronized, and any action that is pointed + * directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI + * thread of the keyguard. + */ +public class KeyguardViewMediator extends SystemUI { + private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; + final static boolean DEBUG = false; + private static final boolean ENABLE_ANALYTICS = Build.IS_DEBUGGABLE; + private final static boolean DBG_WAKE = false; + + private final static String TAG = "KeyguardViewMediator"; + + private static final String DELAYED_KEYGUARD_ACTION = + "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"; + + // used for handler messages + private static final int SHOW = 2; + private static final int HIDE = 3; + private static final int RESET = 4; + private static final int VERIFY_UNLOCK = 5; + private static final int NOTIFY_SCREEN_OFF = 6; + private static final int NOTIFY_SCREEN_ON = 7; + private static final int KEYGUARD_DONE = 9; + private static final int KEYGUARD_DONE_DRAWING = 10; + private static final int KEYGUARD_DONE_AUTHENTICATING = 11; + private static final int SET_OCCLUDED = 12; + private static final int KEYGUARD_TIMEOUT = 13; + private static final int DISMISS = 17; + + /** + * The default amount of time we stay awake (used for all key input) + */ + public static final int AWAKE_INTERVAL_DEFAULT_MS = 10000; + + /** + * How long to wait after the screen turns off due to timeout before + * turning on the keyguard (i.e, the user has this much time to turn + * the screen back on without having to face the keyguard). + */ + private static final int KEYGUARD_LOCK_AFTER_DELAY_DEFAULT = 5000; + + /** + * How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()} + * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)} + * that is reenabling the keyguard. + */ + private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000; + + /** + * Allow the user to expand the status bar when the keyguard is engaged + * (without a pattern or password). + */ + private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true; + + /** + * Allow the user to expand the status bar when a SECURE keyguard is engaged + * and {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS} is set + * (private notifications will be masked). + */ + private static final boolean ENABLE_SECURE_STATUS_BAR_EXPAND = true; + + /** + * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}. + */ + private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false; + + /** + * Secure setting whether analytics are collected on the keyguard. + */ + private static final String KEYGUARD_ANALYTICS_SETTING = "keyguard_analytics"; + + /** The stream type that the lock sounds are tied to. */ + private int mMasterStreamType; + + private AlarmManager mAlarmManager; + private AudioManager mAudioManager; + private StatusBarManager mStatusBarManager; + private boolean mSwitchingUser; + + private boolean mSystemReady; + + // Whether the next call to playSounds() should be skipped. Defaults to + // true because the first lock (on boot) should be silent. + private boolean mSuppressNextLockSound = true; + + + /** High level access to the power manager for WakeLocks */ + private PowerManager mPM; + + /** UserManager for querying number of users */ + private UserManager mUserManager; + + /** SearchManager for determining whether or not search assistant is available */ + private SearchManager mSearchManager; + + /** + * Used to keep the device awake while to ensure the keyguard finishes opening before + * we sleep. + */ + private PowerManager.WakeLock mShowKeyguardWakeLock; + + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + + private KeyguardAnalytics mKeyguardAnalytics; + + // these are protected by synchronized (this) + + /** + * External apps (like the phone app) can tell us to disable the keygaurd. + */ + private boolean mExternallyEnabled = true; + + /** + * Remember if an external call to {@link #setKeyguardEnabled} with value + * false caused us to hide the keyguard, so that we need to reshow it once + * the keygaurd is reenabled with another call with value true. + */ + private boolean mNeedToReshowWhenReenabled = false; + + // cached value of whether we are showing (need to know this to quickly + // answer whether the input should be restricted) + private boolean mShowing; + + // true if the keyguard is hidden by another window + private boolean mOccluded = false; + + /** + * Helps remember whether the screen has turned on since the last time + * it turned off due to timeout. see {@link #onScreenTurnedOff(int)} + */ + private int mDelayedShowingSequence; + + /** + * If the user has disabled the keyguard, then requests to exit, this is + * how we'll ultimately let them know whether it was successful. We use this + * var being non-null as an indicator that there is an in progress request. + */ + private IKeyguardExitCallback mExitSecureCallback; + + // the properties of the keyguard + + private KeyguardUpdateMonitor mUpdateMonitor; + + private boolean mScreenOn; + + // last known state of the cellular connection + private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE; + + /** + * we send this intent when the keyguard is dismissed. + */ + private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT) + .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING + | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + + /** + * {@link #setKeyguardEnabled} waits on this condition when it reenables + * the keyguard. + */ + private boolean mWaitingUntilKeyguardVisible = false; + private LockPatternUtils mLockPatternUtils; + private boolean mKeyguardDonePending = false; + + private SoundPool mLockSounds; + private int mLockSoundId; + private int mUnlockSoundId; + private int mLockSoundStreamId; + + /** + * Tracks value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}. + */ + private boolean mAllowNotificationsWhenSecure; + + /** + * The volume applied to the lock/unlock sounds. + */ + private float mLockSoundVolume; + + /** + * For managing external displays + */ + private KeyguardDisplayManager mKeyguardDisplayManager; + + KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { + + @Override + public void onUserSwitching(int userId) { + // Note that the mLockPatternUtils user has already been updated from setCurrentUser. + // We need to force a reset of the views, since lockNow (called by + // ActivityManagerService) will not reconstruct the keyguard if it is already showing. + synchronized (KeyguardViewMediator.this) { + mSwitchingUser = true; + resetStateLocked(); + adjustStatusBarLocked(); + // When we switch users we want to bring the new user to the biometric unlock even + // if the current user has gone to the backup. + KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); + } + } + + @Override + public void onUserSwitchComplete(int userId) { + mSwitchingUser = false; + } + + @Override + public void onUserRemoved(int userId) { + mLockPatternUtils.removeUser(userId); + MultiUserAvatarCache.getInstance().clear(userId); + } + + @Override + public void onUserInfoChanged(int userId) { + MultiUserAvatarCache.getInstance().clear(userId); + } + + @Override + public void onPhoneStateChanged(int phoneState) { + synchronized (KeyguardViewMediator.this) { + if (TelephonyManager.CALL_STATE_IDLE == phoneState // call ending + && !mScreenOn // screen off + && mExternallyEnabled) { // not disabled by any app + + // note: this is a way to gracefully reenable the keyguard when the call + // ends and the screen is off without always reenabling the keyguard + // each time the screen turns off while in call (and having an occasional ugly + // flicker while turning back on the screen and disabling the keyguard again). + if (DEBUG) Log.d(TAG, "screen is off and call ended, let's make sure the " + + "keyguard is showing"); + doKeyguardLocked(null); + } + } + } + + @Override + public void onClockVisibilityChanged() { + adjustStatusBarLocked(); + } + + @Override + public void onDeviceProvisioned() { + sendUserPresentBroadcast(); + } + + @Override + public void onSimStateChanged(IccCardConstants.State simState) { + if (DEBUG) Log.d(TAG, "onSimStateChanged: " + simState); + + switch (simState) { + case NOT_READY: + case ABSENT: + // only force lock screen in case of missing sim if user hasn't + // gone through setup wizard + synchronized (this) { + if (!mUpdateMonitor.isDeviceProvisioned()) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "ICC_ABSENT isn't showing," + + " we need to show the keyguard since the " + + "device isn't provisioned yet."); + doKeyguardLocked(null); + } else { + resetStateLocked(); + } + } + } + break; + case PIN_REQUIRED: + case PUK_REQUIRED: + synchronized (this) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't " + + "showing; need to show keyguard so user can enter sim pin"); + doKeyguardLocked(null); + } else { + resetStateLocked(); + } + } + break; + case PERM_DISABLED: + synchronized (this) { + if (!isShowing()) { + if (DEBUG) Log.d(TAG, "PERM_DISABLED and " + + "keygaurd isn't showing."); + doKeyguardLocked(null); + } else { + if (DEBUG) Log.d(TAG, "PERM_DISABLED, resetStateLocked to" + + "show permanently disabled message in lockscreen."); + resetStateLocked(); + } + } + break; + case READY: + synchronized (this) { + if (isShowing()) { + resetStateLocked(); + } + } + break; + } + } + + }; + + ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { + + public void userActivity() { + KeyguardViewMediator.this.userActivity(); + } + + public void userActivity(long holdMs) { + KeyguardViewMediator.this.userActivity(holdMs); + } + + public void keyguardDone(boolean authenticated) { + KeyguardViewMediator.this.keyguardDone(authenticated, true); + } + + public void keyguardDoneDrawing() { + mHandler.sendEmptyMessage(KEYGUARD_DONE_DRAWING); + } + + @Override + public void setNeedsInput(boolean needsInput) { + mStatusBarKeyguardViewManager.setNeedsInput(needsInput); + } + + @Override + public void onUserActivityTimeoutChanged() { + mStatusBarKeyguardViewManager.updateUserActivityTimeout(); + } + + @Override + public void keyguardDonePending() { + mKeyguardDonePending = true; + } + + @Override + public void keyguardGone() { + mKeyguardDisplayManager.hide(); + } + }; + + private void userActivity() { + userActivity(AWAKE_INTERVAL_DEFAULT_MS); + } + + public void userActivity(long holdMs) { + // We ignore the hold time. Eventually we should remove it. + // Instead, the keyguard window has an explicit user activity timeout set on it. + mPM.userActivity(SystemClock.uptimeMillis(), false); + } + + private void setup() { + mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard"); + mShowKeyguardWakeLock.setReferenceCounted(false); + + mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION)); + + mKeyguardDisplayManager = new KeyguardDisplayManager(mContext); + + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + + mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); + + mLockPatternUtils = new LockPatternUtils(mContext); + mLockPatternUtils.setCurrentUser(UserHandle.USER_OWNER); + + // Assume keyguard is showing (unless it's disabled) until we know for sure... + mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure()) + && !mLockPatternUtils.isLockScreenDisabled(); + + mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(mContext, + mViewMediatorCallback, mLockPatternUtils); + final ContentResolver cr = mContext.getContentResolver(); + + if (ENABLE_ANALYTICS && !LockPatternUtils.isSafeModeEnabled() && + Settings.Secure.getInt(cr, KEYGUARD_ANALYTICS_SETTING, 0) == 1) { + mKeyguardAnalytics = new KeyguardAnalytics(mContext, new SessionTypeAdapter() { + + @Override + public int getSessionType() { + return mLockPatternUtils.isSecure() && !mUpdateMonitor.getUserHasTrust( + mLockPatternUtils.getCurrentUser()) + ? Session.TYPE_KEYGUARD_SECURE + : Session.TYPE_KEYGUARD_INSECURE; + } + }, new File(mContext.getCacheDir(), "keyguard_analytics.bin")); + } else { + mKeyguardAnalytics = null; + } + + mScreenOn = mPM.isScreenOn(); + + mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); + String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND); + if (soundPath != null) { + mLockSoundId = mLockSounds.load(soundPath, 1); + } + if (soundPath == null || mLockSoundId == 0) { + Log.w(TAG, "failed to load lock sound from " + soundPath); + } + soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND); + if (soundPath != null) { + mUnlockSoundId = mLockSounds.load(soundPath, 1); + } + if (soundPath == null || mUnlockSoundId == 0) { + Log.w(TAG, "failed to load unlock sound from " + soundPath); + } + int lockSoundDefaultAttenuation = mContext.getResources().getInteger( + com.android.internal.R.integer.config_lockSoundVolumeDb); + mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20); + } + + @Override + public void start() { + setup(); + putComponent(KeyguardViewMediator.class, this); + } + + /** + * Let us know that the system is ready after startup. + */ + public void onSystemReady() { + mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + synchronized (this) { + if (DEBUG) Log.d(TAG, "onSystemReady"); + mSystemReady = true; + mUpdateMonitor.registerCallback(mUpdateCallback); + + // Suppress biometric unlock right after boot until things have settled if it is the + // selected security method, otherwise unsuppress it. It must be unsuppressed if it is + // not the selected security method for the following reason: if the user starts + // without a screen lock selected, the biometric unlock would be suppressed the first + // time they try to use it. + // + // Note that the biometric unlock will still not show if it is not the selected method. + // Calling setAlternateUnlockEnabled(true) simply says don't suppress it if it is the + // selected method. + if (mLockPatternUtils.usingBiometricWeak() + && mLockPatternUtils.isBiometricWeakInstalled()) { + if (DEBUG) Log.d(TAG, "suppressing biometric unlock during boot"); + mUpdateMonitor.setAlternateUnlockEnabled(false); + } else { + mUpdateMonitor.setAlternateUnlockEnabled(true); + } + + doKeyguardLocked(null); + } + // Most services aren't available until the system reaches the ready state, so we + // send it here when the device first boots. + maybeSendUserPresentBroadcast(); + } + + /** + * Called to let us know the screen was turned off. + * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER}, + * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT} or + * {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_PROX_SENSOR}. + */ + public void onScreenTurnedOff(int why) { + synchronized (this) { + mScreenOn = false; + if (DEBUG) Log.d(TAG, "onScreenTurnedOff(" + why + ")"); + + mKeyguardDonePending = false; + + // Lock immediately based on setting if secure (user has a pin/pattern/password). + // This also "locks" the device when not secure to provide easy access to the + // camera while preventing unwanted input. + final boolean lockImmediately = + mLockPatternUtils.getPowerButtonInstantlyLocks() || !mLockPatternUtils.isSecure(); + + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled"); + try { + mExitSecureCallback.onKeyguardExitResult(false); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e); + } + mExitSecureCallback = null; + if (!mExternallyEnabled) { + hideLocked(); + } + } else if (mShowing) { + notifyScreenOffLocked(); + resetStateLocked(); + } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT + || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) { + doKeyguardLaterLocked(); + } else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) { + // Do not enable the keyguard if the prox sensor forced the screen off. + } else { + doKeyguardLocked(null); + } + if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { + mKeyguardAnalytics.getCallback().onScreenOff(); + } + } + KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why); + } + + private void doKeyguardLaterLocked() { + // if the screen turned off because of timeout or the user hit the power button + // and we don't need to lock immediately, set an alarm + // to enable it a little bit later (i.e, give the user a chance + // to turn the screen back on within a certain window without + // having to unlock the screen) + final ContentResolver cr = mContext.getContentResolver(); + + // From DisplaySettings + long displayTimeout = Settings.System.getInt(cr, SCREEN_OFF_TIMEOUT, + KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT); + + // From SecuritySettings + final long lockAfterTimeout = Settings.Secure.getInt(cr, + Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, + KEYGUARD_LOCK_AFTER_DELAY_DEFAULT); + + // From DevicePolicyAdmin + final long policyTimeout = mLockPatternUtils.getDevicePolicyManager() + .getMaximumTimeToLock(null, mLockPatternUtils.getCurrentUser()); + + long timeout; + if (policyTimeout > 0) { + // policy in effect. Make sure we don't go beyond policy limit. + displayTimeout = Math.max(displayTimeout, 0); // ignore negative values + timeout = Math.min(policyTimeout - displayTimeout, lockAfterTimeout); + } else { + timeout = lockAfterTimeout; + } + + if (timeout <= 0) { + // Lock now + mSuppressNextLockSound = true; + doKeyguardLocked(null); + } else { + // Lock in the future + long when = SystemClock.elapsedRealtime() + timeout; + Intent intent = new Intent(DELAYED_KEYGUARD_ACTION); + intent.putExtra("seq", mDelayedShowingSequence); + PendingIntent sender = PendingIntent.getBroadcast(mContext, + 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender); + if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = " + + mDelayedShowingSequence); + } + } + + private void cancelDoKeyguardLaterLocked() { + mDelayedShowingSequence++; + } + + /** + * Let's us know the screen was turned on. + */ + public void onScreenTurnedOn(IKeyguardShowCallback callback) { + synchronized (this) { + mScreenOn = true; + cancelDoKeyguardLaterLocked(); + if (DEBUG) Log.d(TAG, "onScreenTurnedOn, seq = " + mDelayedShowingSequence); + if (callback != null) { + notifyScreenOnLocked(callback); + } + } + KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurnedOn(); + maybeSendUserPresentBroadcast(); + } + + private void maybeSendUserPresentBroadcast() { + if (mSystemReady && mLockPatternUtils.isLockScreenDisabled() + && !mUserManager.isUserSwitcherEnabled()) { + // Lock screen is disabled because the user has set the preference to "None". + // In this case, send out ACTION_USER_PRESENT here instead of in + // handleKeyguardDone() + sendUserPresentBroadcast(); + } + } + + /** + * A dream started. We should lock after the usual screen-off lock timeout but only + * if there is a secure lock pattern. + */ + public void onDreamingStarted() { + synchronized (this) { + if (mScreenOn && mLockPatternUtils.isSecure()) { + doKeyguardLaterLocked(); + } + } + } + + /** + * A dream stopped. + */ + public void onDreamingStopped() { + synchronized (this) { + if (mScreenOn) { + cancelDoKeyguardLaterLocked(); + } + } + } + + /** + * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide + * a way for external stuff to override normal keyguard behavior. For instance + * the phone app disables the keyguard when it receives incoming calls. + */ + public void setKeyguardEnabled(boolean enabled) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")"); + + mExternallyEnabled = enabled; + + if (!enabled && mShowing) { + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring"); + // we're in the process of handling a request to verify the user + // can get past the keyguard. ignore extraneous requests to disable / reenable + return; + } + + // hiding keyguard that is showing, remember to reshow later + if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, " + + "disabling status bar expansion"); + mNeedToReshowWhenReenabled = true; + hideLocked(); + } else if (enabled && mNeedToReshowWhenReenabled) { + // reenabled after previously hidden, reshow + if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling " + + "status bar expansion"); + mNeedToReshowWhenReenabled = false; + + if (mExitSecureCallback != null) { + if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting"); + try { + mExitSecureCallback.onKeyguardExitResult(false); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e); + } + mExitSecureCallback = null; + resetStateLocked(); + } else { + showLocked(null); + + // block until we know the keygaurd is done drawing (and post a message + // to unblock us after a timeout so we don't risk blocking too long + // and causing an ANR). + mWaitingUntilKeyguardVisible = true; + mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS); + if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false"); + while (mWaitingUntilKeyguardVisible) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible"); + } + } + } + } + + /** + * @see android.app.KeyguardManager#exitKeyguardSecurely + */ + public void verifyUnlock(IKeyguardExitCallback callback) { + synchronized (this) { + if (DEBUG) Log.d(TAG, "verifyUnlock"); + if (!mUpdateMonitor.isDeviceProvisioned()) { + // don't allow this api when the device isn't provisioned + if (DEBUG) Log.d(TAG, "ignoring because device isn't provisioned"); + try { + callback.onKeyguardExitResult(false); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e); + } + } else if (mExternallyEnabled) { + // this only applies when the user has externally disabled the + // keyguard. this is unexpected and means the user is not + // using the api properly. + Log.w(TAG, "verifyUnlock called when not externally disabled"); + try { + callback.onKeyguardExitResult(false); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e); + } + } else if (mExitSecureCallback != null) { + // already in progress with someone else + try { + callback.onKeyguardExitResult(false); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e); + } + } else { + mExitSecureCallback = callback; + verifyUnlockLocked(); + } + } + } + + /** + * Is the keyguard currently showing? + */ + public boolean isShowing() { + return mShowing; + } + + public boolean isOccluded() { + return mOccluded; + } + + /** + * Is the keyguard currently showing and not being force hidden? + */ + public boolean isShowingAndNotOccluded() { + return mShowing && !mOccluded; + } + + /** + * Notify us when the keyguard is occluded by another window + */ + public void setOccluded(boolean isOccluded) { + if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded); + mUpdateMonitor.sendKeyguardVisibilityChanged(!isOccluded); + mHandler.removeMessages(SET_OCCLUDED); + Message msg = mHandler.obtainMessage(SET_OCCLUDED, (isOccluded ? 1 : 0), 0); + mHandler.sendMessage(msg); + } + + /** + * Handles SET_OCCLUDED message sent by setOccluded() + */ + private void handleSetOccluded(boolean isOccluded) { + synchronized (KeyguardViewMediator.this) { + if (mOccluded != isOccluded) { + mOccluded = isOccluded; + mStatusBarKeyguardViewManager.setOccluded(isOccluded); + updateActivityLockScreenState(); + adjustStatusBarLocked(); + } + if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { + mKeyguardAnalytics.getCallback().onSetOccluded(isOccluded); + } + } + } + + /** + * Used by PhoneWindowManager to enable the keyguard due to a user activity timeout. + * This must be safe to call from any thread and with any window manager locks held. + */ + public void doKeyguardTimeout(Bundle options) { + mHandler.removeMessages(KEYGUARD_TIMEOUT); + Message msg = mHandler.obtainMessage(KEYGUARD_TIMEOUT, options); + mHandler.sendMessage(msg); + } + + /** + * Given the state of the keyguard, is the input restricted? + * Input is restricted when the keyguard is showing, or when the keyguard + * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet. + */ + public boolean isInputRestricted() { + return mShowing || mNeedToReshowWhenReenabled || !mUpdateMonitor.isDeviceProvisioned(); + } + + /** + * Enable the keyguard if the settings are appropriate. + */ + private void doKeyguardLocked(Bundle options) { + // if another app is disabling us, don't show + if (!mExternallyEnabled) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); + + // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes + // for an occasional ugly flicker in this situation: + // 1) receive a call with the screen on (no keyguard) or make a call + // 2) screen times out + // 3) user hits key to turn screen back on + // instead, we reenable the keyguard when we know the screen is off and the call + // ends (see the broadcast receiver below) + // TODO: clean this up when we have better support at the window manager level + // for apps that wish to be on top of the keyguard + return; + } + + // note whether notification access should be allowed + mAllowNotificationsWhenSecure = ENABLE_SECURE_STATUS_BAR_EXPAND + && 0 != Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, + ALLOW_NOTIFICATIONS_DEFAULT ? 1 : 0); + + // if the keyguard is already showing, don't bother + if (mStatusBarKeyguardViewManager.isShowing()) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing"); + return; + } + + // if the setup wizard hasn't run yet, don't show + final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", + false); + final boolean provisioned = mUpdateMonitor.isDeviceProvisioned(); + final IccCardConstants.State state = mUpdateMonitor.getSimState(); + final boolean lockedOrMissing = state.isPinLocked() + || ((state == IccCardConstants.State.ABSENT + || state == IccCardConstants.State.PERM_DISABLED) + && requireSim); + + if (!lockedOrMissing && !provisioned) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned" + + " and the sim is not locked or missing"); + return; + } + + if (!mUserManager.isUserSwitcherEnabled() + && mLockPatternUtils.isLockScreenDisabled() && !lockedOrMissing) { + if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off"); + return; + } + + if (mLockPatternUtils.checkVoldPassword()) { + if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted"); + // Without this, settings is not enabled until the lock screen first appears + hideLocked(); + return; + } + + if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen"); + showLocked(options); + } + + /** + * Dismiss the keyguard through the security layers. + */ + public void handleDismiss() { + if (mShowing && !mOccluded) { + mStatusBarKeyguardViewManager.dismiss(); + } + } + + public void dismiss() { + mHandler.sendEmptyMessage(DISMISS); + } + + /** + * Send message to keyguard telling it to reset its state. + * @see #handleReset + */ + private void resetStateLocked() { + if (DEBUG) Log.e(TAG, "resetStateLocked"); + Message msg = mHandler.obtainMessage(RESET); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to verify unlock + * @see #handleVerifyUnlock() + */ + private void verifyUnlockLocked() { + if (DEBUG) Log.d(TAG, "verifyUnlockLocked"); + mHandler.sendEmptyMessage(VERIFY_UNLOCK); + } + + + /** + * Send a message to keyguard telling it the screen just turned on. + * @see #onScreenTurnedOff(int) + * @see #handleNotifyScreenOff + */ + private void notifyScreenOffLocked() { + if (DEBUG) Log.d(TAG, "notifyScreenOffLocked"); + mHandler.sendEmptyMessage(NOTIFY_SCREEN_OFF); + } + + /** + * Send a message to keyguard telling it the screen just turned on. + * @see #onScreenTurnedOn + * @see #handleNotifyScreenOn + */ + private void notifyScreenOnLocked(IKeyguardShowCallback result) { + if (DEBUG) Log.d(TAG, "notifyScreenOnLocked"); + Message msg = mHandler.obtainMessage(NOTIFY_SCREEN_ON, result); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to show itself + * @see #handleShow + */ + private void showLocked(Bundle options) { + if (DEBUG) Log.d(TAG, "showLocked"); + // ensure we stay awake until we are finished displaying the keyguard + mShowKeyguardWakeLock.acquire(); + Message msg = mHandler.obtainMessage(SHOW, options); + mHandler.sendMessage(msg); + } + + /** + * Send message to keyguard telling it to hide itself + * @see #handleHide() + */ + private void hideLocked() { + if (DEBUG) Log.d(TAG, "hideLocked"); + Message msg = mHandler.obtainMessage(HIDE); + mHandler.sendMessage(msg); + } + + public boolean isSecure() { + return mLockPatternUtils.isSecure() + || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure(); + } + + /** + * Update the newUserId. Call while holding WindowManagerService lock. + * NOTE: Should only be called by KeyguardViewMediator in response to the user id changing. + * + * @param newUserId The id of the incoming user. + */ + public void setCurrentUser(int newUserId) { + mLockPatternUtils.setCurrentUser(newUserId); + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DELAYED_KEYGUARD_ACTION.equals(intent.getAction())) { + final int sequence = intent.getIntExtra("seq", 0); + if (DEBUG) Log.d(TAG, "received DELAYED_KEYGUARD_ACTION with seq = " + + sequence + ", mDelayedShowingSequence = " + mDelayedShowingSequence); + synchronized (KeyguardViewMediator.this) { + if (mDelayedShowingSequence == sequence) { + // Don't play lockscreen SFX if the screen went off due to timeout. + mSuppressNextLockSound = true; + doKeyguardLocked(null); + } + } + } + } + }; + + public void keyguardDone(boolean authenticated, boolean wakeup) { + if (DEBUG) Log.d(TAG, "keyguardDone(" + authenticated + ")"); + EventLog.writeEvent(70000, 2); + synchronized (this) { + mKeyguardDonePending = false; + } + Message msg = mHandler.obtainMessage(KEYGUARD_DONE, authenticated ? 1 : 0, wakeup ? 1 : 0); + mHandler.sendMessage(msg); + } + + /** + * This handler will be associated with the policy thread, which will also + * be the UI thread of the keyguard. Since the apis of the policy, and therefore + * this class, can be called by other threads, any action that directly + * interacts with the keyguard ui should be posted to this handler, rather + * than called directly. + */ + private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW: + handleShow((Bundle) msg.obj); + break; + case HIDE: + handleHide(); + break; + case RESET: + handleReset(); + break; + case VERIFY_UNLOCK: + handleVerifyUnlock(); + break; + case NOTIFY_SCREEN_OFF: + handleNotifyScreenOff(); + break; + case NOTIFY_SCREEN_ON: + handleNotifyScreenOn((IKeyguardShowCallback) msg.obj); + break; + case KEYGUARD_DONE: + handleKeyguardDone(msg.arg1 != 0, msg.arg2 != 0); + break; + case KEYGUARD_DONE_DRAWING: + handleKeyguardDoneDrawing(); + break; + case KEYGUARD_DONE_AUTHENTICATING: + keyguardDone(true, true); + break; + case SET_OCCLUDED: + handleSetOccluded(msg.arg1 != 0); + break; + case KEYGUARD_TIMEOUT: + synchronized (KeyguardViewMediator.this) { + doKeyguardLocked((Bundle) msg.obj); + } + break; + case DISMISS: + handleDismiss(); + break; + } + } + }; + + /** + * @see #keyguardDone + * @see #KEYGUARD_DONE + */ + private void handleKeyguardDone(boolean authenticated, boolean wakeup) { + if (DEBUG) Log.d(TAG, "handleKeyguardDone"); + + if (authenticated) { + mUpdateMonitor.clearFailedUnlockAttempts(); + } + + if (mExitSecureCallback != null) { + try { + mExitSecureCallback.onKeyguardExitResult(authenticated); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to call onKeyguardExitResult(" + authenticated + ")", e); + } + + mExitSecureCallback = null; + + if (authenticated) { + // after succesfully exiting securely, no need to reshow + // the keyguard when they've released the lock + mExternallyEnabled = true; + mNeedToReshowWhenReenabled = false; + } + } + + handleHide(); + sendUserPresentBroadcast(); + } + + private void sendUserPresentBroadcast() { + final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser()); + mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, currentUser); + } + + /** + * @see #keyguardDone + * @see #KEYGUARD_DONE_DRAWING + */ + private void handleKeyguardDoneDrawing() { + synchronized(this) { + if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing"); + if (mWaitingUntilKeyguardVisible) { + if (DEBUG) Log.d(TAG, "handleKeyguardDoneDrawing: notifying mWaitingUntilKeyguardVisible"); + mWaitingUntilKeyguardVisible = false; + notifyAll(); + + // there will usually be two of these sent, one as a timeout, and one + // as a result of the callback, so remove any remaining messages from + // the queue + mHandler.removeMessages(KEYGUARD_DONE_DRAWING); + } + } + } + + private void playSounds(boolean locked) { + // User feedback for keyguard. + + if (mSuppressNextLockSound) { + mSuppressNextLockSound = false; + return; + } + + final ContentResolver cr = mContext.getContentResolver(); + if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) { + final int whichSound = locked + ? mLockSoundId + : mUnlockSoundId; + mLockSounds.stop(mLockSoundStreamId); + // Init mAudioManager + if (mAudioManager == null) { + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + if (mAudioManager == null) return; + mMasterStreamType = mAudioManager.getMasterStreamType(); + } + // If the stream is muted, don't play the sound + if (mAudioManager.isStreamMute(mMasterStreamType)) return; + + mLockSoundStreamId = mLockSounds.play(whichSound, + mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/); + } + } + + private void updateActivityLockScreenState() { + try { + ActivityManagerNative.getDefault().setLockScreenShown(mShowing && !mOccluded); + } catch (RemoteException e) { + } + } + + /** + * Handle message sent by {@link #showLocked}. + * @see #SHOW + */ + private void handleShow(Bundle options) { + synchronized (KeyguardViewMediator.this) { + if (!mSystemReady) { + if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready."); + return; + } else { + if (DEBUG) Log.d(TAG, "handleShow"); + } + + mStatusBarKeyguardViewManager.show(options); + mShowing = true; + mKeyguardDonePending = false; + updateActivityLockScreenState(); + adjustStatusBarLocked(); + userActivity(); + + // Do this at the end to not slow down display of the keyguard. + playSounds(true); + + mShowKeyguardWakeLock.release(); + } + mKeyguardDisplayManager.show(); + } + + /** + * Handle message sent by {@link #hideLocked()} + * @see #HIDE + */ + private void handleHide() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleHide"); + + // only play "unlock" noises if not on a call (since the incall UI + // disables the keyguard) + if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) { + playSounds(false); + } + + mStatusBarKeyguardViewManager.hide(); + mShowing = false; + mKeyguardDonePending = false; + updateActivityLockScreenState(); + adjustStatusBarLocked(); + } + } + + private void adjustStatusBarLocked() { + if (mStatusBarManager == null) { + mStatusBarManager = (StatusBarManager) + mContext.getSystemService(Context.STATUS_BAR_SERVICE); + } + if (mStatusBarManager == null) { + Log.w(TAG, "Could not get status bar manager"); + } else { + // Disable aspects of the system/status/navigation bars that must not be re-enabled by + // windows that appear on top, ever + int flags = StatusBarManager.DISABLE_NONE; + if (mShowing) { + // Permanently disable components not available when keyguard is enabled + // (like recents). Temporary enable/disable (e.g. the "back" button) are + // done in KeyguardHostView. + flags |= StatusBarManager.DISABLE_RECENT; + if ((isSecure() && !mAllowNotificationsWhenSecure) + || !ENABLE_INSECURE_STATUS_BAR_EXPAND) { + // showing secure lockscreen; disable expanding. + flags |= StatusBarManager.DISABLE_EXPAND; + } + if (isSecure()) { + // showing secure lockscreen; disable ticker and switch private notifications + // to show their public versions, if available. + flags |= StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS; + } + if (!isAssistantAvailable()) { + flags |= StatusBarManager.DISABLE_SEARCH; + } + } + + if (DEBUG) { + Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded + + " isSecure=" + isSecure() + " --> flags=0x" + Integer.toHexString(flags)); + } + + if (!(mContext instanceof Activity)) { + mStatusBarManager.disable(flags); + } + } + } + + /** + * Handle message sent by {@link #resetStateLocked} + * @see #RESET + */ + private void handleReset() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleReset"); + mStatusBarKeyguardViewManager.reset(); + } + } + + /** + * Handle message sent by {@link #verifyUnlock} + * @see #VERIFY_UNLOCK + */ + private void handleVerifyUnlock() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleVerifyUnlock"); + mStatusBarKeyguardViewManager.verifyUnlock(); + mShowing = true; + updateActivityLockScreenState(); + } + } + + /** + * Handle message sent by {@link #notifyScreenOffLocked()} + * @see #NOTIFY_SCREEN_OFF + */ + private void handleNotifyScreenOff() { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOff"); + mStatusBarKeyguardViewManager.onScreenTurnedOff(); + } + } + + /** + * Handle message sent by {@link #notifyScreenOnLocked} + * @see #NOTIFY_SCREEN_ON + */ + private void handleNotifyScreenOn(IKeyguardShowCallback callback) { + synchronized (KeyguardViewMediator.this) { + if (DEBUG) Log.d(TAG, "handleNotifyScreenOn"); + mStatusBarKeyguardViewManager.onScreenTurnedOn(callback); + } + } + + public boolean isDismissable() { + return mKeyguardDonePending || !isSecure(); + } + + private boolean isAssistantAvailable() { + return mSearchManager != null + && mSearchManager.getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; + } + + public void onBootCompleted() { + mUpdateMonitor.dispatchBootCompleted(); + } + + public StatusBarKeyguardViewManager registerStatusBar(PhoneStatusBar phoneStatusBar, + ViewGroup container, StatusBarWindowManager statusBarWindowManager) { + mStatusBarKeyguardViewManager.registerStatusBar(phoneStatusBar, container, + statusBarWindowManager); + return mStatusBarKeyguardViewManager; + } + + public ViewMediatorCallback getViewMediatorCallback() { + return mViewMediatorCallback; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java new file mode 100644 index 0000000..87ebcc1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java @@ -0,0 +1,172 @@ +/* + * 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.settings; + +import com.android.systemui.R; + +import android.app.ActivityManagerNative; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.RemoteException; +import android.os.UserManager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManagerGlobal; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; + +/** + * A quick and dirty view to show a user switcher. + */ +public class UserSwitcherHostView extends FrameLayout implements ListView.OnItemClickListener { + + private static final String TAG = "UserSwitcherDialog"; + + private ArrayList<UserInfo> mUserInfo = new ArrayList<UserInfo>(); + private Adapter mAdapter = new Adapter(); + private UserManager mUserManager; + private Runnable mFinishRunnable; + private ListView mListView; + + public UserSwitcherHostView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + if (isInEditMode()) { + return; + } + mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + } + + public UserSwitcherHostView(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.listViewStyle); + } + + public UserSwitcherHostView(Context context) { + this(context, null); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mListView = (ListView) findViewById(android.R.id.list); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(this); + } + + @Override + public void onItemClick(AdapterView<?> l, View v, int position, long id) { + int userId = mAdapter.getItem(position).id; + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + ActivityManagerNative.getDefault().switchUser(userId); + finish(); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't switch user.", e); + } + } + + private void finish() { + if (mFinishRunnable != null) { + mFinishRunnable.run(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + finish(); + } + return true; + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + // A gross hack to get rid of the switcher when the shade is collapsed. + if (visibility != VISIBLE) { + finish(); + } + } + + public void setFinishRunnable(Runnable finishRunnable) { + mFinishRunnable = finishRunnable; + } + + public void refreshUsers() { + mUserInfo.clear(); + mUserInfo.addAll(mUserManager.getUsers(true)); + mAdapter.notifyDataSetChanged(); + } + + private class Adapter extends BaseAdapter { + + @Override + public int getCount() { + return mUserInfo.size(); + } + + @Override + public UserInfo getItem(int position) { + return mUserInfo.get(position); + } + + @Override + public long getItemId(int position) { + return getItem(position).serialNumber; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null || (!(convertView.getTag() instanceof ViewHolder))) { + convertView = createView(parent); + } + ViewHolder h = (ViewHolder) convertView.getTag(); + bindView(h, getItem(position)); + return convertView; + } + + private View createView(ViewGroup parent) { + View v = LayoutInflater.from(getContext()).inflate( + R.layout.user_switcher_item, parent, false); + ViewHolder h = new ViewHolder(); + h.name = (TextView) v.findViewById(R.id.user_name); + h.picture = (ImageView) v.findViewById(R.id.user_picture); + v.setTag(h); + return v; + } + + private void bindView(ViewHolder h, UserInfo item) { + h.name.setText(item.name); + h.picture.setImageBitmap(mUserManager.getUserIcon(item.id)); + } + + class ViewHolder { + TextView name; + ImageView picture; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 2f135ec..e51b914 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -168,9 +168,9 @@ public abstract class BaseStatusBar extends SystemUI implements protected int mZenMode; - public IStatusBarService getStatusBarService() { - return mBarService; - } + protected boolean mOnKeyguard; + protected View mKeyguardIconOverflowContainer; + protected NotificationOverflowIconsView mOverflowIconsView; public boolean isDeviceProvisioned() { return mDeviceProvisioned; @@ -435,8 +435,6 @@ public abstract class BaseStatusBar extends SystemUI implements } if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { content.setBackgroundResource(R.drawable.notification_row_legacy_bg); - } else { - content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); } } } @@ -1004,7 +1002,7 @@ public abstract class BaseStatusBar extends SystemUI implements // Remove the expanded view. ViewGroup rowParent = (ViewGroup)entry.row.getParent(); if (rowParent != null) rowParent.removeView(entry.row); - updateExpansionStates(); + updateRowStates(); updateNotificationIcons(); return entry.notification; @@ -1047,7 +1045,7 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Log.d(TAG, "addNotificationViews: added at " + pos); } - updateExpansionStates(); + updateRowStates(); updateNotificationIcons(); } @@ -1055,28 +1053,53 @@ public abstract class BaseStatusBar extends SystemUI implements addNotificationViews(createNotificationViews(key, notification)); } - protected void updateExpansionStates() { + /** + * @return The number of notifications we show on Keyguard. + */ + protected abstract int getMaxKeyguardNotifications(); - // TODO: Handle user expansion better - int N = mNotificationData.size(); - for (int i = 0; i < N; i++) { + /** + * Updates expanded, dimmed and locked states of notification rows. + */ + protected void updateRowStates() { + int maxKeyguardNotifications = getMaxKeyguardNotifications(); + mOverflowIconsView.removeAllViews(); + int n = mNotificationData.size(); + int visibleNotifications = 0; + for (int i = n-1; i >= 0; i--) { NotificationData.Entry entry = mNotificationData.get(i); - if (!entry.row.isUserLocked()) { - if (i == (N-1)) { - if (DEBUG) Log.d(TAG, "expanding top notification at " + i); - entry.row.setSystemExpanded(true); - } else { - if (!entry.row.isUserExpanded()) { - if (DEBUG) Log.d(TAG, "collapsing notification at " + i); - entry.row.setSystemExpanded(false); - } else { - if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i); - } + if (mOnKeyguard) { + entry.row.setSystemExpanded(false); + } else { + if (!entry.row.isUserLocked()) { + boolean top = (i == n-1); + entry.row.setSystemExpanded(top || entry.row.isUserExpanded()); + } + } + entry.row.setDimmed(mOnKeyguard); + entry.row.setLocked(mOnKeyguard); + boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); + if (mOnKeyguard && (visibleNotifications >= maxKeyguardNotifications + || !showOnKeyguard)) { + entry.row.setVisibility(View.GONE); + if (showOnKeyguard) { + mOverflowIconsView.addNotification(entry); } } else { - if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i); + entry.row.setVisibility(View.VISIBLE); + visibleNotifications++; } } + + if (mOnKeyguard && mOverflowIconsView.getChildCount() > 0) { + mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); + } else { + mKeyguardIconOverflowContainer.setVisibility(View.GONE); + } + } + + private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { + return sbn.getNotification().priority >= Notification.PRIORITY_LOW; } protected void setZenMode(int mode) { @@ -1208,7 +1231,7 @@ public abstract class BaseStatusBar extends SystemUI implements handleNotificationError(key, notification, "Couldn't update icon: " + ic); return; } - updateExpansionStates(); + updateRowStates(); } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. @@ -1315,7 +1338,7 @@ public abstract class BaseStatusBar extends SystemUI implements boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) && isAllowed && mPowerManager.isScreenOn() - && !keyguard.isShowingAndNotHidden() + && !keyguard.isShowingAndNotOccluded() && !keyguard.isInputRestricted(); try { interrupt = interrupt && !mDreamManager.isDreaming(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 39333d7..bbbe8fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -21,6 +21,7 @@ import android.os.IBinder; import android.os.Message; import android.service.notification.StatusBarNotification; +import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; @@ -98,6 +99,7 @@ public class CommandQueue extends IStatusBar.Stub { public void hideSearchPanel(); public void cancelPreloadRecentApps(); public void setWindowState(int window, int state); + } public CommandQueue(Callbacks callbacks, StatusBarIconList list) { @@ -232,6 +234,7 @@ public class CommandQueue extends IStatusBar.Stub { } } + private final class H extends Handler { public void handleMessage(Message msg) { final int what = msg.what & MSG_MASK; @@ -295,7 +298,7 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.topAppWindowChanged(msg.arg1 != 0); break; case MSG_SHOW_IME_BUTTON: - mCallbacks.setImeWindowStatus((IBinder)msg.obj, msg.arg1, msg.arg2); + mCallbacks.setImeWindowStatus((IBinder) msg.obj, msg.arg1, msg.arg2); break; case MSG_SET_HARD_KEYBOARD_STATUS: mCallbacks.setHardKeyboardStatus(msg.arg1 != 0, msg.arg2 != 0); @@ -312,6 +315,7 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_SET_WINDOW_STATE: mCallbacks.setWindowState(msg.arg1, msg.arg2); break; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 2daf619..7bacc13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -40,6 +40,8 @@ public class ExpandableNotificationRow extends FrameLayout { /** Are we showing the "public" version */ private boolean mShowingPublic; + private LatestItemView mLatestItemView; + /** * Is this notification expanded by the system. The expansion state can be overridden by the * user expansion. @@ -59,8 +61,10 @@ public class ExpandableNotificationRow extends FrameLayout { super.onFinishInflate(); mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic); mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded); + mLatestItemView = (LatestItemView) findViewById(R.id.container); } + public void setHeightRange(int rowMinHeight, int rowMaxHeight) { mRowMinHeight = rowMinHeight; mRowMaxHeight = rowMaxHeight; @@ -152,8 +156,6 @@ public class ExpandableNotificationRow extends FrameLayout { return mShowingPublic ? mRowMinHeight : getMaxExpandHeight(); } - - private void updateMaxExpandHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); int oldHeight = lp.height; @@ -195,6 +197,13 @@ public class ExpandableNotificationRow extends FrameLayout { mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE); } + /** + * Sets the notification as dimmed, meaning that it will appear in a more gray variant. + */ + public void setDimmed(boolean dimmed) { + mLatestItemView.setDimmed(dimmed); + } + public int getMaxExpandHeight() { if (mMaxHeightNeedsUpdate) { updateMaxExpandHeight(); @@ -202,4 +211,12 @@ public class ExpandableNotificationRow extends FrameLayout { } return mMaxExpandHeight; } + + /** + * Sets the notification as locked. In the locked state, the first tap will produce a quantum + * ripple to make the notification brighter and only the second tap will cause a click. + */ + public void setLocked(boolean locked) { + mLatestItemView.setLocked(locked); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java index 6419777..ad9028d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LatestItemView.java @@ -17,16 +17,44 @@ package com.android.systemui.statusbar; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; public class LatestItemView extends FrameLayout { + + private static final long DOUBLETAP_TIMEOUT_MS = 1000; + + private boolean mDimmed; + private boolean mLocked; + + /** + * Flag to indicate that the notification has been touched once and the second touch will + * click it. + */ + private boolean mActivated; + + private float mDownX; + private float mDownY; + private final float mTouchSlop; + private boolean mHotspotActive; + public LatestItemView(Context context, AttributeSet attrs) { super(context, attrs); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } + private final Runnable mTapTimeoutRunnable = new Runnable() { + @Override + public void run() { + makeInactive(); + } + }; + @Override public void setOnClickListener(OnClickListener l) { super.setOnClickListener(l); @@ -45,4 +73,94 @@ public class LatestItemView extends FrameLayout { } return false; } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mLocked) { + return handleTouchEventLocked(event); + } else { + return super.onTouchEvent(event); + } + } + + private boolean handleTouchEventLocked(MotionEvent event) { + int action = event.getActionMasked(); + Drawable background = getBackground(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownX = event.getX(); + mDownY = event.getY(); + if (!mActivated) { + background.setHotspot(0, event.getX(), event.getY()); + mHotspotActive = true; + } + break; + case MotionEvent.ACTION_MOVE: + if (!isWithinTouchSlop(event)) { + makeInactive(); + return false; + } + break; + case MotionEvent.ACTION_UP: + if (isWithinTouchSlop(event)) { + if (!mActivated) { + mActivated = true; + postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); + } else { + performClick(); + makeInactive(); + } + } else { + makeInactive(); + } + break; + case MotionEvent.ACTION_CANCEL: + makeInactive(); + break; + default: + break; + } + return true; + } + + /** + * Cancels the hotspot and makes the notification inactive. + */ + private void makeInactive() { + if (mHotspotActive) { + // Make sure that we clear the hotspot from the center. + getBackground().setHotspot(0, getWidth()/2, getHeight()/2); + getBackground().removeHotspot(0); + mHotspotActive = false; + } + mActivated = false; + removeCallbacks(mTapTimeoutRunnable); + } + + private boolean isWithinTouchSlop(MotionEvent event) { + return Math.abs(event.getX() - mDownX) < mTouchSlop + && Math.abs(event.getY() - mDownY) < mTouchSlop; + } + + /** + * Sets the notification as dimmed, meaning that it will appear in a more gray variant. + */ + public void setDimmed(boolean dimmed) { + if (mDimmed != dimmed) { + mDimmed = dimmed; + if (dimmed) { + setBackgroundResource(com.android.internal.R.drawable.notification_quantum_bg_dim); + } else { + setBackgroundResource(com.android.internal.R.drawable.notification_quantum_bg); + } + } + } + + /** + * Sets the notification as locked. In the locked state, the first tap will produce a quantum + * ripple to make the notification brighter and only the second tap will cause a click. + */ + public void setLocked(boolean locked) { + mLocked = locked; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java new file mode 100644 index 0000000..ce31894 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java @@ -0,0 +1,69 @@ +/* + * 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; + +import android.app.Notification; +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.statusbar.StatusBarIcon; +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.IconMerger; + +/** + * A view to display all the overflowing icons on Keyguard. + */ +public class NotificationOverflowIconsView extends IconMerger { + + private TextView mMoreText; + private int mTintColor; + + public NotificationOverflowIconsView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mTintColor = getResources().getColor(R.color.keyguard_overflow_content_color); + } + + public void setMoreText(TextView moreText) { + mMoreText = moreText; + } + + public void addNotification(NotificationData.Entry notification) { + StatusBarIconView v = new StatusBarIconView(getContext(), "", + notification.notification.getNotification()); + v.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + v.setColorFilter(mTintColor, PorterDuff.Mode.MULTIPLY); + addView(v); + v.set(notification.icon.getStatusBarIcon()); + updateMoreText(); + } + + private void updateMoreText() { + mMoreText.setText(getResources().getQuantityString( + R.plurals.keyguard_more_overflow_text, getChildCount(), getChildCount())); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java new file mode 100644 index 0000000..7cbde36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -0,0 +1,139 @@ +/* + * 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.os.RemoteException; +import android.util.Slog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.policy.IKeyguardShowCallback; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardHostView; +import com.android.keyguard.KeyguardViewBase; +import com.android.keyguard.R; +import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.keyguard.KeyguardViewMediator; + +/** + * A class which manages the bouncer on the lockscreen. + */ +public class KeyguardBouncer { + + private Context mContext; + private ViewMediatorCallback mCallback; + private LockPatternUtils mLockPatternUtils; + private ViewGroup mContainer; + private StatusBarWindowManager mWindowManager; + private KeyguardViewBase mKeyguardView; + private ViewGroup mRoot; + + public KeyguardBouncer(Context context, ViewMediatorCallback callback, + LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager, + ViewGroup container) { + mContext = context; + mCallback = callback; + mLockPatternUtils = lockPatternUtils; + mContainer = container; + mWindowManager = windowManager; + } + + public void prepare() { + ensureView(); + } + + public void show() { + ensureView(); + + // Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole + // Keyguard. If we need to authenticate, show the bouncer. + if (!mKeyguardView.dismiss()) { + mRoot.setVisibility(View.VISIBLE); + mKeyguardView.requestFocus(); + } + } + + public void hide() { + if (mKeyguardView != null) { + mKeyguardView.cleanUp(); + } + removeView(); + } + + /** + * Reset the state of the view. + */ + public void reset() { + inflateView(); + } + + public void onScreenTurnedOff() { + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOff(); + } + } + + public void onScreenTurnedOn() { + if (mKeyguardView != null) { + mKeyguardView.onScreenTurnedOn(); + } + } + + public long getUserActivityTimeout() { + if (mKeyguardView != null) { + long timeout = mKeyguardView.getUserActivityTimeout(); + if (timeout >= 0) { + return timeout; + } + } + return KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS; + } + + public boolean isShowing() { + return mRoot != null && mRoot.getVisibility() == View.VISIBLE; + } + + private void ensureView() { + if (mRoot == null) { + inflateView(); + } + } + + private void inflateView() { + removeView(); + mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null); + mKeyguardView = (KeyguardViewBase) mRoot.findViewById(R.id.keyguard_host_view); + mKeyguardView.setLockPatternUtils(mLockPatternUtils); + mKeyguardView.setViewMediatorCallback(mCallback); + mContainer.addView(mRoot, mContainer.getChildCount()); + mRoot.setVisibility(View.INVISIBLE); + mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME); + } + + private void removeView() { + if (mRoot != null && mRoot.getParent() == mContainer) { + mContainer.removeView(mRoot); + mRoot = null; + } + } + + public boolean onBackPressed() { + return mKeyguardView != null && mKeyguardView.handleBackKey(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java index 1ea920d..754075a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java @@ -39,8 +39,8 @@ import java.util.List; */ public class KeyguardTouchDelegate { // TODO: propagate changes to these to {@link KeyguardServiceDelegate} - static final String KEYGUARD_PACKAGE = "com.android.keyguard"; - static final String KEYGUARD_CLASS = "com.android.keyguard.KeyguardService"; + static final String KEYGUARD_PACKAGE = "com.android.systemui"; + static final String KEYGUARD_CLASS = "com.android.systemui.keyguard.KeyguardService"; private static KeyguardTouchDelegate sInstance; private static final List<OnKeyguardConnectionListener> sConnectionListeners = @@ -140,16 +140,16 @@ public class KeyguardTouchDelegate { return false; } - public boolean isShowingAndNotHidden() { + public boolean isShowingAndNotOccluded() { final IKeyguardService service = mService; if (service != null) { try { - return service.isShowingAndNotHidden(); + return service.isShowingAndNotOccluded(); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } } else { - Slog.w(TAG, "isShowingAndNotHidden(): NO SERVICE!"); + Slog.w(TAG, "isShowingAndNotOccluded(): NO SERVICE!"); } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index a74230b..d26b32f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -117,7 +117,7 @@ public final class NavigationBarTransitions extends BarTransitions { @Override public void setContentVisible(boolean visible) { final float alpha = visible ? 1 : 0; - fadeContent(mView.getCameraButton(), alpha); + fadeContent(mView.getBackButton(), alpha); fadeContent(mView.getSearchLight(), alpha); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index d9e0903..db26a42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -34,17 +34,23 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.MediaStore; import android.util.AttributeSet; import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.Surface; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.LinearLayout; @@ -92,12 +98,16 @@ public class NavigationBarView extends LinearLayout { final static boolean WORKAROUND_INVALID_LAYOUT = true; final static int MSG_CHECK_INVALID_LAYOUT = 8686; + private final float mCameraDragDistance; + // used to disable the camera icon in navbar when disabled by DPM private boolean mCameraDisabledByDpm; // performs manual animation in sync with layout transitions private final NavTransitionListener mTransitionListener = new NavTransitionListener(); + private final PowerManager mPowerManager; + private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; private boolean mHomeAppearing; @@ -155,22 +165,73 @@ public class NavigationBarView extends LinearLayout { } }; + private final int mScaledTouchSlop; + private final OnTouchListener mCameraTouchListener = new OnTouchListener() { + private float mStartX; + private boolean mTouchSlopReached; + private boolean mSkipCancelAnimation; + @Override - public boolean onTouch(View cameraButtonView, MotionEvent event) { + public boolean onTouch(final View cameraButtonView, MotionEvent event) { + float realX = event.getRawX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // disable search gesture while interacting with camera mDelegateHelper.setDisabled(true); mBarTransitions.setContentVisible(false); + mStartX = realX; + mTouchSlopReached = false; + mSkipCancelAnimation = false; + break; + case MotionEvent.ACTION_MOVE: + if (realX > mStartX) { + realX = mStartX; + } + if (realX < mStartX - mCameraDragDistance) { + ((KeyButtonView) cameraButtonView).setPressed(true); + mPowerManager.userActivity(event.getEventTime(), false); + } else { + ((KeyButtonView) cameraButtonView).setPressed(false); + } + if (realX < mStartX - mScaledTouchSlop) { + mTouchSlopReached = true; + } + cameraButtonView.setTranslationX(Math.max(realX - mStartX, + -mCameraDragDistance)); break; case MotionEvent.ACTION_UP: + if (realX < mStartX - mCameraDragDistance) { + mContext.startActivityAsUser( + new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), + UserHandle.CURRENT); + } + if (realX < mStartX - mScaledTouchSlop) { + mTouchSlopReached = true; + } + if (!mTouchSlopReached) { + mSkipCancelAnimation = true; + cameraButtonView.animate().translationX(-mCameraDragDistance / 2). + setInterpolator(new DecelerateInterpolator()).withEndAction( + new Runnable() { + @Override + public void run() { + cameraButtonView.animate().translationX(0). + setInterpolator(new AccelerateInterpolator()); + } + }); + } case MotionEvent.ACTION_CANCEL: + ((KeyButtonView) cameraButtonView).setPressed(false); mDelegateHelper.setDisabled(false); mBarTransitions.setContentVisible(true); + if (!mSkipCancelAnimation) { + cameraButtonView.animate().translationX(0) + .setInterpolator(new AccelerateInterpolator(2f)); + } break; } - return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event); + return true; } }; @@ -235,6 +296,9 @@ public class NavigationBarView extends LinearLayout { KeyguardTouchDelegate.addListener(mKeyguardConnectionListener); mCameraDisabledByDpm = isCameraDisabledByDpm(); watchForDevicePolicyChanges(); + mCameraDragDistance = res.getDimension(R.dimen.camera_drag_distance); + mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } private void watchForDevicePolicyChanges() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index a3e35d1..324d6f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -151,7 +151,8 @@ public class PanelBar extends FrameLayout { if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName()); mPanelExpandedFractionSum = 0f; for (PanelView pv : mPanels) { - final boolean visible = pv.getVisibility() == View.VISIBLE; + boolean visible = pv.getExpandedHeight() > 0; + pv.setVisibility(visible ? View.VISIBLE : View.GONE); // adjust any other panels that may be partially visible if (pv.getExpandedHeight() > 0f) { if (mState == STATE_CLOSED) { @@ -166,11 +167,6 @@ public class PanelBar extends FrameLayout { if (thisFrac == 1f) fullyOpenedPanel = panel; } } - if (pv.getExpandedHeight() > 0f) { - if (!visible) pv.setVisibility(View.VISIBLE); - } else { - if (visible) pv.setVisibility(View.GONE); - } } mPanelExpandedFractionSum /= mPanels.size(); if (fullyOpenedPanel != null && !mTracking) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 6922ca5..10a9b64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -651,6 +651,12 @@ public class PanelView extends FrameLayout { int newHeight = getMeasuredHeight(); if (newHeight != mMaxPanelHeight) { mMaxPanelHeight = newHeight; + // If the user isn't actively poking us, let's rubberband to the content + if (!mTracking && !mRubberbanding && !mTimeAnimator.isStarted() + && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight + && mMaxPanelHeight > 0) { + mExpandedHeight = mMaxPanelHeight; + } } heightMeasureSpec = MeasureSpec.makeMeasureSpec( getDesiredMeasureHeight(), MeasureSpec.AT_MOST); 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 841f3ca..ec9f3ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -16,14 +16,16 @@ package com.android.systemui.statusbar.phone; + import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; +import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; +import static com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -66,6 +68,7 @@ import android.util.EventLog; import android.util.Log; import android.view.Display; import android.view.Gravity; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -84,15 +87,19 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.statusbar.StatusBarIcon; +import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.DemoMode; import com.android.systemui.EventLogTags; import com.android.systemui.R; +import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.InterceptedNotifications; +import com.android.systemui.statusbar.LatestItemView; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; +import com.android.systemui.statusbar.NotificationOverflowIconsView; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.BatteryController; @@ -174,6 +181,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { StatusBarWindowView mStatusBarWindow; PhoneStatusBarView mStatusBarView; private int mStatusBarWindowState = WINDOW_STATE_SHOWING; + private StatusBarWindowManager mStatusBarWindowManager; int mPixelFormat; Object mQueueLock = new Object(); @@ -212,9 +220,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { // top bar View mNotificationPanelHeader; + View mKeyguardStatusView; + int mKeyguardMaxNotificationCount; View mDateTimeView; View mClearButton; ImageView mSettingsButton, mNotificationButton; + View mKeyguardSettingsFlipButton; // carrier/wifi label private TextView mCarrierLabel; @@ -337,6 +348,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { private int mNavigationBarMode; private Boolean mScreenOn; + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private ViewMediatorCallback mKeyguardViewMediatorCallback; + private final Runnable mAutohide = new Runnable() { @Override public void run() { @@ -349,6 +363,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { private Runnable mOnFlipRunnable; private InterceptedNotifications mIntercepted; + private final OnChildLocationsChangedListener mOnChildLocationsChangedListener = + new OnChildLocationsChangedListener() { + @Override + public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) { + userActivity(); + } + }; + public void setOnFlipRunnable(Runnable onFlipRunnable) { mOnFlipRunnable = onFlipRunnable; } @@ -388,6 +410,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, mHeadsUpObserver); } + startKeyguard(); } // ================================================================================ @@ -494,10 +517,21 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById( R.id.notification_stack_scroller); mStackScroller.setLongPressListener(getNotificationLongClicker()); + mStackScroller.setChildLocationsChangedListener(mOnChildLocationsChangedListener); + + mKeyguardIconOverflowContainer = LayoutInflater.from(mContext).inflate( + R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false); + ((LatestItemView) mKeyguardIconOverflowContainer.findViewById(R.id.container)).setLocked(true); + mOverflowIconsView = (NotificationOverflowIconsView) mKeyguardIconOverflowContainer.findViewById( + R.id.overflow_icons_view); + mOverflowIconsView.setMoreText( + (TextView) mKeyguardIconOverflowContainer.findViewById(R.id.more_text)); + mStackScroller.addView(mKeyguardIconOverflowContainer); mExpandedContents = mStackScroller; mNotificationPanelHeader = mStatusBarWindow.findViewById(R.id.header); + mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view); mClearButton = mStatusBarWindow.findViewById(R.id.clear_all_button); mClearButton.setOnClickListener(mClearButtonListener); @@ -687,6 +721,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { return mStatusBarView; } + private void startKeyguard() { + KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class); + mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, + mStatusBarWindow, mStatusBarWindowManager); + mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback(); + } + @Override protected void onShowSearchPanel() { if (mNavigationBarView != null) { @@ -764,10 +805,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } } - protected int getStatusBarGravity() { - return Gravity.TOP | Gravity.FILL_HORIZONTAL; - } - public int getStatusBarHeight() { if (mNaturalBarHeight < 0) { final Resources res = mContext.getResources(); @@ -1024,7 +1061,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 - && !mNotificationPanel.isTracking()) { + && !mNotificationPanel.isTracking() && !mOnKeyguard) { animateCollapsePanels(); } } @@ -1093,7 +1130,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { ArrayList<View> toRemove = new ArrayList<View>(); for (int i=0; i< mStackScroller.getChildCount(); i++) { View child = mStackScroller.getChildAt(i); - if (!toShow.contains(child)) { + if (!toShow.contains(child) && child != mKeyguardIconOverflowContainer) { toRemove.add(child); } } @@ -1493,26 +1530,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { // Expand the window to encompass the full screen in anticipation of the drag. // This is only possible to do atomically because the status bar is at the top of the screen! - WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); - lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - lp.height = ViewGroup.LayoutParams.MATCH_PARENT; - mWindowManager.updateViewLayout(mStatusBarWindow, lp); + mStatusBarWindowManager.setStatusBarExpanded(true); visibilityChanged(true); setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); } - private void releaseFocus() { - if (mStatusBarWindow == null) return; - WindowManager.LayoutParams lp = - (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); - lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - lp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mWindowManager.updateViewLayout(mStatusBarWindow, lp); - } - public void animateCollapsePanels() { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } @@ -1524,9 +1548,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { + " flags=" + flags); } - // release focus immediately to kick off focus change transition - releaseFocus(); - if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); @@ -1538,6 +1559,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } if (mStatusBarWindow != null) { + + // release focus immediately to kick off focus change transition + mStatusBarWindowManager.setStatusBarFocusable(false); + mStatusBarWindow.cancelExpandHelper(); mStatusBarView.collapseAllPanels(true); } @@ -1796,11 +1821,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { visibilityChanged(false); // Shrink the window to the size of the status bar only - WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); - lp.height = getStatusBarHeight(); - lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - lp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mWindowManager.updateViewLayout(mStatusBarWindow, lp); + mStatusBarWindowManager.setStatusBarExpanded(false); if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); @@ -1815,6 +1836,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); + + showBouncer(); } public boolean interceptTouchEvent(MotionEvent event) { @@ -2303,30 +2326,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } private void addStatusBarWindow() { - // Put up the view - final int height = getStatusBarHeight(); - - // Now that the status bar window encompasses the sliding panel and its - // translucent backdrop, the entire thing is made TRANSLUCENT and is - // hardware-accelerated. - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - height, - WindowManager.LayoutParams.TYPE_STATUS_BAR, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, - PixelFormat.TRANSLUCENT); - - lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - - lp.gravity = getStatusBarGravity(); - lp.setTitle("StatusBar"); - lp.packageName = mContext.getPackageName(); - makeStatusBarView(); - mWindowManager.addView(mStatusBarWindow, lp); + mStatusBarWindowManager = new StatusBarWindowManager(mContext); + mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight()); } void setNotificationIconVisibility(boolean visible, int anim) { @@ -2464,8 +2466,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOn = false; - // no waiting! - makeExpandedInvisible(); notifyNavigationBarScreenOn(false); notifyHeadsUpScreenOn(false); finishBarAnimations(); @@ -2644,6 +2644,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { mRowMinHeight = res.getDimensionPixelSize(R.dimen.notification_row_min_height); mRowMaxHeight = res.getDimensionPixelSize(R.dimen.notification_row_max_height); + mKeyguardMaxNotificationCount = res.getInteger(R.integer.keyguard_max_notification_count); + if (false) Log.v(TAG, "updateResources"); } @@ -2815,4 +2817,120 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { ((DemoMode)v).dispatchDemoCommand(command, args); } } + + public boolean isOnKeyguard() { + return mOnKeyguard; + } + + public void showKeyguard() { + mOnKeyguard = true; + instantExpandNotificationsPanel(); + if (isFlippedToSettings()) { + flipToNotifications(); + } + mStatusBarWindow.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME); + mKeyguardStatusView.setVisibility(View.VISIBLE); + mNotificationPanelHeader.setVisibility(View.GONE); + if (mKeyguardSettingsFlipButton == null) { + ViewStub flipStub = (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_flip_stub); + mKeyguardSettingsFlipButton = flipStub.inflate(); + installSettingsButton(mKeyguardSettingsFlipButton); + } + mKeyguardSettingsFlipButton.setVisibility(View.VISIBLE); + mKeyguardSettingsFlipButton.findViewById(R.id.settings_button).setVisibility(View.VISIBLE); + mKeyguardSettingsFlipButton.findViewById(R.id.notification_button) + .setVisibility(View.INVISIBLE); + updateRowStates(); + } + + public void hideKeyguard() { + mOnKeyguard = false; + mStatusBarWindow.setSystemUiVisibility(0); + mKeyguardStatusView.setVisibility(View.GONE); + mNotificationPanelHeader.setVisibility(View.VISIBLE); + mKeyguardSettingsFlipButton.setVisibility(View.GONE); + updateRowStates(); + } + + public void userActivity() { + if (mOnKeyguard) { + mKeyguardViewMediatorCallback.userActivity(); + } + } + + public boolean onBackPressed() { + if (mOnKeyguard) { + return mStatusBarKeyguardViewManager.onBackPressed(); + } else { + animateCollapsePanels(); + return true; + } + } + + private void showBouncer() { + if (mOnKeyguard) { + mStatusBarKeyguardViewManager.dismiss(); + } + } + + private void instantExpandNotificationsPanel() { + mExpandedVisible = true; + mNotificationPanel.setExpandedFraction(1); + } + + @Override + protected int getMaxKeyguardNotifications() { + return mKeyguardMaxNotificationCount; + } + + /** + * @return a ViewGroup that spans the entire panel which contains the quick settings + */ + public ViewGroup getQuickSettingsOverlayParent() { + if (mHasSettingsPanel) { + if (mHasFlipSettings) { + return mNotificationPanel; + } else { + return mSettingsPanel; + } + } else { + return null; + } + } + + private void installSettingsButton(View parent) { + final ImageView settingsButton = + (ImageView) mStatusBarWindow.findViewById(R.id.settings_button); + final ImageView notificationButton = + (ImageView) mStatusBarWindow.findViewById(R.id.notification_button); + if (settingsButton != null) { + settingsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + animateExpandSettingsPanel(); + v.setVisibility(View.INVISIBLE); + notificationButton.setVisibility(View.VISIBLE); + } + }); + settingsButton.setVisibility(View.VISIBLE); + if (mHasSettingsPanel) { + // the settings panel is hiding behind this button + settingsButton.setImageResource(R.drawable.ic_notify_quicksettings); + } else { + // no settings panel, go straight to settings + settingsButton.setImageResource(R.drawable.ic_notify_settings); + } + } + if (notificationButton != null) { + notificationButton.setVisibility(View.INVISIBLE); + notificationButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + flipToNotifications(); + v.setVisibility(View.INVISIBLE); + settingsButton.setVisibility(View.VISIBLE); + } + }); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index d9e3fdf..583fc3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -37,6 +37,7 @@ public class PhoneStatusBarView extends PanelBar { PhoneStatusBar mBar; int mScrimColor; + int mScrimColorKeyguard; float mSettingsPanelDragzoneFrac; float mSettingsPanelDragzoneMin; @@ -52,6 +53,7 @@ public class PhoneStatusBarView extends PanelBar { Resources res = getContext().getResources(); mScrimColor = res.getColor(R.color.notification_panel_scrim_color); + mScrimColorKeyguard = res.getColor(R.color.notification_panel_scrim_color_keyguard); mSettingsPanelDragzoneMin = res.getDimension(R.dimen.settings_panel_dragzone_min); try { mSettingsPanelDragzoneFrac = res.getFraction(R.dimen.settings_panel_dragzone_fraction, 1, 1); @@ -217,6 +219,7 @@ public class PhoneStatusBarView extends PanelBar { if (panel == mFadingPanel && mScrimColor != 0 && ActivityManager.isHighEndGfx() && mBar.mStatusBarWindow != null) { if (mShouldFade) { + int scrimColor = mBar.isOnKeyguard() ? mScrimColorKeyguard : mScrimColor; frac = mPanelExpandedFractionSum; // don't judge me // let's start this 20% of the way down the screen frac = frac * 1.2f - 0.2f; @@ -226,7 +229,7 @@ public class PhoneStatusBarView extends PanelBar { // woo, special effects final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); // attenuate background color alpha by k - final int color = (int) ((mScrimColor >>> 24) * k) << 24 | (mScrimColor & 0xFFFFFF); + final int color = (int) ((scrimColor >>> 24) * k) << 24 | (scrimColor & 0xFFFFFF); mBar.mStatusBarWindow.setBackgroundColor(color); } } @@ -249,5 +252,6 @@ public class PhoneStatusBarView extends PanelBar { mBar.animateHeadsUp(mNotificationPanel == panel, mPanelExpandedFractionSum); mBar.updateCarrierLabelVisibility(false); + mBar.userActivity(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java index d67f7cd..b3fba76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java @@ -71,6 +71,7 @@ import android.widget.TextView; import com.android.internal.app.MediaRouteDialogPresenter; import com.android.systemui.R; +import com.android.systemui.settings.UserSwitcherHostView; import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState; import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState; import com.android.systemui.statusbar.phone.QuickSettingsModel.RSSIState; @@ -310,30 +311,28 @@ class QuickSettings { collapsePanels(); } - private void addUserTiles(ViewGroup parent, LayoutInflater inflater) { + private void addUserTiles(final ViewGroup parent, final LayoutInflater inflater) { QuickSettingsTileView userTile = (QuickSettingsTileView) inflater.inflate(R.layout.quick_settings_tile, parent, false); userTile.setContent(R.layout.quick_settings_tile_user, inflater); userTile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - collapsePanels(); final UserManager um = UserManager.get(mContext); if (um.isUserSwitcherEnabled()) { - // Since keyguard and systemui were merged into the same process to save - // memory, they share the same Looper and graphics context. As a result, - // there's no way to allow concurrent animation while keyguard inflates. - // The workaround is to add a slight delay to allow the animation to finish. - mHandler.postDelayed(new Runnable() { + final ViewGroup switcherParent = getService().getQuickSettingsOverlayParent(); + final UserSwitcherHostView switcher = (UserSwitcherHostView) inflater.inflate( + R.layout.user_switcher_host, switcherParent, false); + switcher.setFinishRunnable(new Runnable() { + @Override public void run() { - try { - WindowManagerGlobal.getWindowManagerService().lockNow(null); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't show user switcher", e); - } + switcherParent.removeView(switcher); } - }, 400); // TODO: ideally this would be tied to the collapse of the panel + }); + switcher.refreshUsers(); + switcherParent.addView(switcher); } else { + collapsePanels(); Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent( mContext, v, ContactsContract.Profile.CONTENT_URI, ContactsContract.QuickContact.MODE_LARGE, null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java new file mode 100644 index 0000000..b8592c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -0,0 +1,194 @@ +/* + * 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.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; + +import com.android.internal.policy.IKeyguardShowCallback; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardHostView; +import com.android.keyguard.KeyguardSimpleHostView; +import com.android.keyguard.R; +import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.keyguard.KeyguardViewMediator; + +/** + * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back + * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done, + * which is in turn, reported to this class by the current + * {@link com.android.keyguard.KeyguardViewBase}. + */ +public class StatusBarKeyguardViewManager { + private static String TAG = "StatusBarKeyguardViewManager"; + + private final Context mContext; + + private LockPatternUtils mLockPatternUtils; + private ViewMediatorCallback mViewMediatorCallback; + private PhoneStatusBar mPhoneStatusBar; + + private ViewGroup mContainer; + private StatusBarWindowManager mStatusBarWindowManager; + + private boolean mScreenOn = false; + private KeyguardBouncer mBouncer; + private boolean mShowing; + + public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback, + LockPatternUtils lockPatternUtils) { + mContext = context; + mViewMediatorCallback = callback; + mLockPatternUtils = lockPatternUtils; + } + + public void registerStatusBar(PhoneStatusBar phoneStatusBar, + ViewGroup container, StatusBarWindowManager statusBarWindowManager) { + mPhoneStatusBar = phoneStatusBar; + mContainer = container; + mStatusBarWindowManager = statusBarWindowManager; + mBouncer = new KeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, + mStatusBarWindowManager, container); + } + + /** + * Show the keyguard. Will handle creating and attaching to the view manager + * lazily. + */ + public void show(Bundle options) { + mShowing = true; + mStatusBarWindowManager.setKeyguardShowing(true); + mPhoneStatusBar.showKeyguard(); + mBouncer.prepare(); + updateBackButtonState(); + } + + public void showBouncer() { + mBouncer.show(); + updateBackButtonState(); + } + + /** + * Reset the state of the view. + */ + public void reset() { + mBouncer.reset(); + mPhoneStatusBar.showKeyguard(); + updateBackButtonState(); + } + + public void onScreenTurnedOff() { + mScreenOn = false; + mBouncer.onScreenTurnedOff(); + } + + public void onScreenTurnedOn(final IKeyguardShowCallback callback) { + mScreenOn = true; + mBouncer.onScreenTurnedOn(); + if (callback != null) { + callbackAfterDraw(callback); + } + } + + private void callbackAfterDraw(final IKeyguardShowCallback callback) { + mContainer.post(new Runnable() { + @Override + public void run() { + try { + callback.onShown(mContainer.getWindowToken()); + } catch (RemoteException e) { + Slog.w(TAG, "Exception calling onShown():", e); + } + } + }); + } + + public void verifyUnlock() { + dismiss(); + } + + public void setNeedsInput(boolean needsInput) { + mStatusBarWindowManager.setKeyguardNeedsInput(needsInput); + } + + public void updateUserActivityTimeout() { + mStatusBarWindowManager.setKeyguardUserActivityTimeout(mBouncer.getUserActivityTimeout()); + } + + public void setOccluded(boolean occluded) { + mStatusBarWindowManager.setKeyguardOccluded(occluded); + } + + /** + * Hides the keyguard view + */ + public void hide() { + mShowing = false; + mPhoneStatusBar.hideKeyguard(); + mStatusBarWindowManager.setKeyguardShowing(false); + mBouncer.hide(); + mViewMediatorCallback.keyguardGone(); + } + + /** + * Dismisses the keyguard by going to the next screen or making it gone. + */ + public void dismiss() { + if (mScreenOn) { + showBouncer(); + } + } + + /** + * @return Whether the keyguard is showing + */ + public boolean isShowing() { + return mShowing; + } + + /** + * Notifies this manager that the back button has been pressed. + * + * @return whether the back press has been handled + */ + public boolean onBackPressed() { + if (mBouncer.isShowing()) { + mBouncer.hide(); + mPhoneStatusBar.showKeyguard(); + updateBackButtonState(); + return true; + } + return false; + } + + private void updateBackButtonState() { + int vis = mContainer.getSystemUiVisibility(); + if (mBouncer.isShowing()) { + mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK); + } else { + mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java new file mode 100644 index 0000000..6153cde --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -0,0 +1,200 @@ +/* + * 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.app.ActionBar; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.SystemProperties; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.android.keyguard.R; + +/** + * Encapsulates all logic for the status bar window state management. + */ +public class StatusBarWindowManager { + + private final Context mContext; + private final WindowManager mWindowManager; + private View mStatusBarView; + private WindowManager.LayoutParams mLp; + private int mBarHeight; + private final boolean mKeyguardScreenRotation; + + private final State mCurrentState = new State(); + + public StatusBarWindowManager(Context context) { + mContext = context; + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation(); + } + + private boolean shouldEnableKeyguardScreenRotation() { + Resources res = mContext.getResources(); + return SystemProperties.getBoolean("lockscreen.rot_override", false) + || res.getBoolean(R.bool.config_enableLockScreenRotation); + } + + /** + * Adds the status bar view to the window manager. + * + * @param statusBarView The view to add. + * @param barHeight The height of the status bar in collapsed state. + */ + public void add(View statusBarView, int barHeight) { + + // Now that the status bar window encompasses the sliding panel and its + // translucent backdrop, the entire thing is made TRANSLUCENT and is + // hardware-accelerated. + mLp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + barHeight, + WindowManager.LayoutParams.TYPE_STATUS_BAR, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION + | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + PixelFormat.TRANSLUCENT); + + mLp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + mLp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; + mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + mLp.setTitle("StatusBar"); + mLp.packageName = mContext.getPackageName(); + mStatusBarView = statusBarView; + mBarHeight = barHeight; + mWindowManager.addView(mStatusBarView, mLp); + } + + private void applyKeyguardFlags(State state) { + if (state.keyguardShowing) { + mLp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + mLp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; + } else { + mLp.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + mLp.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; + } + } + + private void adjustScreenOrientation(State state) { + if (!state.isKeyguardShowingAndNotOccluded() || mKeyguardScreenRotation) { + mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; + } else { + mLp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + } + } + + private void applyFocusableFlag(State state) { + if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput) { + mLp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mLp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) { + mLp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mLp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } else { + mLp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mLp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + } + + private void applyHeight(State state) { + boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded; + if (expanded) { + mLp.height = ViewGroup.LayoutParams.MATCH_PARENT; + } else { + mLp.height = mBarHeight; + } + } + + private void applyUserActivityTimeout(State state) { + if (state.isKeyguardShowingAndNotOccluded()) { + mLp.userActivityTimeout = state.keyguardUserActivityTimeout; + } else { + mLp.userActivityTimeout = -1; + } + } + + private void applyInputFeatures(State state) { + if (state.isKeyguardShowingAndNotOccluded()) { + mLp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; + } else { + mLp.inputFeatures &= ~WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; + } + } + + private void apply(State state) { + applyKeyguardFlags(state); + applyFocusableFlag(state); + adjustScreenOrientation(state); + applyHeight(state); + applyUserActivityTimeout(state); + applyInputFeatures(state); + mWindowManager.updateViewLayout(mStatusBarView, mLp); + } + + public void setKeyguardShowing(boolean showing) { + mCurrentState.keyguardShowing = showing; + apply(mCurrentState); + } + + public void setKeyguardOccluded(boolean occluded) { + mCurrentState.keyguardOccluded = occluded; + apply(mCurrentState); + } + + public void setKeyguardNeedsInput(boolean needsInput) { + mCurrentState.keyguardNeedsInput = needsInput; + apply(mCurrentState); + } + + public void setStatusBarExpanded(boolean expanded) { + mCurrentState.statusBarExpanded = expanded; + mCurrentState.statusBarFocusable = expanded; + apply(mCurrentState); + } + + public void setStatusBarFocusable(boolean focusable) { + mCurrentState.statusBarFocusable = focusable; + apply(mCurrentState); + } + + public void setKeyguardUserActivityTimeout(long timeout) { + mCurrentState.keyguardUserActivityTimeout = timeout; + apply(mCurrentState); + } + + private static class State { + boolean keyguardShowing; + boolean keyguardOccluded; + boolean keyguardNeedsInput; + boolean statusBarExpanded; + boolean statusBarFocusable; + long keyguardUserActivityTimeout; + + private boolean isKeyguardShowingAndNotOccluded() { + return keyguardShowing && !keyguardOccluded; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index a7121c4..dd89f47 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -79,7 +79,7 @@ public class StatusBarWindowView extends FrameLayout switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: if (!down) { - mService.animateCollapsePanels(); + mService.onBackPressed(); } return true; } @@ -90,7 +90,8 @@ public class StatusBarWindowView extends FrameLayout public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercept = false; if (mNotificationPanel.isFullyExpanded() - && mStackScrollLayout.getVisibility() == View.VISIBLE) { + && mStackScrollLayout.getVisibility() == View.VISIBLE + && !mService.isOnKeyguard()) { intercept = mExpandHelper.onInterceptTouchEvent(ev); } if (!intercept) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index f31896a..9800bc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -656,9 +656,11 @@ public class NotificationStackScrollLayout extends ViewGroup int height = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); - height += child.getHeight(); - if (i < getChildCount()-1) { - height += mPaddingBetweenElements; + if (child.getVisibility() != View.GONE) { + height += child.getHeight(); + if (i < getChildCount()-1) { + height += mPaddingBetweenElements; + } } } mContentHeight = height; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index 5506a55..431f6fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -23,6 +23,8 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; +import java.util.ArrayList; + /** * The Algorithm of the {@link com.android.systemui.statusbar.stack * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar @@ -98,6 +100,7 @@ public class StackScrollAlgorithm { algorithmState.lastTopStackIndex = 0; algorithmState.scrollY = resultState.getScrollY(); algorithmState.itemsInBottomStack = 0.0f; + updateVisibleChildren(resultState, algorithmState); // Phase 1: findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState); @@ -113,6 +116,23 @@ public class StackScrollAlgorithm { } /** + * Update the visible children on the state. + */ + private void updateVisibleChildren(StackScrollState resultState, + StackScrollAlgorithmState state) { + ViewGroup hostView = resultState.getHostView(); + int childCount = hostView.getChildCount(); + state.visibleChildren.clear(); + state.visibleChildren.ensureCapacity(childCount); + for (int i = 0; i < childCount; i++) { + View v = hostView.getChildAt(i); + if (v.getVisibility() != View.GONE) { + state.visibleChildren.add(v); + } + } + } + + /** * Determine the positions for the views. This is the main part of the algorithm. * * @param resultState The result state to update if a change to the properties of a child occurs @@ -135,11 +155,10 @@ public class StackScrollAlgorithm { // How far in is the element currently transitioning into the bottom stack. float yPositionInScrollView = 0.0f; - ViewGroup hostView = resultState.getHostView(); - int childCount = hostView.getChildCount(); + int childCount = algorithmState.visibleChildren.size(); int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; for (int i = 0; i < childCount; i++) { - View child = hostView.getChildAt(i); + View child = algorithmState.visibleChildren.get(i); StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); childViewState.yTranslation = currentYPosition; childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN; @@ -317,12 +336,11 @@ public class StackScrollAlgorithm { // The y Position if the element would be in a regular scrollView float yPositionInScrollView = 0.0f; - ViewGroup hostView = resultState.getHostView(); - int childCount = hostView.getChildCount(); + int childCount = algorithmState.visibleChildren.size(); // find the number of elements in the top stack. for (int i = 0; i < childCount; i++) { - View child = hostView.getChildAt(i); + View child = algorithmState.visibleChildren.get(i); StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); int childHeight = child.getHeight(); float yPositionInScrollViewAfterElement = yPositionInScrollView @@ -397,10 +415,9 @@ public class StackScrollAlgorithm { */ private void updateZValuesForState(StackScrollState resultState, StackScrollAlgorithmState algorithmState) { - ViewGroup hostView = resultState.getHostView(); - int childCount = hostView.getChildCount(); + int childCount = algorithmState.visibleChildren.size(); for (int i = 0; i < childCount; i++) { - View child = hostView.getChildAt(i); + View child = algorithmState.visibleChildren.get(i); StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); if (i < algorithmState.itemsInTopStack) { float stackIndex = algorithmState.itemsInTopStack - i; @@ -434,8 +451,8 @@ public class StackScrollAlgorithm { } private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) { - if (hostView.getChildCount() > 0) { - mFirstChildWhileExpanding = hostView.getChildAt(0); + mFirstChildWhileExpanding = findFirstVisibleChild(hostView); + if (mFirstChildWhileExpanding != null) { if (mExpandedOnStart) { // We are collapsing the shade, so the first child can get as most as high as the @@ -447,11 +464,21 @@ public class StackScrollAlgorithm { mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding); } } else { - mFirstChildWhileExpanding = null; mFirstChildMaxHeight = 0; } } + private View findFirstVisibleChild(ViewGroup container) { + int childCount = container.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = container.getChildAt(i); + if (child.getVisibility() != View.GONE) { + return child; + } + } + return null; + } + public void onExpansionStopped() { mIsExpansionChanging = false; mFirstChildWhileExpanding = null; @@ -501,6 +528,11 @@ public class StackScrollAlgorithm { * how far in is the element currently transitioning into the bottom stack */ public float partialInBottom; + + /** + * The children from the host view which are not gone. + */ + public final ArrayList<View> visibleChildren = new ArrayList<View>(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 67a1735..06a08f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -63,7 +63,8 @@ public class StackScrollState { } // initialize with the default values of the view viewState.height = child.getHeight(); - viewState.alpha = 1.0f; + viewState.alpha = 1; + viewState.gone = child.getVisibility() == View.GONE; } } @@ -116,7 +117,7 @@ public class StackScrollState { // apply visibility int oldVisibility = child.getVisibility(); int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; - if (newVisibility != oldVisibility) { + if (newVisibility != oldVisibility && !state.gone) { child.setVisibility(newVisibility); } @@ -164,6 +165,7 @@ public class StackScrollState { float yTranslation; float zTranslation; int height; + boolean gone; /** * The location this view is currently rendered at. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index dd13e31..d615542 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -22,6 +22,7 @@ import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; +import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.BaseStatusBar; @@ -93,10 +94,6 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void createAndAddWindows() { - } - - @Override protected WindowManager.LayoutParams getSearchLayoutParams( LayoutParams layoutParams) { return null; @@ -141,10 +138,19 @@ public class TvStatusBar extends BaseStatusBar { } @Override + protected int getMaxKeyguardNotifications() { + return 0; + } + + @Override public void animateExpandSettingsPanel() { } @Override + protected void createAndAddWindows() { + } + + @Override protected void refreshLayout(int layoutDirection) { } |