Merge "Add simple null checks to StorageWizard*, also listen to..." into mnc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4dcaba7..358eae9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -633,14 +633,11 @@
android:value="true" />
</activity>
- <!-- TODO: This should also be forwarded, but we can't use cross-profile intent filters -->
<receiver android:name=".inputmethod.InputMethodDialogReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.settings.SHOW_INPUT_METHOD_PICKER" />
</intent-filter>
- <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
- android:value="true" />
</receiver>
<activity android:name="Settings$UserDictionarySettingsActivity"
@@ -767,7 +764,6 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
- <category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.notification.ZenModeScheduleRuleSettings" />
@@ -787,7 +783,6 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
- <category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.notification.ZenModeEventRuleSettings" />
@@ -807,7 +802,6 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
- <category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.notification.ZenModeExternalRuleSettings" />
@@ -926,6 +920,19 @@
android:value="true" />
</activity>
+ <activity android:name="Settings$MemorySettingsActivity"
+ android:label="@string/memory_settings_title"
+ android:taskAffinity="">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.settings.SHORTCUT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.applications.ProcessStatsSummary" />
+ <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID"
+ android:resource="@id/manage_memory" />
+ </activity>
+
<activity android:name="Settings$AllApplicationsActivity"
android:label="@string/applications_settings"
android:taskAffinity="">
@@ -1423,6 +1430,7 @@
<activity android:name=".fingerprint.FingerprintEnrollFindSensor" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollEnrolling" android:exported="false"/>
<activity android:name=".fingerprint.FingerprintEnrollFinish" android:exported="false"/>
+ <activity android:name=".fingerprint.FingerprintEnrollIntroduction" android:exported="false"/>
<!-- Note this must not be exported since it returns the password in the intent -->
<activity android:name="ConfirmLockPattern$InternalActivity"
diff --git a/res/drawable-nodpi/fingerprint_indicator.png b/res/drawable-nodpi/fingerprint_indicator.png
deleted file mode 100644
index fc5ef0f..0000000
--- a/res/drawable-nodpi/fingerprint_indicator.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxxhdpi/shortcut_base.png b/res/drawable-xxxhdpi/shortcut_base.png
new file mode 100644
index 0000000..cd509df
--- /dev/null
+++ b/res/drawable-xxxhdpi/shortcut_base.png
Binary files differ
diff --git a/res/drawable/fp_illustration_enrollment.xml b/res/drawable/fp_illustration_enrollment.xml
new file mode 100644
index 0000000..f9b7ed4
--- /dev/null
+++ b/res/drawable/fp_illustration_enrollment.xml
@@ -0,0 +1,36 @@
+<!--
+Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="88.0dp"
+ android:height="88.0dp"
+ android:viewportWidth="88.0"
+ android:viewportHeight="88.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M67.74,11.59c-0.41,0.0 -0.82,-0.1 -1.2,-0.31c-7.44,-4.06 -15.0,-6.04 -23.11,-6.04c-7.92,0.0 -14.67,1.85 -21.88,6.01c-1.2,0.69 -2.73,0.28 -3.42,-0.92s-0.28,-2.72 0.92,-3.41c7.9,-4.55 15.65,-6.68 24.37,-6.68c8.97,0.0 17.32,2.17 25.51,6.65c1.21,0.66 1.66,2.18 1.0,3.39C69.48,11.12 68.62,11.59 67.74,11.59z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9.25,34.74c-0.48,0.0 -0.96,-0.14 -1.39,-0.42c-1.15,-0.77 -1.45,-2.32 -0.68,-3.47c4.09,-6.09 9.3,-10.89 15.49,-14.27c6.52,-3.55 13.91,-5.43 21.38,-5.43c7.44,0.0 14.8,1.86 21.3,5.39c6.17,3.35 11.38,8.12 15.47,14.16c0.77,1.14 0.47,2.7 -0.67,3.47c-1.14,0.77 -2.7,0.47 -3.47,-0.67c-3.64,-5.38 -8.25,-9.61 -13.71,-12.57c-5.77,-3.13 -12.31,-4.78 -18.92,-4.78c-6.63,0.0 -13.2,1.67 -18.98,4.82c-5.48,2.99 -10.1,7.25 -13.73,12.66C10.85,34.35 10.06,34.74 9.25,34.74z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M34.76,86.82c-0.67,0.0 -1.33,-0.27 -1.82,-0.79c-3.49,-3.72 -5.51,-6.25 -8.26,-11.45c-2.84,-5.35 -4.34,-11.88 -4.34,-18.86c0.0,-13.02 10.59,-23.61 23.61,-23.61c13.02,0.0 23.61,10.59 23.61,23.61c0.0,1.38 -1.12,2.5 -2.5,2.5s-2.5,-1.12 -2.5,-2.5c0.0,-10.26 -8.35,-18.61 -18.61,-18.61c-10.26,0.0 -18.61,8.35 -18.61,18.61c0.0,6.17 1.3,11.89 3.76,16.52c2.62,4.94 4.37,7.04 7.49,10.37c0.94,1.01 0.89,2.59 -0.11,3.53C35.99,86.6 35.38,86.82 34.76,86.82z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M64.28,78.84c-4.99,0.0 -9.35,-1.32 -12.98,-3.92c-6.17,-4.43 -9.86,-11.6 -9.86,-19.19c0.0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0.0,5.98 2.91,11.64 7.77,15.13c2.8,2.01 6.09,2.98 10.06,2.98c0.97,0.0 2.57,-0.11 4.17,-0.4c1.36,-0.25 2.66,0.64 2.92,2.0c0.25,1.36 -0.64,2.66 -2.0,2.92C66.93,78.8 64.86,78.84 64.28,78.84z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M55.92,87.75c-0.23,0.0 -0.46,-0.03 -0.7,-0.1c-6.6,-1.91 -10.92,-4.49 -15.4,-9.2c-5.76,-6.06 -8.94,-14.13 -8.94,-22.72c0.0,-7.2 5.86,-13.05 13.05,-13.05c7.2,0.0 13.05,5.86 13.05,13.05c0.0,4.44 3.61,8.05 8.05,8.05s8.05,-3.61 8.05,-8.05c0.0,-16.08 -13.08,-29.16 -29.16,-29.16c-11.43,0.0 -21.86,6.73 -26.58,17.15c-1.57,3.48 -2.37,7.52 -2.37,12.01c0.0,3.36 0.28,8.62 2.71,15.49c0.46,1.3 -0.22,2.73 -1.52,3.19c-1.3,0.46 -2.73,-0.22 -3.19,-1.52c-2.02,-5.7 -3.0,-11.31 -3.0,-17.16c0.0,-5.21 0.95,-9.94 2.82,-14.07c5.52,-12.2 17.74,-20.09 31.13,-20.09c18.83,0.0 34.16,15.32 34.16,34.16c0.0,7.2 -5.86,13.05 -13.05,13.05S52.0,62.92 52.0,55.73c0.0,-4.44 -3.61,-8.05 -8.05,-8.05s-8.05,3.61 -8.05,8.05c0.0,7.3 2.69,14.15 7.56,19.28c3.86,4.06 7.43,6.18 13.17,7.84c1.33,0.38 2.09,1.77 1.71,3.1C58.01,87.04 57.01,87.75 55.92,87.75z"/>
+</vector>
diff --git a/res/layout-land/fingerprint_enroll_enrolling.xml b/res/layout-land/fingerprint_enroll_enrolling.xml
index c60888b..4d02c07 100644
--- a/res/layout-land/fingerprint_enroll_enrolling.xml
+++ b/res/layout-land/fingerprint_enroll_enrolling.xml
@@ -69,7 +69,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
- android:layout_gravity="center_horizontal|bottom"/>
+ android:layout_gravity="center_horizontal|bottom"
+ android:visibility="invisible"/>
</FrameLayout>
diff --git a/res/layout/choose_lock_pattern.xml b/res/layout/choose_lock_pattern.xml
index 50f05d8..94eecef 100644
--- a/res/layout/choose_lock_pattern.xml
+++ b/res/layout/choose_lock_pattern.xml
@@ -21,7 +21,7 @@
android:layout_height="match_parent">
<!-- takes up all space above button bar at bottom -->
- <LinearLayout
+ <com.android.settings.widget.MatchParentShrinkingLinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dip"
@@ -29,10 +29,11 @@
<TextView android:id="@+id/headerText"
android:layout_width="match_parent"
- android:layout_height="0dip"
+ android:layout_height="wrap_content"
android:layout_weight="1"
+ android:minLines="2"
android:gravity="center"
- android:textSize="18sp"/>
+ android:textSize="18sp" />
<View
android:background="@*android:drawable/code_lock_top"
@@ -41,8 +42,7 @@
<com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/lock_pattern_background" />
+ android:layout_height="match_parent" />
<!-- bottom line looks bad when button bar is their too, omit in this case -->
<!--View
@@ -57,7 +57,7 @@
android:gravity="center"
android:textSize="14sp"/>
- </LinearLayout>
+ </com.android.settings.widget.MatchParentShrinkingLinearLayout>
<LinearLayout
style="@style/SecurityPreferenceButtonContainer"
diff --git a/res/layout/data_usage_detail.xml b/res/layout/data_usage_detail.xml
index 914e8ca..5113139 100644
--- a/res/layout/data_usage_detail.xml
+++ b/res/layout/data_usage_detail.xml
@@ -116,7 +116,7 @@
android:layout_weight="0.5" />
<Button
android:id="@+id/app_settings"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:text="@string/data_usage_app_settings" />
diff --git a/res/layout/empty_print_state.xml b/res/layout/empty_print_state.xml
index e97bb85..361bf3c 100644
--- a/res/layout/empty_print_state.xml
+++ b/res/layout/empty_print_state.xml
@@ -44,6 +44,18 @@
android:textColor="?android:attr/textColorSecondary">
</TextView>
+ <Button android:id="@+id/add_new_service"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:visibility="gone"
+ style="?android:attr/buttonBarButtonStyle"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/print_menu_item_add_service"
+ android:textAllCaps="true"
+ />
+
</LinearLayout>
</FrameLayout>
diff --git a/res/layout/fingerprint_enroll_enrolling_base.xml b/res/layout/fingerprint_enroll_enrolling_base.xml
index 37e10ba..21221cf 100644
--- a/res/layout/fingerprint_enroll_enrolling_base.xml
+++ b/res/layout/fingerprint_enroll_enrolling_base.xml
@@ -66,7 +66,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
- android:layout_gravity="center_horizontal"/>
+ android:layout_gravity="center_horizontal"
+ android:visibility="invisible"/>
</LinearLayout>
diff --git a/res/layout/fingerprint_enroll_enrolling_content.xml b/res/layout/fingerprint_enroll_enrolling_content.xml
index 9fa503b..b3171a3 100644
--- a/res/layout/fingerprint_enroll_enrolling_content.xml
+++ b/res/layout/fingerprint_enroll_enrolling_content.xml
@@ -23,17 +23,13 @@
android:layout_gravity="center_horizontal">
<ImageView
- android:layout_width="88dp"
- android:layout_height="88dp"
- android:layout_centerInParent="true"
- android:src="@drawable/fingerprint_indicator" />
-
- <ImageView
android:id="@+id/fingerprint_animator"
android:layout_width="88dp"
android:layout_height="88dp"
android:layout_centerInParent="true"
- android:src="@drawable/enrollment_fingerprint_isolated_animation" />
+ android:background="@drawable/fp_illustration_enrollment"
+ android:backgroundTint="@color/fingerprint_indicator_background_resting"
+ android:src="@drawable/enrollment_fingerprint_isolated_animation"/>
<ProgressBar
android:id="@+id/fingerprint_progress_bar"
diff --git a/res/layout/fingerprint_enroll_finish_base.xml b/res/layout/fingerprint_enroll_finish_base.xml
index 981c9f0..1c66103 100644
--- a/res/layout/fingerprint_enroll_finish_base.xml
+++ b/res/layout/fingerprint_enroll_finish_base.xml
@@ -63,7 +63,6 @@
android:id="@+id/add_another_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
android:text="@string/fingerprint_enroll_button_add" />
<Button
diff --git a/res/layout/fingerprint_enroll_introduction.xml b/res/layout/fingerprint_enroll_introduction.xml
new file mode 100644
index 0000000..df83bd9
--- /dev/null
+++ b/res/layout/fingerprint_enroll_introduction.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+
+<com.android.setupwizardlib.SetupWizardLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/setup_wizard_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/SetupWizardFingerprintStyle">
+
+ <LinearLayout
+ style="@style/SuwContentFrame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <TextView
+ style="@style/TextAppearance.FingerprintMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/suw_description_margin_top"
+ android:text="@string/security_settings_fingerprint_enroll_introduction_message"/>
+
+ <TextView
+ style="@style/TextAppearance.FingerprintMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
+ android:text="@string/security_settings_fingerprint_enroll_introduction_message_warning"/>
+
+ <TextView
+ style="@style/TextAppearance.FingerprintLink"
+ android:id="@+id/learn_more_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:text="@string/security_settings_fingerprint_enroll_introduction_risk_link_text"/>
+
+ <View
+ android:layout_height="0dp"
+ android:layout_width="match_parent"
+ android:layout_weight="1"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-12dp"
+ android:layout_marginBottom="4dp"
+ android:layout_gravity="end"
+ android:orientation="horizontal">
+
+ <Button
+ style="@style/Button.FingerprintButton"
+ android:id="@+id/cancel_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="8dp"
+ android:text="@string/security_settings_fingerprint_enroll_introduction_cancel" />
+
+ <Button
+ style="@style/Button.FingerprintButton"
+ android:id="@+id/next_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="end|center_vertical"
+ android:text="@string/security_settings_fingerprint_enroll_introduction_continue" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</com.android.setupwizardlib.SetupWizardLayout>
diff --git a/res/layout/shortcut_badge.xml b/res/layout/shortcut_badge.xml
new file mode 100644
index 0000000..117b386
--- /dev/null
+++ b/res/layout/shortcut_badge.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/shortcut_size"
+ android:layout_height="@dimen/shortcut_size">
+
+ <ImageView
+ android:layout_width="@dimen/shortcut_size"
+ android:layout_height="@dimen/shortcut_size"
+ android:scaleType="fitXY"
+ android:src="@drawable/shortcut_base" />
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/shortcut_size"
+ android:layout_height="@dimen/shortcut_size"
+ android:padding="12dp"
+ android:scaleType="fitXY" />
+
+</RelativeLayout>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index d0ef652..c874970 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -66,6 +66,8 @@
<color name="fingerprint_message_color">#de000000</color>
<color name="fingerprint_progress_ring">?android:attr/colorAccent</color>
<color name="fingerprint_progress_ring_bg">#20000000</color>
+ <color name="fingerprint_indicator_background_resting">#12000000</color>
+ <color name="fingerprint_indicator_background_activated">#80009688</color>
<color name="running_processes_system_ram">#ff384248</color>
<color name="running_processes_apps_ram">#ff009587</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5891438..c10e83f 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -231,6 +231,8 @@
<dimen name="fingerprint_find_sensor_graphic_size">200dp</dimen>
<item name="fingerprint_illustration_aspect_ratio" format="float" type="dimen">2.6</item>
<dimen name="fingerprint_decor_padding_top">0dp</dimen>
+ <dimen name="fingerprint_error_text_appear_distance">16dp</dimen>
+ <dimen name="fingerprint_error_text_disappear_distance">-8dp</dimen>
<dimen name="confirm_credentials_security_method_margin">48dp</dimen>
<dimen name="fab_size">56dp</dimen>
@@ -244,4 +246,7 @@
<dimen name="mdm_app_info_height">72dp</dimen>
<dimen name="mdm_app_info_padding_top_bottom">8dp</dimen>
<dimen name="mdm_app_name_padding_left">16dp</dimen>
+
+ <dimen name="shortcut_size">40dp</dimen>
+ <dimen name="badge_size">10dp</dimen>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e28007..6a61aa0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -823,6 +823,8 @@
<string name="security_settings_fingerprint_enroll_disclaimer">In addition to unlocking your phone, you can also use your fingerprint to authorize purchases and app access. <annotation id="url">Learn more</annotation></string>
<!-- Text shown in fingerprint settings explaining what the fingerprint can be used for in the case unlocking is disabled [CHAR LIMIT=NONE] -->
<string name="security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled">Screen lock option disabled. You can still use your fingerprint to authorize purchases and app access. <annotation id="url">Learn more</annotation></string>
+ <!-- Text shown in fingerprint enroll when we didn't observe progress for a few seconds. [CHAR LIMIT=100] -->
+ <string name="security_settings_fingerprint_enroll_lift_touch_again">Lift finger, then touch sensor again</string>
<!-- Title of the preferences category for preference items to control encryption -->
<string name="crypt_keeper_settings_title">Encryption</string>
@@ -1206,6 +1208,8 @@
<!-- Message in pairing dialogs. [CHAR LIMIT=NONE] -->
<string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string>
+ <!-- Checkbox message in pairing dialogs. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_pairing_shares_phonebook">Grant access to your contacts and call history when connected.</string>
<!-- Title for BT error dialogs. -->
<string name="bluetooth_error_title"></string>
@@ -5194,6 +5198,8 @@
<string name="data_usage_menu_sim_cards">SIM cards</string>
<!-- Title for menu option to show details for all cellular networks. [CHAR LIMIT=26] -->
<string name="data_usage_menu_cellular_networks">Cellular networks</string>
+ <!-- Summary String for Cellular data enable toggle. [CHAR LIMIT=26] -->
+ <string name="data_usage_cellular_data_summary">Paused at limit</string>
<!-- Title for menu option to enable global auto-sync of personal account data [CHAR LIMIT=30] -->
<string name="account_settings_menu_auto_sync">Auto-sync data</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 1d58779..1b7caf7 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -309,6 +309,11 @@
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
+ <style name="TextAppearance.FingerprintLink"
+ parent="TextAppearance.FingerprintMessage">
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ </style>
+
<style name="TextAppearance.FingerprintErrorText"
parent="android:TextAppearance.Material.Caption">
<item name="android:textColor">@color/warning</item>
diff --git a/src/com/android/settings/CreateShortcut.java b/src/com/android/settings/CreateShortcut.java
index fa2ce7c..637bc6f 100644
--- a/src/com/android/settings/CreateShortcut.java
+++ b/src/com/android/settings/CreateShortcut.java
@@ -17,18 +17,33 @@
package com.android.settings;
import android.app.LauncherActivity;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.MeasureSpec;
+import android.widget.ImageView;
import android.widget.ListView;
import com.android.settings.Settings.TetherSettingsActivity;
+import com.android.settings.dashboard.DashboardCategory;
+import com.android.settings.dashboard.DashboardTile;
import com.android.settingslib.TetherUtil;
+import java.util.ArrayList;
import java.util.List;
public class CreateShortcut extends LauncherActivity {
+ private static final String TOP_LEVEL_HEADER = "com.android.settings.TOP_LEVEL_HEADER_ID";
+
@Override
protected Intent getTargetIntent() {
Intent targetIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -46,10 +61,45 @@
Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_settings));
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, itemForPosition(position).label);
+ ResolveInfo resolveInfo = itemForPosition(position).resolveInfo;
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo.metaData != null && activityInfo.metaData.containsKey(TOP_LEVEL_HEADER)) {
+ int topLevelId = activityInfo.metaData.getInt(TOP_LEVEL_HEADER);
+ int resourceId = getDrawableResource(topLevelId);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(resourceId));
+ }
setResult(RESULT_OK, intent);
finish();
}
+ private Bitmap createIcon(int resource) {
+ Context context = new ContextThemeWrapper(this, android.R.style.Theme_Material_Light);
+ View view = LayoutInflater.from(context).inflate(R.layout.shortcut_badge, null);
+ ((ImageView) view.findViewById(android.R.id.icon)).setImageResource(resource);
+
+ int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ view.measure(spec, spec);
+ Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(),
+ Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
+ view.draw(canvas);
+ return bitmap;
+ }
+
+ private int getDrawableResource(int topLevelId) {
+ ArrayList<DashboardCategory> categories = new ArrayList<>();
+ SettingsActivity.loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
+ for (DashboardCategory category : categories) {
+ for (DashboardTile tile : category.tiles) {
+ if (tile.id == topLevelId) {
+ return tile.iconRes;
+ }
+ }
+ }
+ return 0;
+ }
+
@Override
protected boolean onEvaluateShowIcons() {
return false;
@@ -60,7 +110,8 @@
* implementation queries for activities.
*/
protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) {
- List<ResolveInfo> activities = super.onQueryPackageManager(queryIntent);
+ List<ResolveInfo> activities = getPackageManager().queryIntentActivities(queryIntent,
+ PackageManager.GET_META_DATA);
if (activities == null) return null;
for (int i = activities.size() - 1; i >= 0; i--) {
ResolveInfo info = activities.get(i);
diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java
index 23f6812..6ab36c1 100644
--- a/src/com/android/settings/MasterClear.java
+++ b/src/com/android/settings/MasterClear.java
@@ -234,15 +234,18 @@
authContext.getDrawable(desc.iconId), userHandle);
}
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "No icon for account type " + desc.type);
+ Log.w(TAG, "Bad package name for account type " + desc.type);
+ } catch (Resources.NotFoundException e) {
+ Log.w(TAG, "Invalid icon id for account type " + desc.type, e);
+ }
+ if (icon == null) {
+ icon = context.getPackageManager().getDefaultActivityIcon();
}
TextView child = (TextView)inflater.inflate(R.layout.master_clear_account,
contents, false);
child.setText(account.name);
- if (icon != null) {
- child.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
- }
+ child.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
contents.addView(child);
}
}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index 8f6c849..93bf1e0 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -17,8 +17,6 @@
package com.android.settings;
-import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
-
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
@@ -54,8 +52,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.TrustAgentUtils.TrustAgentComponentInfo;
-import com.android.settings.fingerprint.FingerprintEnrollFindSensor;
-import com.android.settings.fingerprint.FingerprintEnrollOnboard;
+import com.android.settings.fingerprint.FingerprintEnrollIntroduction;
import com.android.settings.fingerprint.FingerprintSettings;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Index;
@@ -65,6 +62,8 @@
import java.util.ArrayList;
import java.util.List;
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
/**
* Gesture lock pattern settings.
*/
@@ -358,12 +357,9 @@
R.plurals.security_settings_fingerprint_preference_summary,
fingerprintCount, fingerprintCount));
clazz = FingerprintSettings.class.getName();
- } else if (!hasPassword) {
- // No fingerprints registered, launch into enrollment wizard.
- clazz = FingerprintEnrollOnboard.class.getName();
} else {
- // Lock thingy is already set up, launch directly into find sensor step from wizard.
- clazz = FingerprintEnrollFindSensor.class.getName();
+ clazz = FingerprintEnrollIntroduction.class.getName();
+ intent.putExtra(FingerprintEnrollIntroduction.EXTRA_HAS_PASSWORD, hasPassword);
}
intent.setClassName("com.android.settings", clazz);
fingerprintPreference.setIntent(intent);
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 5fb94f0..ea4f77a 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -113,5 +113,6 @@
public static class TopLevelSettings extends SettingsActivity { /* empty */ }
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class MemorySettingsActivity extends SettingsActivity { /* empty */ }
}
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 89231df..f376644 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -1039,7 +1039,7 @@
*/
private void buildDashboardCategories(List<DashboardCategory> categories) {
categories.clear();
- loadCategoriesFromResource(R.xml.dashboard_categories, categories);
+ loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
updateTilesList(categories);
}
@@ -1050,10 +1050,11 @@
* @param resid The XML resource to load and parse.
* @param target The list in which the parsed categories and tiles should be placed.
*/
- private void loadCategoriesFromResource(int resid, List<DashboardCategory> target) {
+ public static void loadCategoriesFromResource(int resid, List<DashboardCategory> target,
+ Context context) {
XmlResourceParser parser = null;
try {
- parser = getResources().getXml(resid);
+ parser = context.getResources().getXml(resid);
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -1082,7 +1083,7 @@
if ("dashboard-category".equals(nodeName)) {
DashboardCategory category = new DashboardCategory();
- TypedArray sa = obtainStyledAttributes(
+ TypedArray sa = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.PreferenceHeader);
category.id = sa.getResourceId(
com.android.internal.R.styleable.PreferenceHeader_id,
@@ -1098,12 +1099,13 @@
}
}
sa.recycle();
- sa = obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference);
+ sa = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Preference);
tv = sa.peekValue(
com.android.internal.R.styleable.Preference_key);
if (tv != null && tv.type == TypedValue.TYPE_STRING) {
if (tv.resourceId != 0) {
- category.key = getString(tv.resourceId);
+ category.key = context.getString(tv.resourceId);
} else {
category.key = tv.string.toString();
}
@@ -1121,7 +1123,7 @@
if (innerNodeName.equals("dashboard-tile")) {
DashboardTile tile = new DashboardTile();
- sa = obtainStyledAttributes(
+ sa = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.PreferenceHeader);
tile.id = sa.getResourceId(
com.android.internal.R.styleable.PreferenceHeader_id,
@@ -1163,11 +1165,13 @@
String innerNodeName2 = parser.getName();
if (innerNodeName2.equals("extra")) {
- getResources().parseBundleExtra("extra", attrs, curBundle);
+ context.getResources().parseBundleExtra("extra", attrs,
+ curBundle);
XmlUtils.skipCurrentTag(parser);
} else if (innerNodeName2.equals("intent")) {
- tile.intent = Intent.parseIntent(getResources(), parser, attrs);
+ tile.intent = Intent.parseIntent(context.getResources(), parser,
+ attrs);
} else {
XmlUtils.skipCurrentTag(parser);
@@ -1180,7 +1184,7 @@
}
// Show the SIM Cards setting if there are more than 2 SIMs installed.
- if(tile.id != R.id.sim_settings || Utils.showSimCardTile(this)){
+ if(tile.id != R.id.sim_settings || Utils.showSimCardTile(context)){
category.addTile(tile);
}
diff --git a/src/com/android/settings/accounts/AuthenticatorHelper.java b/src/com/android/settings/accounts/AuthenticatorHelper.java
index 86e0da5..56a689c 100644
--- a/src/com/android/settings/accounts/AuthenticatorHelper.java
+++ b/src/com/android/settings/accounts/AuthenticatorHelper.java
@@ -96,7 +96,8 @@
/**
* Gets an icon associated with a particular account type. If none found, return null.
* @param accountType the type of account
- * @return a drawable for the icon or null if one cannot be found.
+ * @return a drawable for the icon or a default icon returned by
+ * {@link PackageManager#getDefaultActivityIcon} if one cannot be found.
*/
public Drawable getDrawableForType(Context context, final String accountType) {
Drawable icon = null;
diff --git a/src/com/android/settings/accounts/ChooseAccountActivity.java b/src/com/android/settings/accounts/ChooseAccountActivity.java
index c4dace8..12077af 100644
--- a/src/com/android/settings/accounts/ChooseAccountActivity.java
+++ b/src/com/android/settings/accounts/ChooseAccountActivity.java
@@ -214,7 +214,8 @@
/**
* Gets an icon associated with a particular account type. If none found, return null.
* @param accountType the type of account
- * @return a drawable for the icon or null if one cannot be found.
+ * @return a drawable for the icon or a default icon returned by
+ * {@link PackageManager#getDefaultActivityIcon} if one cannot be found.
*/
protected Drawable getDrawableForType(final String accountType) {
Drawable icon = null;
@@ -225,14 +226,16 @@
icon = getPackageManager().getUserBadgedIcon(
authContext.getDrawable(desc.iconId), mUserHandle);
} catch (PackageManager.NameNotFoundException e) {
- // TODO: place holder icon for missing account icons?
Log.w(TAG, "No icon name for account type " + accountType);
} catch (Resources.NotFoundException e) {
- // TODO: place holder icon for missing account icons?
Log.w(TAG, "No icon resource for account type " + accountType);
}
}
- return icon;
+ if (icon != null) {
+ return icon;
+ } else {
+ return getPackageManager().getDefaultActivityIcon();
+ }
}
/**
diff --git a/src/com/android/settings/deviceinfo/MigrateEstimateTask.java b/src/com/android/settings/deviceinfo/MigrateEstimateTask.java
index bc8ff92..34a44ac 100644
--- a/src/com/android/settings/deviceinfo/MigrateEstimateTask.java
+++ b/src/com/android/settings/deviceinfo/MigrateEstimateTask.java
@@ -104,6 +104,7 @@
protected void onPostExecute(Long result) {
mSizeBytes = result;
mTimeMillis = (mSizeBytes * DateUtils.SECOND_IN_MILLIS) / SPEED_ESTIMATE_BPS;
+ mTimeMillis = Math.max(mTimeMillis, DateUtils.SECOND_IN_MILLIS);
final String size = Formatter.formatFileSize(mContext, mSizeBytes);
final String time = DateUtils.formatDuration(mTimeMillis).toString();
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
index 886cdb2..49c5e82 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollEnrolling.java
@@ -17,12 +17,15 @@
package com.android.settings.fingerprint;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
@@ -50,6 +53,12 @@
private static final int FINISH_DELAY = 250;
/**
+ * If we don't see progress during this time, we show an error message to remind the user that
+ * he needs to lift the finger and touch again.
+ */
+ private static final int HINT_TIMEOUT_DURATION = 2500;
+
+ /**
* How long the user needs to touch the icon until we show the dialog.
*/
private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500;
@@ -67,10 +76,15 @@
private TextView mRepeatMessage;
private TextView mErrorText;
private Interpolator mFastOutSlowInInterpolator;
+ private Interpolator mLinearOutSlowInInterpolator;
+ private Interpolator mFastOutLinearInInterpolator;
private int mIconTouchCount;
private FingerprintEnrollSidecar mSidecar;
private boolean mAnimationCancelled;
private AnimatedVectorDrawable mIconAnimationDrawable;
+ private int mIndicatorBackgroundRestingColor;
+ private int mIndicatorBackgroundActivatedColor;
+ private boolean mRestoring;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -86,6 +100,10 @@
mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
this, android.R.interpolator.fast_out_slow_in);
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
+ this, android.R.interpolator.linear_out_slow_in);
+ mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
+ this, android.R.interpolator.fast_out_linear_in);
mFingerprintAnimator.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -104,6 +122,11 @@
return true;
}
});
+ mIndicatorBackgroundRestingColor
+ = getColor(R.color.fingerprint_indicator_background_resting);
+ mIndicatorBackgroundActivatedColor
+ = getColor(R.color.fingerprint_indicator_background_activated);
+ mRestoring = savedInstanceState != null;
}
@Override
@@ -117,6 +140,9 @@
mSidecar.setListener(this);
updateProgress(false /* animate */);
updateDescription();
+ if (mRestoring) {
+ startIconAnimation();
+ }
}
@Override
@@ -158,6 +184,34 @@
mProgressAnim = anim;
}
+ private void animateFlash() {
+ ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundRestingColor,
+ mIndicatorBackgroundActivatedColor);
+ final ValueAnimator.AnimatorUpdateListener listener =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mFingerprintAnimator.setBackgroundTintList(ColorStateList.valueOf(
+ (Integer) animation.getAnimatedValue()));
+ }
+ };
+ anim.addUpdateListener(listener);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundActivatedColor,
+ mIndicatorBackgroundRestingColor);
+ anim.addUpdateListener(listener);
+ anim.setDuration(300);
+ anim.setInterpolator(mLinearOutSlowInInterpolator);
+ anim.start();
+ }
+ });
+ anim.setInterpolator(mFastOutSlowInInterpolator);
+ anim.setDuration(300);
+ anim.start();
+ }
+
private void launchFinish(byte[] token) {
Intent intent = new Intent();
intent.setClassName("com.android.settings", FingerprintEnrollFinish.class.getName());
@@ -187,7 +241,7 @@
@Override
public void onEnrollmentError(CharSequence errString) {
- mErrorText.setText(errString);
+ showError(errString);
stopIconAnimation();
}
@@ -195,7 +249,10 @@
public void onEnrollmentProgressChange(int steps, int remaining) {
updateProgress(true /* animate */);
updateDescription();
- mErrorText.setText("");
+ clearError();
+ animateFlash();
+ mErrorText.removeCallbacks(mTouchAgainRunnable);
+ mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION);
}
private void updateProgress(boolean animate) {
@@ -221,6 +278,44 @@
new IconTouchDialog().show(getFragmentManager(), null /* tag */);
}
+ private void showError(CharSequence error) {
+ mErrorText.setText(error);
+ if (mErrorText.getVisibility() == View.INVISIBLE) {
+ mErrorText.setVisibility(View.VISIBLE);
+ mErrorText.setTranslationY(getResources().getDimensionPixelSize(
+ R.dimen.fingerprint_error_text_appear_distance));
+ mErrorText.setAlpha(0f);
+ mErrorText.animate()
+ .alpha(1f)
+ .translationY(0f)
+ .setDuration(200)
+ .setInterpolator(mLinearOutSlowInInterpolator)
+ .start();
+ } else {
+ mErrorText.animate().cancel();
+ mErrorText.setAlpha(1f);
+ mErrorText.setTranslationY(0f);
+ }
+ }
+
+ private void clearError() {
+ if (mErrorText.getVisibility() == View.VISIBLE) {
+ mErrorText.animate()
+ .alpha(0f)
+ .translationY(getResources().getDimensionPixelSize(
+ R.dimen.fingerprint_error_text_disappear_distance))
+ .setDuration(100)
+ .setInterpolator(mFastOutLinearInInterpolator)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
+ })
+ .start();
+ }
+ }
+
private final Animator.AnimatorListener mProgressAnimationListener
= new Animator.AnimatorListener() {
@@ -274,6 +369,13 @@
}
};
+ private final Runnable mTouchAgainRunnable = new Runnable() {
+ @Override
+ public void run() {
+ showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again));
+ }
+ };
+
private static class IconTouchDialog extends DialogFragment {
@Override
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
index b3a5d22..552ed71 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollFindSensor.java
@@ -73,6 +73,7 @@
}
} else if (requestCode == ENROLLING) {
if (resultCode == RESULT_FINISHED) {
+ setResult(RESULT_FINISHED);
finish();
}
} else {
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java
new file mode 100644
index 0000000..a488358
--- /dev/null
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollIntroduction.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.fingerprint;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.settings.HelpUtils;
+import com.android.settings.R;
+
+/**
+ * Onboarding activity for fingerprint enrollment.
+ */
+public class FingerprintEnrollIntroduction extends FingerprintEnrollBase {
+
+ public static final String EXTRA_HAS_PASSWORD = "fp_existing_password";
+ private boolean mHasPassword;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fingerprint_enroll_introduction);
+ setHeaderText(R.string.security_settings_fingerprint_enroll_introduction_title);
+ findViewById(R.id.cancel_button).setOnClickListener(this);
+ findViewById(R.id.learn_more_button).setOnClickListener(this);
+ mHasPassword = getIntent().getBooleanExtra(EXTRA_HAS_PASSWORD, false);
+ }
+
+ @Override
+ protected void onNextButtonClick() {
+ Intent intent = new Intent();
+ final String clazz;
+ if (!mHasPassword) {
+ // No fingerprints registered, launch into enrollment wizard.
+ clazz = FingerprintEnrollOnboard.class.getName();
+ } else {
+ // Lock thingy is already set up, launch directly into find sensor step from wizard.
+ clazz = FingerprintEnrollFindSensor.class.getName();
+ }
+ intent.setClassName("com.android.settings", clazz);
+ startActivityForResult(intent, 0);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == RESULT_FINISHED) {
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.cancel_button) {
+ finish();
+ }
+ if (v.getId() == R.id.learn_more_button) {
+ launchFingerprintHelp();
+ }
+ super.onClick(v);
+ }
+
+ private void launchFingerprintHelp() {
+ Intent helpIntent = HelpUtils.getHelpIntent(this,
+ getString(R.string.help_url_fingerprint), getClass().getName());
+ startActivity(helpIntent);
+ }
+}
diff --git a/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java b/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java
index f9ad2ba..b78636a 100644
--- a/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java
+++ b/src/com/android/settings/fingerprint/FingerprintEnrollOnboard.java
@@ -49,6 +49,7 @@
if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST && resultCode == RESULT_FINISHED) {
byte[] token = data.getByteArrayExtra(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
+ setResult(RESULT_FINISHED);
launchFindSensor(token);
} else {
super.onActivityResult(requestCode, resultCode, data);
diff --git a/src/com/android/settings/print/PrintSettingsFragment.java b/src/com/android/settings/print/PrintSettingsFragment.java
index ebd51d5..d737282 100644
--- a/src/com/android/settings/print/PrintSettingsFragment.java
+++ b/src/com/android/settings/print/PrintSettingsFragment.java
@@ -17,6 +17,7 @@
package com.android.settings.print;
import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ActivityNotFoundException;
import android.content.AsyncTaskLoader;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -26,6 +27,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -46,13 +48,12 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
@@ -75,8 +76,8 @@
* Fragment with the top level print settings.
*/
public class PrintSettingsFragment extends SettingsPreferenceFragment
- implements DialogCreatable, Indexable, OnItemSelectedListener {
-
+ implements DialogCreatable, Indexable, OnItemSelectedListener, OnClickListener {
+ public static final String TAG = "PrintSettingsFragment";
private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
private static final String PRINT_JOBS_CATEGORY = "print_jobs_category";
@@ -99,6 +100,8 @@
private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
"EXTRA_PRINT_SERVICE_COMPONENT_NAME";
+ private static final int ORDER_LAST = 1000;
+
private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
private final Handler mHandler = new Handler() {
@@ -122,6 +125,7 @@
private PrintJobsController mPrintJobsController;
private UserAdapter mProfileSpinnerAdapter;
private Spinner mSpinner;
+ private Button mAddNewServiceButton;
@Override
protected int getMetricsCategory() {
@@ -167,18 +171,6 @@
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- String searchUri = Settings.Secure.getString(getContentResolver(),
- Settings.Secure.PRINT_SERVICE_SEARCH_URI);
- if (!TextUtils.isEmpty(searchUri)) {
- MenuItem menuItem = menu.add(R.string.print_menu_item_add_service);
- menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
- menuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
- }
- }
-
- @Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewGroup contentRoot = (ViewGroup) getListView().getParent();
@@ -186,6 +178,15 @@
R.layout.empty_print_state, contentRoot, false);
TextView textView = (TextView) emptyView.findViewById(R.id.message);
textView.setText(R.string.print_no_services_installed);
+
+ final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
+ if (addNewServiceIntent != null) {
+ mAddNewServiceButton = (Button) emptyView.findViewById(R.id.add_new_service);
+ mAddNewServiceButton.setOnClickListener(this);
+ // The empty is used elsewhere too so it's hidden by default.
+ mAddNewServiceButton.setVisibility(View.VISIBLE);
+ }
+
contentRoot.addView(emptyView);
getListView().setEmptyView(emptyView);
@@ -210,7 +211,9 @@
List<ComponentName> enabledServices = PrintSettingsUtils
.readEnabledPrintServices(getActivity());
- List<ResolveInfo> installedServices = getActivity().getPackageManager()
+ final PackageManager pm = getActivity().getPackageManager();
+
+ List<ResolveInfo> installedServices = pm
.queryIntentServices(
new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
@@ -241,6 +244,11 @@
preference.setSummary(getString(R.string.print_feature_state_off));
}
+ final Drawable drawable = installedService.loadIcon(pm);
+ if (drawable != null) {
+ preference.setIcon(drawable);
+ }
+
Bundle extras = preference.getExtras();
extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
@@ -281,9 +289,37 @@
if (mPrintServicesCategory.getPreferenceCount() == 0) {
getPreferenceScreen().removePreference(mPrintServicesCategory);
+ } else {
+ final Preference addNewServicePreference = newAddServicePreferenceOrNull();
+ if (addNewServicePreference != null) {
+ mPrintServicesCategory.addPreference(addNewServicePreference);
+ }
}
}
+ private Preference newAddServicePreferenceOrNull() {
+ final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
+ if (addNewServiceIntent == null) {
+ return null;
+ }
+ Preference preference = new Preference(getContext());
+ preference.setTitle(R.string.print_menu_item_add_service);
+ preference.setIcon(R.drawable.ic_menu_add);
+ preference.setOrder(ORDER_LAST);
+ preference.setIntent(addNewServiceIntent);
+ preference.setPersistent(false);
+ return preference;
+ }
+
+ private Intent createAddNewServiceIntentOrNull() {
+ final String searchUri = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.PRINT_SERVICE_SEARCH_URI);
+ if (TextUtils.isEmpty(searchUri)) {
+ return null;
+ }
+ return new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
+ }
+
private void startSubSettingsIfNeeded() {
if (getArguments() == null) {
return;
@@ -316,6 +352,20 @@
// Nothing to do
}
+ @Override
+ public void onClick(View v) {
+ if (mAddNewServiceButton == v) {
+ final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
+ if (addNewServiceIntent != null) { // check again just in case.
+ try {
+ startActivity(addNewServiceIntent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Unable to start activity", e);
+ }
+ }
+ }
+ }
+
private class SettingsPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
diff --git a/src/com/android/settings/widget/MatchParentShrinkingLinearLayout.java b/src/com/android/settings/widget/MatchParentShrinkingLinearLayout.java
new file mode 100644
index 0000000..79971e5
--- /dev/null
+++ b/src/com/android/settings/widget/MatchParentShrinkingLinearLayout.java
@@ -0,0 +1,1586 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settings.widget;
+
+import com.android.internal.R;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * A LinearLayout with a twist: if the contents don't fit, it takes space away from the
+ * MATCH_PARENT children, instead of taking it from the weighted ones.
+ *
+ * TODO: Remove once we redesign the ChooseLockPattern screen with a sane layout.
+ */
+public class MatchParentShrinkingLinearLayout extends ViewGroup {
+ /** @hide */
+ @IntDef({HORIZONTAL, VERTICAL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OrientationMode {}
+
+ public static final int HORIZONTAL = 0;
+ public static final int VERTICAL = 1;
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ SHOW_DIVIDER_NONE,
+ SHOW_DIVIDER_BEGINNING,
+ SHOW_DIVIDER_MIDDLE,
+ SHOW_DIVIDER_END
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DividerMode {}
+
+ /**
+ * Don't show any dividers.
+ */
+ public static final int SHOW_DIVIDER_NONE = 0;
+ /**
+ * Show a divider at the beginning of the group.
+ */
+ public static final int SHOW_DIVIDER_BEGINNING = 1;
+ /**
+ * Show dividers between each item in the group.
+ */
+ public static final int SHOW_DIVIDER_MIDDLE = 2;
+ /**
+ * Show a divider at the end of the group.
+ */
+ public static final int SHOW_DIVIDER_END = 4;
+
+ /**
+ * Whether the children of this layout are baseline aligned. Only applicable
+ * if {@link #mOrientation} is horizontal.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ private boolean mBaselineAligned = true;
+
+ /**
+ * If this layout is part of another layout that is baseline aligned,
+ * use the child at this index as the baseline.
+ *
+ * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
+ * with whether the children of this layout are baseline aligned.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ private int mBaselineAlignedChildIndex = -1;
+
+ /**
+ * The additional offset to the child's baseline.
+ * We'll calculate the baseline of this layout as we measure vertically; for
+ * horizontal linear layouts, the offset of 0 is appropriate.
+ */
+ @ViewDebug.ExportedProperty(category = "measurement")
+ private int mBaselineChildTop = 0;
+
+ @ViewDebug.ExportedProperty(category = "measurement")
+ private int mOrientation;
+
+ @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+ @ViewDebug.FlagToString(mask = -1,
+ equals = -1, name = "NONE"),
+ @ViewDebug.FlagToString(mask = Gravity.NO_GRAVITY,
+ equals = Gravity.NO_GRAVITY,name = "NONE"),
+ @ViewDebug.FlagToString(mask = Gravity.TOP,
+ equals = Gravity.TOP, name = "TOP"),
+ @ViewDebug.FlagToString(mask = Gravity.BOTTOM,
+ equals = Gravity.BOTTOM, name = "BOTTOM"),
+ @ViewDebug.FlagToString(mask = Gravity.LEFT,
+ equals = Gravity.LEFT, name = "LEFT"),
+ @ViewDebug.FlagToString(mask = Gravity.RIGHT,
+ equals = Gravity.RIGHT, name = "RIGHT"),
+ @ViewDebug.FlagToString(mask = Gravity.START,
+ equals = Gravity.START, name = "START"),
+ @ViewDebug.FlagToString(mask = Gravity.END,
+ equals = Gravity.END, name = "END"),
+ @ViewDebug.FlagToString(mask = Gravity.CENTER_VERTICAL,
+ equals = Gravity.CENTER_VERTICAL, name = "CENTER_VERTICAL"),
+ @ViewDebug.FlagToString(mask = Gravity.FILL_VERTICAL,
+ equals = Gravity.FILL_VERTICAL, name = "FILL_VERTICAL"),
+ @ViewDebug.FlagToString(mask = Gravity.CENTER_HORIZONTAL,
+ equals = Gravity.CENTER_HORIZONTAL, name = "CENTER_HORIZONTAL"),
+ @ViewDebug.FlagToString(mask = Gravity.FILL_HORIZONTAL,
+ equals = Gravity.FILL_HORIZONTAL, name = "FILL_HORIZONTAL"),
+ @ViewDebug.FlagToString(mask = Gravity.CENTER,
+ equals = Gravity.CENTER, name = "CENTER"),
+ @ViewDebug.FlagToString(mask = Gravity.FILL,
+ equals = Gravity.FILL, name = "FILL"),
+ @ViewDebug.FlagToString(mask = Gravity.RELATIVE_LAYOUT_DIRECTION,
+ equals = Gravity.RELATIVE_LAYOUT_DIRECTION, name = "RELATIVE")
+ }, formatToHexString = true)
+ private int mGravity = Gravity.START | Gravity.TOP;
+
+ @ViewDebug.ExportedProperty(category = "measurement")
+ private int mTotalLength;
+
+ @ViewDebug.ExportedProperty(category = "layout")
+ private float mWeightSum;
+
+ @ViewDebug.ExportedProperty(category = "layout")
+ private boolean mUseLargestChild;
+
+ private int[] mMaxAscent;
+ private int[] mMaxDescent;
+
+ private static final int VERTICAL_GRAVITY_COUNT = 4;
+
+ private static final int INDEX_CENTER_VERTICAL = 0;
+ private static final int INDEX_TOP = 1;
+ private static final int INDEX_BOTTOM = 2;
+ private static final int INDEX_FILL = 3;
+
+ private Drawable mDivider;
+ private int mDividerWidth;
+ private int mDividerHeight;
+ private int mShowDividers;
+ private int mDividerPadding;
+
+ private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+
+ public MatchParentShrinkingLinearLayout(Context context) {
+ this(context, null);
+ }
+
+ public MatchParentShrinkingLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MatchParentShrinkingLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MatchParentShrinkingLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
+
+ int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
+ if (index >= 0) {
+ setOrientation(index);
+ }
+
+ index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
+ if (index >= 0) {
+ setGravity(index);
+ }
+
+ boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
+ if (!baselineAligned) {
+ setBaselineAligned(baselineAligned);
+ }
+
+ mWeightSum = a.getFloat(R.styleable.LinearLayout_weightSum, -1.0f);
+
+ mBaselineAlignedChildIndex = a.getInt(
+ com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);
+
+ mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_measureWithLargestChild, false);
+
+ setDividerDrawable(a.getDrawable(R.styleable.LinearLayout_divider));
+ mShowDividers = a.getInt(R.styleable.LinearLayout_showDividers, SHOW_DIVIDER_NONE);
+ mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayout_dividerPadding, 0);
+
+ a.recycle();
+ }
+
+ /**
+ * Set how dividers should be shown between items in this layout
+ *
+ * @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING},
+ * {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
+ * or {@link #SHOW_DIVIDER_NONE} to show no dividers.
+ */
+ public void setShowDividers(@DividerMode int showDividers) {
+ if (showDividers != mShowDividers) {
+ requestLayout();
+ }
+ mShowDividers = showDividers;
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ /**
+ * @return A flag set indicating how dividers should be shown around items.
+ * @see #setShowDividers(int)
+ */
+ @DividerMode
+ public int getShowDividers() {
+ return mShowDividers;
+ }
+
+ /**
+ * @return the divider Drawable that will divide each item.
+ *
+ * @see #setDividerDrawable(android.graphics.drawable.Drawable)
+ *
+ * @attr ref android.R.styleable#LinearLayout_divider
+ */
+ public Drawable getDividerDrawable() {
+ return mDivider;
+ }
+
+ /**
+ * Set a drawable to be used as a divider between items.
+ *
+ * @param divider Drawable that will divide each item.
+ *
+ * @see #setShowDividers(int)
+ *
+ * @attr ref android.R.styleable#LinearLayout_divider
+ */
+ public void setDividerDrawable(Drawable divider) {
+ if (divider == mDivider) {
+ return;
+ }
+ mDivider = divider;
+ if (divider != null) {
+ mDividerWidth = divider.getIntrinsicWidth();
+ mDividerHeight = divider.getIntrinsicHeight();
+ } else {
+ mDividerWidth = 0;
+ mDividerHeight = 0;
+ }
+ setWillNotDraw(divider == null);
+ requestLayout();
+ }
+
+ /**
+ * Set padding displayed on both ends of dividers.
+ *
+ * @param padding Padding value in pixels that will be applied to each end
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(android.graphics.drawable.Drawable)
+ * @see #getDividerPadding()
+ */
+ public void setDividerPadding(int padding) {
+ mDividerPadding = padding;
+ }
+
+ /**
+ * Get the padding size used to inset dividers in pixels
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(android.graphics.drawable.Drawable)
+ * @see #setDividerPadding(int)
+ */
+ public int getDividerPadding() {
+ return mDividerPadding;
+ }
+
+ /**
+ * Get the width of the current divider drawable.
+ *
+ * @hide Used internally by framework.
+ */
+ public int getDividerWidth() {
+ return mDividerWidth;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mDivider == null) {
+ return;
+ }
+
+ if (mOrientation == VERTICAL) {
+ drawDividersVertical(canvas);
+ } else {
+ drawDividersHorizontal(canvas);
+ }
+ }
+
+ void drawDividersVertical(Canvas canvas) {
+ final int count = getVirtualChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child != null && child.getVisibility() != GONE) {
+ if (hasDividerBeforeChildAt(i)) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int top = child.getTop() - lp.topMargin - mDividerHeight;
+ drawHorizontalDivider(canvas, top);
+ }
+ }
+ }
+
+ if (hasDividerBeforeChildAt(count)) {
+ final View child = getVirtualChildAt(count - 1);
+ int bottom = 0;
+ if (child == null) {
+ bottom = getHeight() - getPaddingBottom() - mDividerHeight;
+ } else {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ bottom = child.getBottom() + lp.bottomMargin;
+ }
+ drawHorizontalDivider(canvas, bottom);
+ }
+ }
+
+ void drawDividersHorizontal(Canvas canvas) {
+ final int count = getVirtualChildCount();
+ final boolean isLayoutRtl = isLayoutRtl();
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child != null && child.getVisibility() != GONE) {
+ if (hasDividerBeforeChildAt(i)) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int position;
+ if (isLayoutRtl) {
+ position = child.getRight() + lp.rightMargin;
+ } else {
+ position = child.getLeft() - lp.leftMargin - mDividerWidth;
+ }
+ drawVerticalDivider(canvas, position);
+ }
+ }
+ }
+
+ if (hasDividerBeforeChildAt(count)) {
+ final View child = getVirtualChildAt(count - 1);
+ int position;
+ if (child == null) {
+ if (isLayoutRtl) {
+ position = getPaddingLeft();
+ } else {
+ position = getWidth() - getPaddingRight() - mDividerWidth;
+ }
+ } else {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (isLayoutRtl) {
+ position = child.getLeft() - lp.leftMargin - mDividerWidth;
+ } else {
+ position = child.getRight() + lp.rightMargin;
+ }
+ }
+ drawVerticalDivider(canvas, position);
+ }
+ }
+
+ void drawHorizontalDivider(Canvas canvas, int top) {
+ mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
+ getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
+ mDivider.draw(canvas);
+ }
+
+ void drawVerticalDivider(Canvas canvas, int left) {
+ mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
+ left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
+ mDivider.draw(canvas);
+ }
+
+ /**
+ * <p>Indicates whether widgets contained within this layout are aligned
+ * on their baseline or not.</p>
+ *
+ * @return true when widgets are baseline-aligned, false otherwise
+ */
+ public boolean isBaselineAligned() {
+ return mBaselineAligned;
+ }
+
+ /**
+ * <p>Defines whether widgets contained in this layout are
+ * baseline-aligned or not.</p>
+ *
+ * @param baselineAligned true to align widgets on their baseline,
+ * false otherwise
+ *
+ * @attr ref android.R.styleable#LinearLayout_baselineAligned
+ */
+ @android.view.RemotableViewMethod
+ public void setBaselineAligned(boolean baselineAligned) {
+ mBaselineAligned = baselineAligned;
+ }
+
+ /**
+ * When true, all children with a weight will be considered having
+ * the minimum size of the largest child. If false, all children are
+ * measured normally.
+ *
+ * @return True to measure children with a weight using the minimum
+ * size of the largest child, false otherwise.
+ *
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
+ */
+ public boolean isMeasureWithLargestChildEnabled() {
+ return mUseLargestChild;
+ }
+
+ /**
+ * When set to true, all children with a weight will be considered having
+ * the minimum size of the largest child. If false, all children are
+ * measured normally.
+ *
+ * Disabled by default.
+ *
+ * @param enabled True to measure children with a weight using the
+ * minimum size of the largest child, false otherwise.
+ *
+ * @attr ref android.R.styleable#LinearLayout_measureWithLargestChild
+ */
+ @android.view.RemotableViewMethod
+ public void setMeasureWithLargestChildEnabled(boolean enabled) {
+ mUseLargestChild = enabled;
+ }
+
+ @Override
+ public int getBaseline() {
+ if (mBaselineAlignedChildIndex < 0) {
+ return super.getBaseline();
+ }
+
+ if (getChildCount() <= mBaselineAlignedChildIndex) {
+ throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+ + "set to an index that is out of bounds.");
+ }
+
+ final View child = getChildAt(mBaselineAlignedChildIndex);
+ final int childBaseline = child.getBaseline();
+
+ if (childBaseline == -1) {
+ if (mBaselineAlignedChildIndex == 0) {
+ // this is just the default case, safe to return -1
+ return -1;
+ }
+ // the user picked an index that points to something that doesn't
+ // know how to calculate its baseline.
+ throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+ + "points to a View that doesn't know how to get its baseline.");
+ }
+
+ // TODO: This should try to take into account the virtual offsets
+ // (See getNextLocationOffset and getLocationOffset)
+ // We should add to childTop:
+ // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex])
+ // and also add:
+ // getLocationOffset(child)
+ int childTop = mBaselineChildTop;
+
+ if (mOrientation == VERTICAL) {
+ final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if (majorGravity != Gravity.TOP) {
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ childTop = mBottom - mTop - mPaddingBottom - mTotalLength;
+ break;
+
+ case Gravity.CENTER_VERTICAL:
+ childTop += ((mBottom - mTop - mPaddingTop - mPaddingBottom) -
+ mTotalLength) / 2;
+ break;
+ }
+ }
+ }
+
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ return childTop + lp.topMargin + childBaseline;
+ }
+
+ /**
+ * @return The index of the child that will be used if this layout is
+ * part of a larger layout that is baseline aligned, or -1 if none has
+ * been set.
+ */
+ public int getBaselineAlignedChildIndex() {
+ return mBaselineAlignedChildIndex;
+ }
+
+ /**
+ * @param i The index of the child that will be used if this layout is
+ * part of a larger layout that is baseline aligned.
+ *
+ * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
+ */
+ @android.view.RemotableViewMethod
+ public void setBaselineAlignedChildIndex(int i) {
+ if ((i < 0) || (i >= getChildCount())) {
+ throw new IllegalArgumentException("base aligned child index out "
+ + "of range (0, " + getChildCount() + ")");
+ }
+ mBaselineAlignedChildIndex = i;
+ }
+
+ /**
+ * <p>Returns the view at the specified index. This method can be overriden
+ * to take into account virtual children. Refer to
+ * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+ * for an example.</p>
+ *
+ * @param index the child's index
+ * @return the child at the specified index
+ */
+ View getVirtualChildAt(int index) {
+ return getChildAt(index);
+ }
+
+ /**
+ * <p>Returns the virtual number of children. This number might be different
+ * than the actual number of children if the layout can hold virtual
+ * children. Refer to
+ * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+ * for an example.</p>
+ *
+ * @return the virtual number of children
+ */
+ int getVirtualChildCount() {
+ return getChildCount();
+ }
+
+ /**
+ * Returns the desired weights sum.
+ *
+ * @return A number greater than 0.0f if the weight sum is defined, or
+ * a number lower than or equals to 0.0f if not weight sum is
+ * to be used.
+ */
+ public float getWeightSum() {
+ return mWeightSum;
+ }
+
+ /**
+ * Defines the desired weights sum. If unspecified the weights sum is computed
+ * at layout time by adding the layout_weight of each child.
+ *
+ * This can be used for instance to give a single child 50% of the total
+ * available space by giving it a layout_weight of 0.5 and setting the
+ * weightSum to 1.0.
+ *
+ * @param weightSum a number greater than 0.0f, or a number lower than or equals
+ * to 0.0f if the weight sum should be computed from the children's
+ * layout_weight
+ */
+ @android.view.RemotableViewMethod
+ public void setWeightSum(float weightSum) {
+ mWeightSum = Math.max(0.0f, weightSum);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mOrientation == VERTICAL) {
+ measureVertical(widthMeasureSpec, heightMeasureSpec);
+ } else {
+ measureHorizontal(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ /**
+ * Determines where to position dividers between children.
+ *
+ * @param childIndex Index of child to check for preceding divider
+ * @return true if there should be a divider before the child at childIndex
+ * @hide Pending API consideration. Currently only used internally by the system.
+ */
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+ } else if (childIndex == getChildCount()) {
+ return (mShowDividers & SHOW_DIVIDER_END) != 0;
+ } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
+ boolean hasVisibleViewBefore = false;
+ for (int i = childIndex - 1; i >= 0; i--) {
+ if (getChildAt(i).getVisibility() != GONE) {
+ hasVisibleViewBefore = true;
+ break;
+ }
+ }
+ return hasVisibleViewBefore;
+ }
+ return false;
+ }
+
+ /**
+ * Measures the children when the orientation of this LinearLayout is set
+ * to {@link #VERTICAL}.
+ *
+ * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
+ * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
+ *
+ * @see #getOrientation()
+ * @see #setOrientation(int)
+ * @see #onMeasure(int, int)
+ */
+ void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
+ mTotalLength = 0;
+ int maxWidth = 0;
+ int childState = 0;
+ int alternativeMaxWidth = 0;
+ int weightedMaxWidth = 0;
+ boolean allFillParent = true;
+ float totalWeight = 0;
+
+ final int count = getVirtualChildCount();
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ boolean matchWidth = false;
+ boolean skippedMeasure = false;
+
+ final int baselineChildIndex = mBaselineAlignedChildIndex;
+ final boolean useLargestChild = mUseLargestChild;
+
+ int largestChildHeight = Integer.MIN_VALUE;
+
+ // See how tall everyone is. Also remember max width.
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == View.GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ if (hasDividerBeforeChildAt(i)) {
+ mTotalLength += mDividerHeight;
+ }
+
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ totalWeight += lp.weight;
+
+ if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
+ // Optimization: don't bother measuring children who are going to use
+ // leftover space. These views will get measured again down below if
+ // there is any leftover space.
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
+ skippedMeasure = true;
+ } else {
+ int oldHeight = Integer.MIN_VALUE;
+
+ if (lp.height == 0 && lp.weight > 0) {
+ // heightMode is either UNSPECIFIED or AT_MOST, and this
+ // child wanted to stretch to fill available space.
+ // Translate that to WRAP_CONTENT so that it does not end up
+ // with a height of 0
+ oldHeight = 0;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ }
+
+ // Determine how big this child would like to be. If this or
+ // previous children have given a weight, then we allow it to
+ // use all available space (and we will shrink things later
+ // if needed).
+ measureChildBeforeLayout(
+ child, i, widthMeasureSpec, 0, heightMeasureSpec,
+ totalWeight == 0 ? mTotalLength : 0);
+
+ if (oldHeight != Integer.MIN_VALUE) {
+ lp.height = oldHeight;
+ }
+
+ final int childHeight = child.getMeasuredHeight();
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
+ lp.bottomMargin + getNextLocationOffset(child));
+
+ if (useLargestChild) {
+ largestChildHeight = Math.max(childHeight, largestChildHeight);
+ }
+ }
+
+ /**
+ * If applicable, compute the additional offset to the child's baseline
+ * we'll need later when asked {@link #getBaseline}.
+ */
+ if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
+ mBaselineChildTop = mTotalLength;
+ }
+
+ // if we are trying to use a child index for our baseline, the above
+ // book keeping only works if there are no children above it with
+ // weight. fail fast to aid the developer.
+ if (i < baselineChildIndex && lp.weight > 0) {
+ throw new RuntimeException("A child of LinearLayout with index "
+ + "less than mBaselineAlignedChildIndex has weight > 0, which "
+ + "won't work. Either remove the weight, or don't set "
+ + "mBaselineAlignedChildIndex.");
+ }
+
+ boolean matchWidthLocally = false;
+ if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
+ // The width of the linear layout will scale, and at least one
+ // child said it wanted to match our width. Set a flag
+ // indicating that we need to remeasure at least that view when
+ // we know our width.
+ matchWidth = true;
+ matchWidthLocally = true;
+ }
+
+ final int margin = lp.leftMargin + lp.rightMargin;
+ final int measuredWidth = child.getMeasuredWidth() + margin;
+ maxWidth = Math.max(maxWidth, measuredWidth);
+ childState = combineMeasuredStates(childState, child.getMeasuredState());
+
+ allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
+ if (lp.weight > 0) {
+ /*
+ * Widths of weighted Views are bogus if we end up
+ * remeasuring, so keep them separate.
+ */
+ weightedMaxWidth = Math.max(weightedMaxWidth,
+ matchWidthLocally ? margin : measuredWidth);
+ } else {
+ alternativeMaxWidth = Math.max(alternativeMaxWidth,
+ matchWidthLocally ? margin : measuredWidth);
+ }
+
+ i += getChildrenSkipCount(child, i);
+ }
+
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
+ mTotalLength += mDividerHeight;
+ }
+
+ if (useLargestChild &&
+ (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams)
+ child.getLayoutParams();
+ // Account for negative margins
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
+ }
+ }
+
+ // Add in our padding
+ mTotalLength += mPaddingTop + mPaddingBottom;
+
+ int heightSize = mTotalLength;
+
+ // Check against our minimum height
+ heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
+
+ // Reconcile our calculated size with the heightMeasureSpec
+ int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
+ heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
+
+ // Either expand children with weight to take up available space or
+ // shrink them if they extend beyond our current bounds. If we skipped
+ // measurement on any children, we need to measure them now.
+ int delta = heightSize - mTotalLength;
+ if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
+ float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
+
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+
+ // MatchParentShrinkingLinearLayout custom code starts here.
+ if (childExtra > 0 && delta > 0) {
+ // Child said it could absorb extra space -- give him his share
+ int share = (int) (childExtra * delta / weightSum);
+ weightSum -= childExtra;
+ delta -= share;
+
+ final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+ mPaddingLeft + mPaddingRight +
+ lp.leftMargin + lp.rightMargin, lp.width);
+
+ // TODO: Use a field like lp.isMeasured to figure out if this
+ // child has been previously measured
+ if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
+ // child was measured once already above...
+ // base new measurement on stored values
+ int childHeight = child.getMeasuredHeight() + share;
+ if (childHeight < 0) {
+ childHeight = 0;
+ }
+
+ child.measure(childWidthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+ } else {
+ // child was skipped in the loop above.
+ // Measure for this first time here
+ child.measure(childWidthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
+ MeasureSpec.EXACTLY));
+ }
+
+ // Child may now not fit in vertical dimension.
+ childState = combineMeasuredStates(childState, child.getMeasuredState()
+ & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
+ } else if (delta < 0 && lp.height == LayoutParams.MATCH_PARENT) {
+ final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+ mPaddingLeft + mPaddingRight +
+ lp.leftMargin + lp.rightMargin, lp.width);
+
+ int childHeight = child.getMeasuredHeight() + delta;
+ if (childHeight < 0) {
+ childHeight = 0;
+ }
+ delta -= childHeight - child.getMeasuredHeight();
+
+ child.measure(childWidthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+
+ // Child may now not fit in vertical dimension.
+ childState = combineMeasuredStates(childState, child.getMeasuredState()
+ & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
+ }
+ // MatchParentShrinkingLinearLayout custom code ends here.
+
+ final int margin = lp.leftMargin + lp.rightMargin;
+ final int measuredWidth = child.getMeasuredWidth() + margin;
+ maxWidth = Math.max(maxWidth, measuredWidth);
+
+ boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
+ lp.width == LayoutParams.MATCH_PARENT;
+
+ alternativeMaxWidth = Math.max(alternativeMaxWidth,
+ matchWidthLocally ? margin : measuredWidth);
+
+ allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
+
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
+ }
+
+ // Add in our padding
+ mTotalLength += mPaddingTop + mPaddingBottom;
+ // TODO: Should we recompute the heightSpec based on the new total length?
+ } else {
+ alternativeMaxWidth = Math.max(alternativeMaxWidth,
+ weightedMaxWidth);
+
+
+ // We have no limit, so make all weighted views as tall as the largest child.
+ // Children will have already been measured once.
+ if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ float childExtra = lp.weight;
+ if (childExtra > 0) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(largestChildHeight,
+ MeasureSpec.EXACTLY));
+ }
+ }
+ }
+ }
+
+ if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
+ maxWidth = alternativeMaxWidth;
+ }
+
+ maxWidth += mPaddingLeft + mPaddingRight;
+
+ // Check against our minimum width
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+ heightSizeAndState);
+
+ if (matchWidth) {
+ forceUniformWidth(count, heightMeasureSpec);
+ }
+ }
+
+ private void forceUniformWidth(int count, int heightMeasureSpec) {
+ // Pretend that the linear layout has an exact size.
+ int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
+ MeasureSpec.EXACTLY);
+ for (int i = 0; i< count; ++i) {
+ final View child = getVirtualChildAt(i);
+ if (child.getVisibility() != GONE) {
+ LayoutParams lp =
+ ((LayoutParams)child.getLayoutParams());
+
+ if (lp.width == LayoutParams.MATCH_PARENT) {
+ // Temporarily force children to reuse their old measured height
+ // FIXME: this may not be right for something like wrapping text?
+ int oldHeight = lp.height;
+ lp.height = child.getMeasuredHeight();
+
+ // Remeasue with new dimensions
+ measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+ lp.height = oldHeight;
+ }
+ }
+ }
+ }
+
+ /**
+ * Measures the children when the orientation of this LinearLayout is set
+ * to {@link #HORIZONTAL}.
+ *
+ * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
+ * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
+ *
+ * @see #getOrientation()
+ * @see #setOrientation(int)
+ * @see #onMeasure(int, int)
+ */
+ void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
+ // MatchParentShrinkingLinearLayout custom code starts here.
+ throw new IllegalStateException("horizontal mode not supported.");
+ // MatchParentShrinkingLinearLayout custom code ends here.
+ }
+
+ private void forceUniformHeight(int count, int widthMeasureSpec) {
+ // Pretend that the linear layout has an exact size. This is the measured height of
+ // ourselves. The measured height should be the max height of the children, changed
+ // to accommodate the heightMeasureSpec from the parent
+ int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),
+ MeasureSpec.EXACTLY);
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+ if (child.getVisibility() != GONE) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (lp.height == LayoutParams.MATCH_PARENT) {
+ // Temporarily force children to reuse their old measured width
+ // FIXME: this may not be right for something like wrapping text?
+ int oldWidth = lp.width;
+ lp.width = child.getMeasuredWidth();
+
+ // Remeasure with new dimensions
+ measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
+ lp.width = oldWidth;
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>Returns the number of children to skip after measuring/laying out
+ * the specified child.</p>
+ *
+ * @param child the child after which we want to skip children
+ * @param index the index of the child after which we want to skip children
+ * @return the number of children to skip, 0 by default
+ */
+ int getChildrenSkipCount(View child, int index) {
+ return 0;
+ }
+
+ /**
+ * <p>Returns the size (width or height) that should be occupied by a null
+ * child.</p>
+ *
+ * @param childIndex the index of the null child
+ * @return the width or height of the child depending on the orientation
+ */
+ int measureNullChild(int childIndex) {
+ return 0;
+ }
+
+ /**
+ * <p>Measure the child according to the parent's measure specs. This
+ * method should be overriden by subclasses to force the sizing of
+ * children. This method is called by {@link #measureVertical(int, int)} and
+ * {@link #measureHorizontal(int, int)}.</p>
+ *
+ * @param child the child to measure
+ * @param childIndex the index of the child in this view
+ * @param widthMeasureSpec horizontal space requirements as imposed by the parent
+ * @param totalWidth extra space that has been used up by the parent horizontally
+ * @param heightMeasureSpec vertical space requirements as imposed by the parent
+ * @param totalHeight extra space that has been used up by the parent vertically
+ */
+ void measureChildBeforeLayout(View child, int childIndex,
+ int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
+ int totalHeight) {
+ measureChildWithMargins(child, widthMeasureSpec, totalWidth,
+ heightMeasureSpec, totalHeight);
+ }
+
+ /**
+ * <p>Return the location offset of the specified child. This can be used
+ * by subclasses to change the location of a given widget.</p>
+ *
+ * @param child the child for which to obtain the location offset
+ * @return the location offset in pixels
+ */
+ int getLocationOffset(View child) {
+ return 0;
+ }
+
+ /**
+ * <p>Return the size offset of the next sibling of the specified child.
+ * This can be used by subclasses to change the location of the widget
+ * following <code>child</code>.</p>
+ *
+ * @param child the child whose next sibling will be moved
+ * @return the location offset of the next child in pixels
+ */
+ int getNextLocationOffset(View child) {
+ return 0;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (mOrientation == VERTICAL) {
+ layoutVertical(l, t, r, b);
+ } else {
+ layoutHorizontal(l, t, r, b);
+ }
+ }
+
+ /**
+ * Position the children during a layout pass if the orientation of this
+ * LinearLayout is set to {@link #VERTICAL}.
+ *
+ * @see #getOrientation()
+ * @see #setOrientation(int)
+ * @see #onLayout(boolean, int, int, int, int)
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ */
+ void layoutVertical(int left, int top, int right, int bottom) {
+ final int paddingLeft = mPaddingLeft;
+
+ int childTop;
+ int childLeft;
+
+ // Where right end of child should go
+ final int width = right - left;
+ int childRight = width - mPaddingRight;
+
+ // Space available for child
+ int childSpace = width - paddingLeft - mPaddingRight;
+
+ final int count = getVirtualChildCount();
+
+ final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+ switch (majorGravity) {
+ case Gravity.BOTTOM:
+ // mTotalLength contains the padding already
+ childTop = mPaddingTop + bottom - top - mTotalLength;
+ break;
+
+ // mTotalLength contains the padding already
+ case Gravity.CENTER_VERTICAL:
+ childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
+ break;
+
+ case Gravity.TOP:
+ default:
+ childTop = mPaddingTop;
+ break;
+ }
+
+ for (int i = 0; i < count; i++) {
+ final View child = getVirtualChildAt(i);
+ if (child == null) {
+ childTop += measureNullChild(i);
+ } else if (child.getVisibility() != GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+
+ final LayoutParams lp =
+ (LayoutParams) child.getLayoutParams();
+
+ int gravity = lp.gravity;
+ if (gravity < 0) {
+ gravity = minorGravity;
+ }
+ final int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ + lp.leftMargin - lp.rightMargin;
+ break;
+
+ case Gravity.RIGHT:
+ childLeft = childRight - childWidth - lp.rightMargin;
+ break;
+
+ case Gravity.LEFT:
+ default:
+ childLeft = paddingLeft + lp.leftMargin;
+ break;
+ }
+
+ if (hasDividerBeforeChildAt(i)) {
+ childTop += mDividerHeight;
+ }
+
+ childTop += lp.topMargin;
+ setChildFrame(child, childLeft, childTop + getLocationOffset(child),
+ childWidth, childHeight);
+ childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
+
+ i += getChildrenSkipCount(child, i);
+ }
+ }
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ if (layoutDirection != mLayoutDirection) {
+ mLayoutDirection = layoutDirection;
+ if (mOrientation == HORIZONTAL) {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * Position the children during a layout pass if the orientation of this
+ * LinearLayout is set to {@link #HORIZONTAL}.
+ *
+ * @see #getOrientation()
+ * @see #setOrientation(int)
+ * @see #onLayout(boolean, int, int, int, int)
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ */
+ void layoutHorizontal(int left, int top, int right, int bottom) {
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int paddingTop = mPaddingTop;
+
+ int childTop;
+ int childLeft;
+
+ // Where bottom of child should go
+ final int height = bottom - top;
+ int childBottom = height - mPaddingBottom;
+
+ // Space available for child
+ int childSpace = height - paddingTop - mPaddingBottom;
+
+ final int count = getVirtualChildCount();
+
+ final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+ final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ final boolean baselineAligned = mBaselineAligned;
+
+ final int[] maxAscent = mMaxAscent;
+ final int[] maxDescent = mMaxDescent;
+
+ final int layoutDirection = getLayoutDirection();
+ switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
+ case Gravity.RIGHT:
+ // mTotalLength contains the padding already
+ childLeft = mPaddingLeft + right - left - mTotalLength;
+ break;
+
+ case Gravity.CENTER_HORIZONTAL:
+ // mTotalLength contains the padding already
+ childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
+ break;
+
+ case Gravity.LEFT:
+ default:
+ childLeft = mPaddingLeft;
+ break;
+ }
+
+ int start = 0;
+ int dir = 1;
+ //In case of RTL, start drawing from the last child.
+ if (isLayoutRtl) {
+ start = count - 1;
+ dir = -1;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int childIndex = start + dir * i;
+ final View child = getVirtualChildAt(childIndex);
+
+ if (child == null) {
+ childLeft += measureNullChild(childIndex);
+ } else if (child.getVisibility() != GONE) {
+ final int childWidth = child.getMeasuredWidth();
+ final int childHeight = child.getMeasuredHeight();
+ int childBaseline = -1;
+
+ final LayoutParams lp =
+ (LayoutParams) child.getLayoutParams();
+
+ if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
+ childBaseline = child.getBaseline();
+ }
+
+ int gravity = lp.gravity;
+ if (gravity < 0) {
+ gravity = minorGravity;
+ }
+
+ switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ childTop = paddingTop + lp.topMargin;
+ if (childBaseline != -1) {
+ childTop += maxAscent[INDEX_TOP] - childBaseline;
+ }
+ break;
+
+ case Gravity.CENTER_VERTICAL:
+ // Removed support for baseline alignment when layout_gravity or
+ // gravity == center_vertical. See bug #1038483.
+ // Keep the code around if we need to re-enable this feature
+ // if (childBaseline != -1) {
+ // // Align baselines vertically only if the child is smaller than us
+ // if (childSpace - childHeight > 0) {
+ // childTop = paddingTop + (childSpace / 2) - childBaseline;
+ // } else {
+ // childTop = paddingTop + (childSpace - childHeight) / 2;
+ // }
+ // } else {
+ childTop = paddingTop + ((childSpace - childHeight) / 2)
+ + lp.topMargin - lp.bottomMargin;
+ break;
+
+ case Gravity.BOTTOM:
+ childTop = childBottom - childHeight - lp.bottomMargin;
+ if (childBaseline != -1) {
+ int descent = child.getMeasuredHeight() - childBaseline;
+ childTop -= (maxDescent[INDEX_BOTTOM] - descent);
+ }
+ break;
+ default:
+ childTop = paddingTop;
+ break;
+ }
+
+ if (hasDividerBeforeChildAt(childIndex)) {
+ childLeft += mDividerWidth;
+ }
+
+ childLeft += lp.leftMargin;
+ setChildFrame(child, childLeft + getLocationOffset(child), childTop,
+ childWidth, childHeight);
+ childLeft += childWidth + lp.rightMargin +
+ getNextLocationOffset(child);
+
+ i += getChildrenSkipCount(child, childIndex);
+ }
+ }
+ }
+
+ private void setChildFrame(View child, int left, int top, int width, int height) {
+ child.layout(left, top, left + width, top + height);
+ }
+
+ /**
+ * Should the layout be a column or a row.
+ * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default
+ * value is {@link #HORIZONTAL}.
+ *
+ * @attr ref android.R.styleable#LinearLayout_orientation
+ */
+ public void setOrientation(@OrientationMode int orientation) {
+ if (mOrientation != orientation) {
+ mOrientation = orientation;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Returns the current orientation.
+ *
+ * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
+ */
+ @OrientationMode
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ /**
+ * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If
+ * this layout has a VERTICAL orientation, this controls where all the child
+ * views are placed if there is extra vertical space. If this layout has a
+ * HORIZONTAL orientation, this controls the alignment of the children.
+ *
+ * @param gravity See {@link android.view.Gravity}
+ *
+ * @attr ref android.R.styleable#LinearLayout_gravity
+ */
+ @android.view.RemotableViewMethod
+ public void setGravity(int gravity) {
+ if (mGravity != gravity) {
+ if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.START;
+ }
+
+ if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+ gravity |= Gravity.TOP;
+ }
+
+ mGravity = gravity;
+ requestLayout();
+ }
+ }
+
+ @android.view.RemotableViewMethod
+ public void setHorizontalGravity(int horizontalGravity) {
+ final int gravity = horizontalGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+ if ((mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {
+ mGravity = (mGravity & ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity;
+ requestLayout();
+ }
+ }
+
+ @android.view.RemotableViewMethod
+ public void setVerticalGravity(int verticalGravity) {
+ final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
+ mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity;
+ requestLayout();
+ }
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ /**
+ * Returns a set of layout parameters with a width of
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * when the layout's orientation is {@link #VERTICAL}. When the orientation is
+ * {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT}
+ * and the height to {@link LayoutParams#WRAP_CONTENT}.
+ */
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ if (mOrientation == HORIZONTAL) {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ } else if (mOrientation == VERTICAL) {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+ return null;
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+
+ // Override to allow type-checking of LayoutParams.
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return MatchParentShrinkingLinearLayout.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+ encoder.addProperty("layout:baselineAligned", mBaselineAligned);
+ encoder.addProperty("layout:baselineAlignedChildIndex", mBaselineAlignedChildIndex);
+ encoder.addProperty("measurement:baselineChildTop", mBaselineChildTop);
+ encoder.addProperty("measurement:orientation", mOrientation);
+ encoder.addProperty("measurement:gravity", mGravity);
+ encoder.addProperty("measurement:totalLength", mTotalLength);
+ encoder.addProperty("layout:totalLength", mTotalLength);
+ encoder.addProperty("layout:useLargestChild", mUseLargestChild);
+ }
+
+ /**
+ * Per-child layout information associated with ViewLinearLayout.
+ *
+ * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
+ * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
+ */
+ public static class LayoutParams extends MarginLayoutParams {
+ /**
+ * Indicates how much of the extra space in the LinearLayout will be
+ * allocated to the view associated with these LayoutParams. Specify
+ * 0 if the view should not be stretched. Otherwise the extra pixels
+ * will be pro-rated among all views whose weight is greater than 0.
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ public float weight;
+
+ /**
+ * Gravity for the view associated with these LayoutParams.
+ *
+ * @see android.view.Gravity
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = -1, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
+ @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
+ @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
+ @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.START, to = "START"),
+ @ViewDebug.IntToString(from = Gravity.END, to = "END"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
+ })
+ public int gravity = -1;
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ TypedArray a = c.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.LinearLayout_Layout);
+
+ weight = a.getFloat(
+ com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
+ gravity = a.getInt(
+ com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
+
+ a.recycle();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ weight = 0;
+ }
+
+ /**
+ * Creates a new set of layout parameters with the specified width, height
+ * and weight.
+ *
+ * @param width the width, either {@link #MATCH_PARENT},
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param height the height, either {@link #MATCH_PARENT},
+ * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param weight the weight
+ */
+ public LayoutParams(int width, int height, float weight) {
+ super(width, height);
+ this.weight = weight;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(ViewGroup.LayoutParams p) {
+ super(p);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ /**
+ * Copy constructor. Clones the width, height, margin values, weight,
+ * and gravity of the source.
+ *
+ * @param source The layout params to copy from.
+ */
+ public LayoutParams(LayoutParams source) {
+ super(source);
+
+ this.weight = source.weight;
+ this.gravity = source.gravity;
+ }
+
+ @Override
+ public String debug(String output) {
+ return output + "MatchParentShrinkingLinearLayout.LayoutParams={width="
+ + sizeToString(width) + ", height=" + sizeToString(height)
+ + " weight=" + weight + "}";
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ encoder.addProperty("layout:weight", weight);
+ encoder.addProperty("layout:gravity", gravity);
+ }
+ }
+}