diff options
113 files changed, 7259 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1736165 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +values-v1 diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..26b9dfd --- /dev/null +++ b/Android.mk @@ -0,0 +1,30 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_MODULE_TAGS := optional + +LOCAL_PACKAGE_NAME := CMSetupWizard +LOCAL_CERTIFICATE := platform +LOCAL_PRIVILEGED_MODULE := true + +LOCAL_PROGUARD_FLAG_FILES := proguard.flags + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-v4 \ + android-support-v13 \ + play \ + libphonenumber + +LOCAL_JAVA_LIBRARIES += org.cyanogenmod.hardware + +# Include res dir from chips +google_play_dir := ../../../external/google/google_play_services/libproject/google-play-services_lib/res +res_dir := $(google_play_dir) res + +LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dir)) +LOCAL_AAPT_FLAGS := --auto-add-overlay +LOCAL_AAPT_FLAGS += --extra-packages com.google.android.gms + +include $(BUILD_PACKAGE) diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..8dd4b9b --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.cyanogenmod.setupwizard" + android:versionCode="3"> + + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.STATUS_BAR" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.SET_TIME_ZONE" /> + <uses-permission android:name="android.permission.SET_TIME" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.ACCESS_THEME_MANAGER"/> + <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" /> + <uses-permission android:name="android.permission.HARDWARE_ABSTRACTION_ACCESS" /> + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" /> + + <application android:label="@string/app_name" + android:icon="@drawable/icon" + android:theme="@style/Theme.Setup" + android:name=".SetupWizardApp"> + + <meta-data android:name="com.google.android.gms.version" + android:value="@integer/google_play_services_version" /> + + <activity android:name=".ui.SetupWizardActivity" + android:label="@string/product_name" + android:launchMode="singleInstance" + android:excludeFromRecents="true" + android:immersive="true"> + + <intent-filter android:priority="9"> + + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.DEVICE_INITIALIZATION_WIZARD" /> + + <category android:name="android.intent.category.HOME" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/proguard.flags b/proguard.flags new file mode 100644 index 0000000..979e6bc --- /dev/null +++ b/proguard.flags @@ -0,0 +1,3 @@ +-keep class * extends java.util.ListResourceBundle { + protected Object[][] getContents(); +}
\ No newline at end of file diff --git a/res/anim/fadein.xml b/res/anim/fadein.xml new file mode 100644 index 0000000..6773402 --- /dev/null +++ b/res/anim/fadein.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@android:anim/accelerate_interpolator" + android:duration="1000"/> +</set>
\ No newline at end of file diff --git a/res/anim/fadeout.xml b/res/anim/fadeout.xml new file mode 100644 index 0000000..1922a80 --- /dev/null +++ b/res/anim/fadeout.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@android:anim/accelerate_interpolator" + android:duration="1000"/> +</set>
\ No newline at end of file diff --git a/res/anim/slide_left.xml b/res/anim/slide_left.xml new file mode 100644 index 0000000..fa15cc7 --- /dev/null +++ b/res/anim/slide_left.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<set> + <translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromXDelta="-100%" + android:toXDelta="0" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="500"/> +</set> diff --git a/res/anim/slide_right.xml b/res/anim/slide_right.xml new file mode 100644 index 0000000..849f0c8 --- /dev/null +++ b/res/anim/slide_right.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<set> + <translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromXDelta="0" + android:toXDelta="100%" + android:interpolator="@android:anim/decelerate_interpolator" + android:duration="500"/> +</set> diff --git a/res/drawable-hdpi/brand.png b/res/drawable-hdpi/brand.png Binary files differnew file mode 100755 index 0000000..df120af --- /dev/null +++ b/res/drawable-hdpi/brand.png diff --git a/res/drawable-hdpi/brand_finish.png b/res/drawable-hdpi/brand_finish.png Binary files differnew file mode 100755 index 0000000..5c126f5 --- /dev/null +++ b/res/drawable-hdpi/brand_finish.png diff --git a/res/drawable-hdpi/finish_logo.png b/res/drawable-hdpi/finish_logo.png Binary files differnew file mode 100755 index 0000000..5c126f5 --- /dev/null +++ b/res/drawable-hdpi/finish_logo.png diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png Binary files differnew file mode 100644 index 0000000..974c26b --- /dev/null +++ b/res/drawable-hdpi/icon.png diff --git a/res/drawable-hdpi/powered_by.png b/res/drawable-hdpi/powered_by.png Binary files differnew file mode 100755 index 0000000..4d3d289 --- /dev/null +++ b/res/drawable-hdpi/powered_by.png diff --git a/res/drawable-hdpi/sim_back.png b/res/drawable-hdpi/sim_back.png Binary files differnew file mode 100755 index 0000000..889ebbe --- /dev/null +++ b/res/drawable-hdpi/sim_back.png diff --git a/res/drawable-hdpi/sim_side.png b/res/drawable-hdpi/sim_side.png Binary files differnew file mode 100755 index 0000000..03b5363 --- /dev/null +++ b/res/drawable-hdpi/sim_side.png diff --git a/res/drawable-mdpi/brand.png b/res/drawable-mdpi/brand.png Binary files differnew file mode 100755 index 0000000..b1bb320 --- /dev/null +++ b/res/drawable-mdpi/brand.png diff --git a/res/drawable-mdpi/brand_finish.png b/res/drawable-mdpi/brand_finish.png Binary files differnew file mode 100755 index 0000000..06f1b56 --- /dev/null +++ b/res/drawable-mdpi/brand_finish.png diff --git a/res/drawable-mdpi/finish_logo.png b/res/drawable-mdpi/finish_logo.png Binary files differnew file mode 100755 index 0000000..06f1b56 --- /dev/null +++ b/res/drawable-mdpi/finish_logo.png diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png Binary files differnew file mode 100644 index 0000000..ae9b93d --- /dev/null +++ b/res/drawable-mdpi/icon.png diff --git a/res/drawable-mdpi/powered_by.png b/res/drawable-mdpi/powered_by.png Binary files differnew file mode 100755 index 0000000..7dc3ffc --- /dev/null +++ b/res/drawable-mdpi/powered_by.png diff --git a/res/drawable-mdpi/sim_back.png b/res/drawable-mdpi/sim_back.png Binary files differnew file mode 100755 index 0000000..027f71d --- /dev/null +++ b/res/drawable-mdpi/sim_back.png diff --git a/res/drawable-mdpi/sim_side.png b/res/drawable-mdpi/sim_side.png Binary files differnew file mode 100755 index 0000000..7353e7f --- /dev/null +++ b/res/drawable-mdpi/sim_side.png diff --git a/res/drawable-xhdpi/brand.png b/res/drawable-xhdpi/brand.png Binary files differnew file mode 100755 index 0000000..38f04c5 --- /dev/null +++ b/res/drawable-xhdpi/brand.png diff --git a/res/drawable-xhdpi/brand_finish.png b/res/drawable-xhdpi/brand_finish.png Binary files differnew file mode 100755 index 0000000..5569224 --- /dev/null +++ b/res/drawable-xhdpi/brand_finish.png diff --git a/res/drawable-xhdpi/finish_logo.png b/res/drawable-xhdpi/finish_logo.png Binary files differnew file mode 100755 index 0000000..5569224 --- /dev/null +++ b/res/drawable-xhdpi/finish_logo.png diff --git a/res/drawable-xhdpi/icon.png b/res/drawable-xhdpi/icon.png Binary files differnew file mode 100644 index 0000000..55bede3 --- /dev/null +++ b/res/drawable-xhdpi/icon.png diff --git a/res/drawable-xhdpi/powered_by.png b/res/drawable-xhdpi/powered_by.png Binary files differnew file mode 100755 index 0000000..21aa1c7 --- /dev/null +++ b/res/drawable-xhdpi/powered_by.png diff --git a/res/drawable-xhdpi/sim_back.png b/res/drawable-xhdpi/sim_back.png Binary files differnew file mode 100755 index 0000000..22a032c --- /dev/null +++ b/res/drawable-xhdpi/sim_back.png diff --git a/res/drawable-xhdpi/sim_side.png b/res/drawable-xhdpi/sim_side.png Binary files differnew file mode 100755 index 0000000..8cf1d31 --- /dev/null +++ b/res/drawable-xhdpi/sim_side.png diff --git a/res/drawable-xxhdpi/brand.png b/res/drawable-xxhdpi/brand.png Binary files differnew file mode 100755 index 0000000..fd370bb --- /dev/null +++ b/res/drawable-xxhdpi/brand.png diff --git a/res/drawable-xxhdpi/brand_finish.png b/res/drawable-xxhdpi/brand_finish.png Binary files differnew file mode 100755 index 0000000..367bcff --- /dev/null +++ b/res/drawable-xxhdpi/brand_finish.png diff --git a/res/drawable-xxhdpi/finish_logo.png b/res/drawable-xxhdpi/finish_logo.png Binary files differnew file mode 100755 index 0000000..367bcff --- /dev/null +++ b/res/drawable-xxhdpi/finish_logo.png diff --git a/res/drawable-xxhdpi/powered_by.png b/res/drawable-xxhdpi/powered_by.png Binary files differnew file mode 100755 index 0000000..5bc7625 --- /dev/null +++ b/res/drawable-xxhdpi/powered_by.png diff --git a/res/drawable-xxhdpi/sim_back.png b/res/drawable-xxhdpi/sim_back.png Binary files differnew file mode 100755 index 0000000..d6b7d8c --- /dev/null +++ b/res/drawable-xxhdpi/sim_back.png diff --git a/res/drawable-xxhdpi/sim_side.png b/res/drawable-xxhdpi/sim_side.png Binary files differnew file mode 100755 index 0000000..8f257f8 --- /dev/null +++ b/res/drawable-xxhdpi/sim_side.png diff --git a/res/drawable-xxxhdpi/brand.png b/res/drawable-xxxhdpi/brand.png Binary files differnew file mode 100755 index 0000000..37c6654 --- /dev/null +++ b/res/drawable-xxxhdpi/brand.png diff --git a/res/drawable-xxxhdpi/brand_finish.png b/res/drawable-xxxhdpi/brand_finish.png Binary files differnew file mode 100755 index 0000000..aef7cb7 --- /dev/null +++ b/res/drawable-xxxhdpi/brand_finish.png diff --git a/res/drawable-xxxhdpi/finish_logo.png b/res/drawable-xxxhdpi/finish_logo.png Binary files differnew file mode 100755 index 0000000..aef7cb7 --- /dev/null +++ b/res/drawable-xxxhdpi/finish_logo.png diff --git a/res/drawable-xxxhdpi/powered_by.png b/res/drawable-xxxhdpi/powered_by.png Binary files differnew file mode 100755 index 0000000..5bc7625 --- /dev/null +++ b/res/drawable-xxxhdpi/powered_by.png diff --git a/res/drawable-xxxhdpi/sim_back.png b/res/drawable-xxxhdpi/sim_back.png Binary files differnew file mode 100755 index 0000000..eedd9d4 --- /dev/null +++ b/res/drawable-xxxhdpi/sim_back.png diff --git a/res/drawable-xxxhdpi/sim_side.png b/res/drawable-xxxhdpi/sim_side.png Binary files differnew file mode 100755 index 0000000..a3c5957 --- /dev/null +++ b/res/drawable-xxxhdpi/sim_side.png diff --git a/res/drawable/divider.xml b/res/drawable/divider.xml new file mode 100644 index 0000000..fb38c5b --- /dev/null +++ b/res/drawable/divider.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <solid android:color="@color/divider" /> + <size android:width="316dp" android:height="1dp"/> +</shape>
\ No newline at end of file diff --git a/res/drawable/ic_chevron_left_dark.xml b/res/drawable/ic_chevron_left_dark.xml new file mode 100644 index 0000000..11079af --- /dev/null +++ b/res/drawable/ic_chevron_left_dark.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@color/primary_text" + android:pathData="M15.41,7.41L14,6l-6,6l6,6l1.41-1.41L10.83,12L15.41,7.41Z" /> +</vector> diff --git a/res/drawable/ic_chevron_left_wht.xml b/res/drawable/ic_chevron_left_wht.xml new file mode 100644 index 0000000..b51cb23 --- /dev/null +++ b/res/drawable/ic_chevron_left_wht.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@color/white" + android:pathData="M15.41,7.41L14,6l-6,6l6,6l1.41-1.41L10.83,12L15.41,7.41Z" /> +</vector> diff --git a/res/drawable/ic_chevron_right_dark.xml b/res/drawable/ic_chevron_right_dark.xml new file mode 100644 index 0000000..b322811 --- /dev/null +++ b/res/drawable/ic_chevron_right_dark.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@color/primary_text" + android:pathData="M10,6L8.59,7.41L13.17,12l-4.58,4.59L10,18l6-6L10,6Z" /> +</vector> diff --git a/res/drawable/ic_chevron_right_wht.xml b/res/drawable/ic_chevron_right_wht.xml new file mode 100644 index 0000000..95b8160 --- /dev/null +++ b/res/drawable/ic_chevron_right_wht.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@color/white" + android:pathData="M10,6L8.59,7.41L13.17,12l-4.58,4.59L10,18l6-6L10,6Z" /> +</vector> diff --git a/res/drawable/ic_signal_0.xml b/res/drawable/ic_signal_0.xml new file mode 100644 index 0000000..2c93a12 --- /dev/null +++ b/res/drawable/ic_signal_0.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/> +</vector> diff --git a/res/drawable/ic_signal_1.xml b/res/drawable/ic_signal_1.xml new file mode 100644 index 0000000..a797951 --- /dev/null +++ b/res/drawable/ic_signal_1.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/> + <path + android:fillColor="@color/accent" + android:pathData="M11.300000,12.700000l-9.300000,9.300000 9.300000,0.000000z"/> +</vector> diff --git a/res/drawable/ic_signal_2.xml b/res/drawable/ic_signal_2.xml new file mode 100644 index 0000000..f7f8b96 --- /dev/null +++ b/res/drawable/ic_signal_2.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/> + <path + android:fillColor="@color/accent" + android:pathData="M14.000000,10.000000l-12.000000,12.000000 12.000000,0.000000z"/> +</vector> diff --git a/res/drawable/ic_signal_3.xml b/res/drawable/ic_signal_3.xml new file mode 100644 index 0000000..a5abaae --- /dev/null +++ b/res/drawable/ic_signal_3.xml @@ -0,0 +1,28 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/> + <path + android:fillColor="@color/accent" + android:pathData="M16.700001,7.300000l-14.700001,14.700000 14.700001,0.000000z"/> +</vector> diff --git a/res/drawable/ic_signal_4.xml b/res/drawable/ic_signal_4.xml new file mode 100644 index 0000000..fb2ada0 --- /dev/null +++ b/res/drawable/ic_signal_4.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="@color/accent" + android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/> +</vector> diff --git a/res/drawable/ic_signal_no_signal.xml b/res/drawable/ic_signal_no_signal.xml new file mode 100644 index 0000000..98f15be --- /dev/null +++ b/res/drawable/ic_signal_no_signal.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2014 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFF" + android:pathData="M2.000000,22.000000l20.000000,0.000000L22.000000,2.000000L2.000000,22.000000zM20.000000,20.000000L6.800000,20.000000L20.000000,6.800000L20.000000,20.000000z"/> +</vector> diff --git a/res/layout/button_bar.xml b/res/layout/button_bar.xml new file mode 100644 index 0000000..9a6f9fa --- /dev/null +++ b/res/layout/button_bar.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/button_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + style="@style/ButtonBar.Left" + android:id="@+id/prev_button" + android:layout_width="0dp" + android:layout_weight="1" + android:text="@string/prev" /> + + <Button + style="@style/ButtonBar.Right" + android:id="@+id/next_button" + android:layout_width="0dp" + android:layout_weight="1" + android:text="@string/next" /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/choose_data_sim_page.xml b/res/layout/choose_data_sim_page.xml new file mode 100644 index 0000000..95162d3 --- /dev/null +++ b/res/layout/choose_data_sim_page.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:id="@+id/page_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/PageSummaryText" + android:textSize="15sp" + android:paddingTop="@dimen/content_margin_top" + android:paddingLeft="@dimen/content_margin_left" + android:paddingRight="@dimen/content_margin_right" + android:paddingBottom="@dimen/summary_margin_bottom" + android:text="@string/choose_data_sim_summary" /> + + <include layout="@layout/divider" /> + + </LinearLayout> +</ScrollView> + + diff --git a/res/layout/data_sim_row.xml b/res/layout/data_sim_row.xml new file mode 100644 index 0000000..c75fd2d --- /dev/null +++ b/res/layout/data_sim_row.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/sim_row" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:paddingLeft="@dimen/content_margin_left" + android:paddingRight="@dimen/data_switch_margin_right" + android:background="?android:attr/selectableItemBackground" + android:clickable="true"> + + <ImageView + android:id="@+id/signal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_signal_0" /> + + <TextView + android:id="@+id/sim_title" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textSize="14sp" + android:layout_marginLeft="@dimen/carrier_text_margin_left" + android:maxLines="4" /> + + <CheckBox + android:id="@+id/enable_check" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:duplicateParentState="true" + android:clickable="false"/> + +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/date_time_setup_custom_list_item_2.xml b/res/layout/date_time_setup_custom_list_item_2.xml new file mode 100644 index 0000000..69ef5c1 --- /dev/null +++ b/res/layout/date_time_setup_custom_list_item_2.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<!-- Based on simple_list_item_2.xml in framework --> +<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:mode="twoLine" + android:gravity="center_vertical"> + + <TextView + android:id="@android:id/text1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="16dip" + style="@style/SpinnerItem" /> + + <TextView + android:id="@android:id/text2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="10dip" + android:layout_below="@android:id/text1" + android:layout_alignStart="@android:id/text1" + android:fontFamily="sans-serif-medium" + style="@style/SpinnerItem" /> +</TwoLineListItem> diff --git a/res/layout/divider.xml b/res/layout/divider.xml new file mode 100644 index 0000000..770c74d --- /dev/null +++ b/res/layout/divider.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:src="@drawable/divider" />
\ No newline at end of file diff --git a/res/layout/header.xml b/res/layout/header.xml new file mode 100644 index 0000000..2e76a08 --- /dev/null +++ b/res/layout/header.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/header_height"> + + <TextView + android:layout_width="match_parent" + android:layout_height="@dimen/page_title_height" + style="@style/PageTitle" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/header_condensed.xml b/res/layout/header_condensed.xml new file mode 100644 index 0000000..c47062c --- /dev/null +++ b/res/layout/header_condensed.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/page_title_height"> + + <TextView + android:layout_width="match_parent" + android:layout_height="@dimen/page_title_height" + style="@style/PageTitle" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/locale_picker.xml b/res/layout/locale_picker.xml new file mode 100644 index 0000000..6c943c5 --- /dev/null +++ b/res/layout/locale_picker.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2008, 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. +*/ +--> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + + <ImageButton android:id="@+id/increment" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="22dip" + android:paddingBottom="22dip"/> + + <EditText + android:id="@+id/numberpicker_input" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMediumInverse" + android:gravity="center" + android:singleLine="true" /> + + <ImageButton android:id="@+id/decrement" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="22dip" + android:paddingBottom="22dip"/> + +</merge> diff --git a/res/layout/locale_picker_item.xml b/res/layout/locale_picker_item.xml new file mode 100644 index 0000000..1beabd4 --- /dev/null +++ b/res/layout/locale_picker_item.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:gravity="left" + android:paddingStart="16dp" + android:paddingTop="6dp" + android:paddingBottom="6dp" + android:paddingEnd="16dp"> + + <TextView + android:id="@+id/locale" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="left" + android:textAppearance="?android:attr/textAppearanceMedium"/> +</LinearLayout> diff --git a/res/layout/location_settings.xml b/res/layout/location_settings.xml new file mode 100644 index 0000000..393a180 --- /dev/null +++ b/res/layout/location_settings.xml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="@style/PageContent"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="15sp" + android:layout_marginBottom="@dimen/summary_margin_bottom" + style="@style/PageSummaryText" + android:text="@string/location_services_summary" /> + + <LinearLayout + android:id="@+id/location" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/location_margin_left" + android:layout_marginRight="@dimen/content_margin_right" + android:background="?android:attr/selectableItemBackground" + android:clickable="true"> + + + <CheckBox + android:id="@+id/location_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginTop="5dp" + android:duplicateParentState="true" + android:clickable="false" /> + + <TextView + android:id="@+id/location_summary" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textSize="15sp" + android:gravity="top" + android:layout_marginLeft="@dimen/location_text_margin_left" + android:layout_marginRight="@dimen/location_text_margin_right" + android:paddingBottom="@dimen/content_margin_bottom" + android:text="@string/location_access_summary" + android:maxLines="5" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/gps" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/location_margin_left" + android:layout_marginRight="@dimen/content_margin_right" + android:background="?android:attr/selectableItemBackground" + android:clickable="true"> + + <CheckBox + android:id="@+id/gps_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginTop="5dp" + android:duplicateParentState="true" + android:clickable="false" /> + + + <TextView + android:id="@+id/gps_summary" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textSize="15sp" + android:gravity="top" + android:layout_marginLeft="@dimen/location_text_margin_left" + android:layout_marginRight="@dimen/location_text_margin_right" + android:paddingBottom="@dimen/content_margin_bottom" + android:text="@string/location_gps" + android:maxLines="5" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/network" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/location_margin_left" + android:layout_marginRight="@dimen/content_margin_right" + android:background="?android:attr/selectableItemBackground" + android:clickable="true"> + + <CheckBox + android:id="@+id/network_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="top" + android:layout_marginTop="5dp" + android:duplicateParentState="true" + android:clickable="false" /> + + <TextView + android:id="@+id/network_summary" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textSize="15sp" + android:gravity="top" + android:layout_marginLeft="@dimen/location_text_margin_left" + android:layout_marginRight="@dimen/location_text_margin_right" + android:paddingBottom="@dimen/content_margin_bottom" + android:text="@string/location_network" + android:maxLines="5" /> + + </LinearLayout> + </LinearLayout> +</ScrollView> diff --git a/res/layout/logo_header.xml b/res/layout/logo_header.xml new file mode 100644 index 0000000..5aa8df4 --- /dev/null +++ b/res/layout/logo_header.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/logo_header_height"> + + <ImageView + android:id="@+id/brand_logo" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/brand" + android:paddingLeft="@dimen/header_logo_margin_left" + android:paddingBottom="@dimen/header_logo_margin_bottom" + android:layout_above="@+id/powered_by_logo"/> + + <ImageView + android:id="@+id/powered_by_logo" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/powered_by" + android:visibility="gone" + android:paddingLeft="@dimen/header_logo_margin_left" + android:paddingBottom="@dimen/header_logo_margin_bottom" + android:layout_above="@android:id/title"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="@dimen/page_title_height" + style="@style/PageTitle" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/mobile_data_settings.xml b/res/layout/mobile_data_settings.xml new file mode 100644 index 0000000..2ac3b35 --- /dev/null +++ b/res/layout/mobile_data_settings.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/mobile_data_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/PageSummaryText" + android:textSize="15sp" + android:paddingTop="@dimen/content_margin_top" + android:paddingLeft="@dimen/content_margin_left" + android:paddingRight="@dimen/content_margin_right" + android:paddingBottom="@dimen/summary_margin_bottom" + android:text="@string/enable_mobile_data_summary" /> + + <include layout="@layout/divider" /> + + <LinearLayout + android:id="@+id/data" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:paddingLeft="@dimen/content_margin_left" + android:paddingRight="@dimen/data_switch_margin_right" + android:background="?android:attr/selectableItemBackground" + android:clickable="true"> + + <ImageView + android:id="@+id/signal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_signal_0" /> + + <TextView + android:id="@+id/enable_data_title" + android:layout_width="0px" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textSize="14sp" + android:layout_marginLeft="@dimen/carrier_text_margin_left" + android:text="@string/setup_mobile_data_no_service" + android:maxLines="4" /> + + <Switch + android:id="@+id/data_switch" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:duplicateParentState="true" + android:clickable="false" + android:textOff="@string/enable_mobile_off" + android:textOn="@string/enable_mobile_on"/> + </LinearLayout> + + <include layout="@layout/divider" /> + + </LinearLayout> +</ScrollView> + diff --git a/res/layout/setup_datetime_page.xml b/res/layout/setup_datetime_page.xml new file mode 100644 index 0000000..2622dad --- /dev/null +++ b/res/layout/setup_datetime_page.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/PageContent"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/PageSummaryText" + android:textSize="16sp" + android:layout_marginBottom="@dimen/summary_margin_bottom" + android:paddingRight="@dimen/location_text_margin_right" + android:text="@string/date_time_summary" /> + + <Spinner + android:id="@+id/timezone_list" + android:layout_width="290dp" + android:layout_height="wrap_content" + android:saveEnabled="false"/> + + <TwoLineListItem + android:id="@+id/date_item" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:mode="twoLine" + android:clickable="true" + android:background="?android:attr/selectableItemBackground" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/date_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/setup_current_date" + style="@style/SpinnerItem" /> + + <TextView + android:id="@+id/date_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/date_title" + android:layout_alignStart="@id/date_title" + android:fontFamily="sans-serif-medium" + style="@style/SpinnerItem" /> + </TwoLineListItem> + + <TwoLineListItem + android:id="@+id/time_item" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:mode="twoLine" + android:clickable="true" + android:background="?android:attr/selectableItemBackground" + android:gravity="center_vertical"> + + <TextView + android:id="@+id/time_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/setup_current_time" + style="@style/SpinnerItem" /> + + <TextView + android:id="@+id/time_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/time_title" + android:layout_alignStart="@id/time_title" + android:fontFamily="sans-serif-medium" + style="@style/SpinnerItem" /> + </TwoLineListItem> + + </LinearLayout> +</ScrollView> diff --git a/res/layout/setup_finalizing.xml b/res/layout/setup_finalizing.xml new file mode 100644 index 0000000..60a30d7 --- /dev/null +++ b/res/layout/setup_finalizing.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <ProgressBar + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:indeterminate="true" + style="@android:style/Widget.Material.ProgressBar.Horizontal" /> + +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/setup_finished_page.xml b/res/layout/setup_finished_page.xml new file mode 100644 index 0000000..9462d06 --- /dev/null +++ b/res/layout/setup_finished_page.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/primary"> + + <ImageView + android:id="@+id/brand_logo" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:src="@drawable/finish_logo" + android:scaleType="centerInside"/> + +</LinearLayout> + + diff --git a/res/layout/setup_main.xml b/res/layout/setup_main.xml new file mode 100644 index 0000000..c29af05 --- /dev/null +++ b/res/layout/setup_main.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/root" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="@style/RootView"> + + <LinearLayout android:id="@+id/page" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + style="@style/PageContainer" > + + <FrameLayout + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/Header"/> + + <FrameLayout + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + + </LinearLayout> + + <include layout="@layout/button_bar" /> + +</LinearLayout> diff --git a/res/layout/setup_welcome_page.xml b/res/layout/setup_welcome_page.xml new file mode 100644 index 0000000..608a66c --- /dev/null +++ b/res/layout/setup_welcome_page.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/header" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="@style/PageContent"> + + <com.cyanogenmod.account.ui.LocalePicker + android:id="@+id/locale_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginRight="12dp" + android:gravity="left" + android:focusable="true" + android:focusableInTouchMode="true"/> + +</FrameLayout> diff --git a/res/layout/sim_missing_page.xml b/res/layout/sim_missing_page.xml new file mode 100644 index 0000000..789de85 --- /dev/null +++ b/res/layout/sim_missing_page.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/sim_missing" + style="@style/PageSummaryText" + android:textSize="15sp" + android:layout_marginBottom="@dimen/summary_margin_bottom" + android:paddingTop="@dimen/content_margin_top" + android:paddingLeft="@dimen/content_margin_left" + android:paddingRight="@dimen/content_margin_right" + android:text="@string/sim_missing_summary" /> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:src="@drawable/sim_back"/> + + </LinearLayout> +</ScrollView> + + + diff --git a/res/transition/explode.xml b/res/transition/explode.xml new file mode 100644 index 0000000..9f7c66e --- /dev/null +++ b/res/transition/explode.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<explode/> diff --git a/res/transition/slide_left.xml b/res/transition/slide_left.xml new file mode 100644 index 0000000..501d191 --- /dev/null +++ b/res/transition/slide_left.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:transitionOrdering="sequential"> +<changeBounds/> +<slide android:slideEdge="left" > + <targets> + <target android:targetId="@id/page" /> + </targets> +</slide> +</transitionSet>
\ No newline at end of file diff --git a/res/transition/slide_right.xml b/res/transition/slide_right.xml new file mode 100644 index 0000000..4f129c7 --- /dev/null +++ b/res/transition/slide_right.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" + android:transitionOrdering="sequential"> + <changeBounds/> + <slide android:slideEdge="right" > + <targets> + <target android:targetId="@id/page" /> + </targets> + </slide> +</transitionSet>
\ No newline at end of file diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml new file mode 100644 index 0000000..5d88714 --- /dev/null +++ b/res/values-af/strings.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<!-- + Copyright (C) 2013-2014 The Cyanogen 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. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="next">Volgende</string> + <string name="continue_label">Gaan voort</string> + <string name="prev">Vorige</string> + <string name="skip">Slaan oor</string> + <string name="cancel">Kanselleer</string> + <string name="ok">OK</string> + + <string name="setup_welcome">Welkom by Cyanogen</string> +</resources> diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml new file mode 100644 index 0000000..d1bd628 --- /dev/null +++ b/res/values-ar/strings.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<!-- + Copyright (C) 2013-2014 The Cyanogen 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. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="next">القادم</string> + <string name="continue_label">متابعة</string> + <string name="prev">سابق</string> + <string name="skip">تخطي</string> + <string name="cancel">إلغاء</string> + <string name="ok">موافق</string> + <string name="setup_complete">اكتمال الإعداد</string> + <string name="setup_welcome">مرحبا بك في Cyanogen</string> +</resources> diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml new file mode 100644 index 0000000..8e8aa91 --- /dev/null +++ b/res/values-ca/strings.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<!-- + Copyright (C) 2013-2014 The Cyanogen 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. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="next">Següent</string> + <string name="continue_label">Continua</string> + <string name="prev">Anterior</string> + <string name="skip">Omet</string> + <string name="cancel">Cancel·la</string> + <string name="ok">D\'acord</string> + <string name="setup_complete">Configuració completa</string> + <string name="setup_welcome">Benvingut a Cyanogen</string> +</resources> diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml new file mode 100644 index 0000000..ffb4896 --- /dev/null +++ b/res/values-cs/strings.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<!-- + Copyright (C) 2013-2014 The Cyanogen 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. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="next">Další</string> + <string name="continue_label">Pokračovat</string> + <string name="prev">Předchozí</string> + <string name="skip">Přeskočit</string> + <string name="cancel">Zrušit</string> + <string name="ok">OK</string> + <string name="setup_complete">Nastavení dokončeno</string> + <string name="setup_welcome">Vítá Vás Cyanogen</string> +</resources> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml new file mode 100644 index 0000000..df0dbbd --- /dev/null +++ b/res/values-es/strings.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Generated by crowdin.net--> +<!-- + Copyright (C) 2013-2014 The Cyanogen 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. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="next">Siguiente</string> + <string name="continue_label">Continuar</string> + <string name="prev">Anterior</string> + <string name="skip">Omitir</string> + <string name="cancel">Cancelar</string> + <string name="ok">Aceptar</string> + <string name="setup_complete">Configuración completada</string> + <string name="setup_welcome">Bienvenido a Cyanogen</string> +</resources> diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml new file mode 100644 index 0000000..deac1a4 --- /dev/null +++ b/res/values-land/dimens.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + +</resources> diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 0000000..951d28e --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <color name="black">#000000</color> + <color name="white">#ffffff</color> + <color name="window_background">#e4e7e8</color> + <color name="page_background">#efefef</color> + + <color name="primary">#2196f3</color> + <color name="primary_dark">#1481d8</color> + <color name="accent">#009789</color> + <color name="primary_text">#8a000000</color> + <color name="secondary_text">#727272</color> + <color name="divider">#40000000</color> +</resources> diff --git a/res/values/config.xml b/res/values/config.xml new file mode 100644 index 0000000..6cb797c --- /dev/null +++ b/res/values/config.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <integer name="local_picker_items">3</integer> +</resources> diff --git a/res/values/dimens.xml b/res/values/dimens.xml new file mode 100644 index 0000000..c0c91e8 --- /dev/null +++ b/res/values/dimens.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <dimen name="logo_header_height">290dp</dimen> + <dimen name="header_height">220dp</dimen> + <dimen name="page_title_height">56dp</dimen> + <dimen name="header_title_margin_left">26dp</dimen> + <dimen name="header_logo_margin_left">25dp</dimen> + <dimen name="header_logo_margin_bottom">11dp</dimen> + <dimen name="content_margin_top">26dp</dimen> + <dimen name="content_margin_left">26dp</dimen> + <dimen name="content_margin_right">14dp</dimen> + <dimen name="content_margin_bottom">20dp</dimen> + <dimen name="summary_margin_bottom">20dp</dimen> + <dimen name="location_margin_left">20dp</dimen> + <dimen name="location_text_margin_left">10dp</dimen> + <dimen name="location_text_margin_right">30dp</dimen> + <dimen name="data_switch_margin_right">30dp</dimen> + <dimen name="carrier_text_margin_left">15dp</dimen> +</resources> diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..6c3edcb --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013-2014 The Cyanogen 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. +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name">Setup Wizard</string> + <string name="product_name" translatable="false">cyanogen</string> + <string name="account_name" translatable="false">Cyanogen</string> + + <string name="next">Next</string> + <string name="continue_label">Continue</string> + <string name="skip">Skip</string> + <string name="finish">Finish</string> + <string name="start">Start</string> + <string name="cancel">Cancel</string> + <string name="create">Create</string> + <string name="login">Login</string> + <string name="ok">OK</string> + <string name="existing">Existing</string> + <string name="new_account">New</string> + <string name="skip_anyway">Skip anyway</string> + <string name="dont_skip">Don\'t skip</string> + + <string name="setup_complete">Setup is complete</string> + <string name="setup_welcome">Welcome</string> + <string name="setup_wifi">Connect to Wi-Fi</string> + <string name="setup_sim_missing">SIM Card Missing</string> + <string name="setup_gms_account">GMS account</string> + <string name="setup_choose_data_sim">Choose a SIM for Data</string> + <string name="setup_location">Location Services</string> + <string name="setup_datetime">Date & time</string> + <string name="setup_current_date">Current date</string> + <string name="setup_current_time">Current time</string> + <string name="setup_msg_no_network">WARNING: You won\'t be able to set up your accounts</string> + <string name="setup_warning_skip_wifi" product="tablet">WARNING: You may incur extra carrier data charges.\n\nTablet setup can require significant network activity.</string> + <string name="setup_warning_skip_wifi" product="default">WARNING: You may incur extra carrier data charges.\n\nPhone setup can require significant network activity.</string> + <string name="setup_privacy">Privacy Settings</string> + <string name="setup_finalizing">Finalizing setup\u2026</string> + + <string name="sim_missing_summary">A SIM card has not been detected in your device. Please insert a valid SIM card.</string> + <string name="choose_data_sim_summary">Which SIM do you want to use for data? The selected SIM may incur network charges as it will be used to set up your device.</string> + + <string name="date_time_summary">Set your time zone and adjust current date and time if needed</string> + + <string name="location_services_summary">Location services allows Maps and other apps to gather and use data such as your approximate location. For example, Maps may use your approximate location to locate nearby coffee shops.</string> + <string name="location_access_summary">Allow apps that have asked your permission to use your location information. This may include your current location and past locations.</string> + <string name="location_gps">Improve location accuracy by allowing apps to use the GPS on your phone.</string> + <string name="location_network">Use Google’s location service to help apps determine your location. This means sending annonymous location data to Google, even when no apps are running.</string> + + <string name="setup_mobile_data">Turn On Mobile Data</string> + <string name="setup_mobile_data_no_service">No service</string> + <string name="setup_mobile_data_emergency_only">Emergency calls only</string> + <string name="enable_mobile_data_summary">Do you want to use mobile data during setup? Turning on mobile data may be subject to data charges.</string> + <string name="enable_mobile_off">No</string> + <string name="enable_mobile_on">Yes</string> + + <string name="data_sim_name">SIM <xliff:g id="sub">%d</xliff:g> - <xliff:g id="name">%s</xliff:g></string> + + <string name="emergency_call">Emergency Call</string> +</resources> diff --git a/res/values/styles.xml b/res/values/styles.xml new file mode 100644 index 0000000..b42d118 --- /dev/null +++ b/res/values/styles.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <style name="Theme.Setup" parent="@android:style/Theme.Material.Light"> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowActivityTransitions">true</item> + <item name="android:windowContentTransitions">true</item> + <item name="android:colorPrimary">@color/primary</item> + <item name="android:colorPrimaryDark">@color/primary</item> + <item name="android:colorAccent">@color/accent</item> + </style> + + <style name="RootView"> + <item name="android:background">@color/window_background</item> + </style> + + <style name="Header"> + <item name="android:elevation">4dp</item> + <item name="android:background">@color/primary</item> + </style> + + <style name="PageContainer"> + <item name="android:textColor">@color/primary_text</item> + <item name="android:background">@color/page_background</item> + </style> + + <style name="PageContent"> + <item name="android:paddingTop">@dimen/content_margin_top</item> + <item name="android:paddingLeft">@dimen/content_margin_left</item> + <item name="android:paddingRight">@dimen/content_margin_right</item> + </style> + + <style name="PageTitle"> + <item name="android:id">@android:id/title</item> + <item name="android:textSize">20sp</item> + <item name="android:textColor">@color/white</item> + <item name="android:background">@color/primary_dark</item> + <item name="android:layout_alignParentBottom">true</item> + <item name="android:gravity">left|center_vertical</item> + <item name="android:paddingLeft">@dimen/header_title_margin_left</item> + </style> + + <style name="PageSummaryText"> + <item name="android:id">@android:id/summary</item> + </style> + + <style name="SpinnerItem"> + <item name="android:textSize">15sp</item> + </style> + + <style name="ButtonBar" parent="@android:style/Widget.Material.ActionButton"> + <item name="android:layout_height">48dp</item> + <item name="android:textSize">15sp</item> + <item name="android:textAllCaps">true</item> + <item name="android:maxLines">1</item> + <item name="android:minWidth">15dp</item> + <item name="android:minHeight">15dp</item> + <item name="android:textColor">@color/primary_text</item> + <item name="android:fontFamily">sans-serif-medium</item> + <item name="android:drawablePadding">5dp</item> + </style> + + <style name="ButtonBar.Left"> + <item name="android:paddingStart">15dp</item> + <item name="android:gravity">center_vertical|start</item> + <item name="android:drawableLeft">@drawable/ic_chevron_left_dark</item> + </style> + + <style name="ButtonBar.Right"> + <item name="android:paddingEnd">15dp</item> + <item name="android:gravity">center_vertical|end</item> + <item name="android:drawableRight">@drawable/ic_chevron_right_dark</item> + </style> +</resources> diff --git a/res/values/version.xml b/res/values/version.xml new file mode 100644 index 0000000..1e7fd4e --- /dev/null +++ b/res/values/version.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <integer name="google_play_services_version">4323000</integer> +</resources> diff --git a/res/xml/timezones.xml b/res/xml/timezones.xml new file mode 100644 index 0000000..26382bc --- /dev/null +++ b/res/xml/timezones.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<timezones> + <timezone id="Pacific/Majuro">Marshall Islands</timezone> + <timezone id="Pacific/Midway">Midway Island</timezone> + <timezone id="Pacific/Honolulu">Hawaii</timezone> + <timezone id="America/Anchorage">Alaska</timezone> + <timezone id="America/Los_Angeles">Pacific Time</timezone> + <timezone id="America/Tijuana">Tijuana</timezone> + <timezone id="America/Phoenix">Arizona</timezone> + <timezone id="America/Chihuahua">Chihuahua</timezone> + <timezone id="America/Denver">Mountain Time</timezone> + <timezone id="America/Costa_Rica">Central America</timezone> + <timezone id="America/Chicago">Central Time</timezone> + <timezone id="America/Mexico_City">Mexico City</timezone> + <timezone id="America/Regina">Saskatchewan</timezone> + <timezone id="America/Bogota">Bogota</timezone> + <timezone id="America/New_York">Eastern Time</timezone> + <timezone id="America/Caracas">Venezuela</timezone> + <timezone id="America/Barbados">Atlantic Time (Barbados)</timezone> + <timezone id="America/Halifax">Atlantic Time (Canada)</timezone> + <timezone id="America/Manaus">Manaus</timezone> + <timezone id="America/Santiago">Santiago</timezone> + <timezone id="America/St_Johns">Newfoundland</timezone> + <timezone id="America/Sao_Paulo">Brasilia</timezone> + <timezone id="America/Argentina/Buenos_Aires">Buenos Aires</timezone> + <timezone id="America/Godthab">Greenland</timezone> + <timezone id="America/Montevideo">Montevideo</timezone> + <timezone id="Atlantic/South_Georgia">Mid-Atlantic</timezone> + <timezone id="Atlantic/Azores">Azores</timezone> + <timezone id="Atlantic/Cape_Verde">Cape Verde Islands</timezone> + <timezone id="Africa/Casablanca">Casablanca</timezone> + <timezone id="Europe/London">London, Dublin</timezone> + <timezone id="Europe/Amsterdam">Amsterdam, Berlin</timezone> + <timezone id="Europe/Belgrade">Belgrade</timezone> + <timezone id="Europe/Brussels">Brussels</timezone> + <timezone id="Europe/Sarajevo">Sarajevo</timezone> + <timezone id="Africa/Windhoek">Windhoek</timezone> + <timezone id="Africa/Brazzaville">W. Africa Time</timezone> + <timezone id="Asia/Amman">Amman, Jordan</timezone> + <timezone id="Europe/Athens">Athens, Istanbul</timezone> + <timezone id="Asia/Beirut">Beirut, Lebanon</timezone> + <timezone id="Africa/Cairo">Cairo</timezone> + <timezone id="Europe/Helsinki">Helsinki</timezone> + <timezone id="Asia/Jerusalem">Jerusalem</timezone> + <timezone id="Europe/Minsk">Minsk</timezone> + <timezone id="Africa/Harare">Harare</timezone> + <timezone id="Asia/Baghdad">Baghdad</timezone> + <timezone id="Europe/Moscow">Moscow</timezone> + <timezone id="Asia/Kuwait">Kuwait</timezone> + <timezone id="Africa/Nairobi">Nairobi</timezone> + <timezone id="Asia/Tehran">Tehran</timezone> + <timezone id="Asia/Baku">Baku</timezone> + <timezone id="Asia/Tbilisi">Tbilisi</timezone> + <timezone id="Asia/Yerevan">Yerevan</timezone> + <timezone id="Asia/Dubai">Dubai</timezone> + <timezone id="Asia/Kabul">Kabul</timezone> + <timezone id="Asia/Karachi">Islamabad, Karachi</timezone> + <timezone id="Asia/Oral">Ural'sk</timezone> + <timezone id="Asia/Yekaterinburg">Yekaterinburg</timezone> + <timezone id="Asia/Calcutta">Kolkata</timezone> + <timezone id="Asia/Colombo">Sri Lanka</timezone> + <timezone id="Asia/Katmandu">Kathmandu</timezone> + <timezone id="Asia/Almaty">Astana</timezone> + <timezone id="Asia/Rangoon">Yangon</timezone> + <timezone id="Asia/Krasnoyarsk">Krasnoyarsk</timezone> + <timezone id="Asia/Bangkok">Bangkok</timezone> + <timezone id="Asia/Jakarta">Jakarta</timezone> + <timezone id="Asia/Shanghai">Beijing</timezone> + <timezone id="Asia/Hong_Kong">Hong Kong</timezone> + <timezone id="Asia/Irkutsk">Irkutsk</timezone> + <timezone id="Asia/Kuala_Lumpur">Kuala Lumpur</timezone> + <timezone id="Australia/Perth">Perth</timezone> + <timezone id="Asia/Taipei">Taipei</timezone> + <timezone id="Asia/Seoul">Seoul</timezone> + <timezone id="Asia/Tokyo">Tokyo, Osaka</timezone> + <timezone id="Asia/Yakutsk">Yakutsk</timezone> + <timezone id="Australia/Adelaide">Adelaide</timezone> + <timezone id="Australia/Darwin">Darwin</timezone> + <timezone id="Australia/Brisbane">Brisbane</timezone> + <timezone id="Australia/Hobart">Hobart</timezone> + <timezone id="Australia/Sydney">Sydney, Canberra</timezone> + <timezone id="Asia/Vladivostok">Vladivostok</timezone> + <timezone id="Pacific/Guam">Guam</timezone> + <timezone id="Asia/Magadan">Magadan</timezone> + <timezone id="Pacific/Auckland">Auckland</timezone> + <timezone id="Pacific/Fiji">Fiji</timezone> + <timezone id="Pacific/Tongatapu">Tonga</timezone> +</timezones> diff --git a/src/com/cyanogenmod/setupwizard/SetupWizardApp.java b/src/com/cyanogenmod/setupwizard/SetupWizardApp.java new file mode 100644 index 0000000..4df1194 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/SetupWizardApp.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard; + + +import android.app.Application; +import android.app.StatusBarManager; +import android.content.Context; + +public class SetupWizardApp extends Application { + + public static final String TAG = SetupWizardApp.class.getSimpleName(); + // Leave this off for release + public static final boolean DEBUG = false; + + public static final String ACCOUNT_TYPE_CYANOGEN = "com.cyanogen"; + public static final String ACCOUNT_TYPE_GMS = "com.google"; + + public static final String ACTION_SETUP_WIFI = "com.android.net.wifi.SETUP_WIFI_NETWORK"; + + public static final String EXTRA_FIRST_RUN = "firstRun"; + public static final String EXTRA_ALLOW_SKIP = "allowSkip"; + public static final String EXTRA_AUTO_FINISH = "wifi_auto_finish_on_connect"; + + public static final int REQUEST_CODE_SETUP_WIFI = 0; + + private StatusBarManager mStatusBarManager; + + @Override + public void onCreate() { + super.onCreate(); + mStatusBarManager = (StatusBarManager)getSystemService(Context.STATUS_BAR_SERVICE); + } + + public void disableStatusBar() { + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND | StatusBarManager.DISABLE_NOTIFICATION_ALERTS + | StatusBarManager.DISABLE_NOTIFICATION_TICKER | StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_HOME + | StatusBarManager.DISABLE_SEARCH); + } + + public void enableStatusBar() { + mStatusBarManager.disable(StatusBarManager.DISABLE_NONE); + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/AbstractSetupData.java b/src/com/cyanogenmod/setupwizard/setup/AbstractSetupData.java new file mode 100644 index 0000000..fed8732 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/AbstractSetupData.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; + +import java.util.ArrayList; + +public abstract class AbstractSetupData implements SetupDataCallbacks { + + private static final String TAG = AbstractSetupData.class.getSimpleName(); + + protected Context mContext; + private ArrayList<SetupDataCallbacks> mListeners = new ArrayList<SetupDataCallbacks>(); + private PageList mPageList; + + private int mCurrentPageIndex = 0; + + public AbstractSetupData(Context context) { + mContext = context; + mPageList = onNewPageList(); + } + + protected abstract PageList onNewPageList(); + + @Override + public void onPageLoaded(Page page) { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onPageLoaded(page); + } + } + + @Override + public void onPageTreeChanged() { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onPageTreeChanged(); + } + } + + @Override + public void onFinish() { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onFinish(); + } + } + + @Override + public Page getPage(String key) { + return mPageList.getPage(key); + } + + @Override + public Page getPage(int index) { + return mPageList.getPage(index); + } + + public Page getCurrentPage() { + return mPageList.getPage(mCurrentPageIndex); + } + + public boolean isFirstPage() { + return mCurrentPageIndex == 0; + } + + public boolean isLastPage() { + return mCurrentPageIndex == mPageList.size() - 1; + } + + @Override + public void onNextPage() { + if (getCurrentPage().doNextAction() == false) { + if (advanceToNextUncompleted()) { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onNextPage(); + } + } + } + } + + @Override + public void onPreviousPage() { + if (getCurrentPage().doPreviousAction() == false) { + if (advanceToPreviousUncompleted()) { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onPreviousPage(); + } + } + } + } + + @Override + public void onPageViewCreated(LayoutInflater inflater, Bundle savedInstanceState, + int layoutResource) {} + + private boolean advanceToNextUncompleted() { + while (mCurrentPageIndex < mPageList.size()) { + mCurrentPageIndex++; + if (!getCurrentPage().isCompleted()) { + return true; + } + } + return false; + } + + private boolean advanceToPreviousUncompleted() { + while (mCurrentPageIndex > 0) { + mCurrentPageIndex--; + if (!getCurrentPage().isCompleted()) { + return true; + } + } + return false; + } + + public void load(Bundle savedValues) { + for (String key : savedValues.keySet()) { + Page page = mPageList.getPage(key); + if (page != null) { + page.resetData(savedValues.getBundle(key)); + } + } + } + + public Bundle save() { + Bundle bundle = new Bundle(); + for (Page page : mPageList.values()) { + bundle.putBundle(page.getKey(), page.getData()); + } + return bundle; + } + + public void registerListener(SetupDataCallbacks listener) { + mListeners.add(listener); + } + + public void unregisterListener(SetupDataCallbacks listener) { + mListeners.remove(listener); + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/CMSetupWizardData.java b/src/com/cyanogenmod/setupwizard/setup/CMSetupWizardData.java new file mode 100644 index 0000000..e50f1ac --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/CMSetupWizardData.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import com.cyanogenmod.setupwizard.util.SetupWizardUtils; + +import android.content.Context; +import android.telephony.SubscriptionManager; + +import java.util.ArrayList; + +public class CMSetupWizardData extends AbstractSetupData { + + public CMSetupWizardData(Context context) { + super(context); + } + + @Override + protected PageList onNewPageList() { + ArrayList<SetupPage> pages = new ArrayList<SetupPage>(); + pages.add(new WelcomePage(mContext, this)); + pages.add(new WifiSetupPage(mContext, this)); + if (SetupWizardUtils.isGSMPhone(mContext) && SetupWizardUtils.isSimMissing(mContext)) { + pages.add(new SimCardMissingPage(mContext, this)); + } + if (SetupWizardUtils.isMultiSimDevice(mContext) + && SubscriptionManager.getActiveSubInfoCount() > 1) { + pages.add(new ChooseDataSimPage(mContext, this)); + } + if (SetupWizardUtils.hasTelephony(mContext) && + !SetupWizardUtils.isMobileDataEnabled(mContext)) { + pages.add(new MobileDataPage(mContext, this)); + } + if (SetupWizardUtils.hasGMS(mContext)) { + pages.add(new GmsAccountPage(mContext, this)); + } + pages.add(new CyanogenAccountPage(mContext, this)); + pages.add(new LocationSettingsPage(mContext, this)); + pages.add(new DateTimePage(mContext, this)); + pages.add(new FinishPage(mContext, this)); + return new PageList(pages.toArray(new SetupPage[pages.size()])); + } + + +}
\ No newline at end of file diff --git a/src/com/cyanogenmod/setupwizard/setup/ChooseDataSimPage.java b/src/com/cyanogenmod/setupwizard/setup/ChooseDataSimPage.java new file mode 100644 index 0000000..820edc6 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/ChooseDataSimPage.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubInfoRecord; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; + +import java.util.List; + +public class ChooseDataSimPage extends SetupPage { + + public static final String TAG = "ChooseDataSimPage"; + + public ChooseDataSimPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(SetupPage.KEY_PAGE_ARGUMENT, getKey()); + + ChooseDataSimFragment fragment = new ChooseDataSimFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_choose_data_sim; + } + + @Override + public int getNextButtonTitleResId() { + return R.string.skip; + } + + + public static class ChooseDataSimFragment extends SetupPageFragment { + + private ViewGroup mPageView; + private SparseArray<TextView> mNameViews; + private SparseArray<ImageView> mSignalViews; + private SparseArray<CheckBox> mCheckBoxes; + + private TelephonyManager mPhone; + private List<SubInfoRecord> mSubInfoRecords; + private SparseArray<SignalStrength> mSignalStrengths; + private SparseArray<ServiceState> mServiceStates; + private SparseArray<PhoneStateListener> mPhoneStateListeners; + + private View.OnClickListener mSetDataSimClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + SubInfoRecord subInfoRecord = (SubInfoRecord)view.getTag(); + if (subInfoRecord != null) { + SubscriptionManager.setDefaultDataSubId(subInfoRecord.subId); + setDataSubChecked(subInfoRecord); + } + } + }; + + @Override + protected void initializePage() { + mPageView = (ViewGroup)mRootView.findViewById(R.id.page_view); + mSubInfoRecords = SubscriptionManager.getActiveSubInfoList(); + int simCount = mSubInfoRecords.size(); + mNameViews = new SparseArray<TextView>(simCount); + mSignalViews = new SparseArray<ImageView>(simCount); + mCheckBoxes = new SparseArray<CheckBox>(simCount); + mServiceStates = new SparseArray<ServiceState>(simCount); + mSignalStrengths = new SparseArray<SignalStrength>(simCount); + mPhoneStateListeners = new SparseArray<PhoneStateListener>(simCount); + LayoutInflater inflater = LayoutInflater.from(getActivity()); + for (int i = 0; i < simCount; i++) { + View simRow = inflater.inflate(R.layout.data_sim_row, null); + mPageView.addView(simRow); + SubInfoRecord subInfoRecord = mSubInfoRecords.get(i); + simRow.setTag(subInfoRecord); + simRow.setOnClickListener(mSetDataSimClickListener); + mNameViews.put(i, (TextView) simRow.findViewById(R.id.sim_title)); + mSignalViews.put(i, (ImageView) simRow.findViewById(R.id.signal)); + mCheckBoxes.put(i, (CheckBox) simRow.findViewById(R.id.enable_check)); + mPhoneStateListeners.put(i, createPhoneStateListener(subInfoRecord)); + mPageView.addView(inflater.inflate(R.layout.divider, null)); + } + mPhone = (TelephonyManager)getActivity().getSystemService(Context.TELEPHONY_SERVICE); + for (int i = 0; i < mPhoneStateListeners.size(); i++) { + mPhone.listen(mPhoneStateListeners.get(i), + PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); + } + updateSignalStrengths(); + updateCurrentDataSub(); + } + + @Override + protected int getLayoutResource() { + return R.layout.choose_data_sim_page; + } + + @Override + public void onResume() { + super.onResume(); + updateSignalStrengths(); + updateCurrentDataSub(); + } + + @Override + public void onDetach() { + super.onDetach(); + for (int i = 0; i < mPhoneStateListeners.size(); i++) { + mPhone.listen(mPhoneStateListeners.get(i), PhoneStateListener.LISTEN_NONE); + } + } + + private PhoneStateListener createPhoneStateListener(final SubInfoRecord subInfoRecord) { + return new PhoneStateListener(subInfoRecord.subId) { + + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mSignalStrengths.put(subInfoRecord.slotId, signalStrength); + updateSignalStrength(subInfoRecord); + } + + @Override + public void onServiceStateChanged(ServiceState state) { + mServiceStates.put(subInfoRecord.slotId, state); + updateSignalStrength(subInfoRecord); + } + }; + } + + private void updateSignalStrengths() { + for (int i = 0; i < mSubInfoRecords.size(); i++) { + updateSignalStrength(mSubInfoRecords.get(i)); + } + } + + private void setDataSubChecked(SubInfoRecord subInfoRecord) { + for (int i = 0; i < mCheckBoxes.size(); i++) { + mCheckBoxes.get(i).setChecked(subInfoRecord.slotId == i); + + } + } + + private void updateCurrentDataSub() { + for (int i = 0; i < mSubInfoRecords.size(); i++) { + SubInfoRecord subInfoRecord = mSubInfoRecords.get(i); + mCheckBoxes.get(i).setChecked(SubscriptionManager.getDefaultDataSubId() + == subInfoRecord.subId); + + } + } + + private void updateCarrierText(SubInfoRecord subInfoRecord) { + String name = mPhone.getNetworkOperatorName(subInfoRecord.subId); + ServiceState serviceState = mServiceStates.get(subInfoRecord.slotId); + if (TextUtils.isEmpty(name)) { + if (serviceState != null && serviceState.isEmergencyOnly()) { + name = getString(R.string.setup_mobile_data_emergency_only); + } else { + name = getString(R.string.setup_mobile_data_no_service); + } + } + String formattedName = + getString(R.string.data_sim_name, subInfoRecord.slotId + 1, name); + mNameViews.get(subInfoRecord.slotId).setText(formattedName); + } + + private void updateSignalStrength(SubInfoRecord subInfoRecord) { + ImageView signalView = mSignalViews.get(subInfoRecord.slotId); + SignalStrength signalStrength = mSignalStrengths.get(subInfoRecord.slotId); + if (!hasService(subInfoRecord)) { + signalView.setImageResource(R.drawable.ic_signal_no_signal); + } else { + if (signalStrength != null) { + int resId; + switch (signalStrength.getLevel()) { + case 4: + resId = R.drawable.ic_signal_4; + break; + case 3: + resId = R.drawable.ic_signal_3; + break; + case 2: + resId = R.drawable.ic_signal_2; + break; + case 1: + resId = R.drawable.ic_signal_1; + break; + default: + resId = R.drawable.ic_signal_0; + break; + } + signalView.setImageResource(resId); + } + } + updateCarrierText(subInfoRecord); + } + + private boolean hasService(SubInfoRecord subInfoRecord) { + boolean retVal; + ServiceState serviceState = mServiceStates.get(subInfoRecord.slotId); + if (serviceState != null) { + // Consider the device to be in service if either voice or data service is available. + // Some SIM cards are marketed as data-only and do not support voice service, and on + // these SIM cards, we want to show signal bars for data service as well as the "no + // service" or "emergency calls only" text that indicates that voice is not available. + switch(serviceState.getVoiceRegState()) { + case ServiceState.STATE_POWER_OFF: + retVal = false; + break; + case ServiceState.STATE_OUT_OF_SERVICE: + case ServiceState.STATE_EMERGENCY_ONLY: + retVal = serviceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; + break; + default: + retVal = true; + } + } else { + retVal = false; + } + Log.d(TAG, "hasService: mServiceState=" + serviceState + " retVal=" + retVal); + return retVal; + } + } + +} diff --git a/src/com/cyanogenmod/setupwizard/setup/CyanogenAccountPage.java b/src/com/cyanogenmod/setupwizard/setup/CyanogenAccountPage.java new file mode 100644 index 0000000..dc48faa --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/CyanogenAccountPage.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import com.cyanogenmod.setupwizard.SetupWizardApp; +import com.cyanogenmod.setupwizard.R; + +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; + +public class CyanogenAccountPage extends SetupPage { + + public static final String TAG = "CyanogenAccountPage"; + + public CyanogenAccountPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public int getNextButtonTitleResId() { + return R.string.skip; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return -1; + } + + @Override + public void doLoadAction(Activity context, int action) { + launchCyanogenAccountSetup(context, action); + } + + public void launchCyanogenAccountSetup(final Activity activity, final int action) { + Bundle bundle = new Bundle(); + bundle.putBoolean(SetupWizardApp.EXTRA_FIRST_RUN, true); + AccountManager + .get(activity).addAccount(SetupWizardApp.ACCOUNT_TYPE_CYANOGEN, null, null, bundle, + activity, new AccountManagerCallback<Bundle>() { + @Override + public void run(AccountManagerFuture<Bundle> bundleAccountManagerFuture) { + if (activity == null) return; //There is a chance this activity has been torn down. + if (accountExists(activity, SetupWizardApp.ACCOUNT_TYPE_CYANOGEN)) { + setCompleted(true); + getCallbacks().onNextPage(); + } else { + if (action == Page.ACTION_NEXT) { + getCallbacks().onNextPage(); + } else { + getCallbacks().onPreviousPage(); + } + } + } + }, null); + } + + private boolean accountExists(Activity activity, String accountType) { + return AccountManager.get(activity).getAccountsByType(accountType).length > 0; + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/DateTimePage.java b/src/com/cyanogenmod/setupwizard/setup/DateTimePage.java new file mode 100644 index 0000000..40eda95 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/DateTimePage.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; + +import org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.DatePickerDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.TimePickerDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.os.Handler; +import android.text.format.DateFormat; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.DatePicker; +import android.widget.SimpleAdapter; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.TimePicker; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +public class DateTimePage extends SetupPage { + + public static final String TAG = "DateTimePage"; + + private static final String KEY_ID = "id"; // value: String + private static final String KEY_DISPLAYNAME = "name"; // value: String + private static final String KEY_GMT = "gmt"; // value: String + private static final String KEY_OFFSET = "offset"; // value: int (Integer) + private static final String XMLTAG_TIMEZONE = "timezone"; + + private static final int HOURS_1 = 60 * 60000; + + + public DateTimePage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(SetupPage.KEY_PAGE_ARGUMENT, getKey()); + + DateTimeFragment fragment = new DateTimeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_datetime; + } + + public static class DateTimeFragment extends SetupPageFragment + implements TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener { + + private TimeZone mCurrentTimeZone; + private View mDateView; + private View mTimeView; + private TextView mDateTextView; + private TextView mTimeTextView; + + + private final Handler mHandler = new Handler(); + + @Override + public void onResume() { + super.onResume(); + // Register for time ticks and other reasons for time change + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + getActivity().registerReceiver(mIntentReceiver, filter, null, null); + + updateTimeAndDateDisplay(getActivity()); + } + + @Override + public void onPause() { + super.onPause(); + getActivity().unregisterReceiver(mIntentReceiver); + } + + @Override + protected void initializePage() { + final Spinner spinner = (Spinner) mRootView.findViewById(R.id.timezone_list); + final SimpleAdapter adapter = constructTimezoneAdapter(getActivity(), false); + mCurrentTimeZone = TimeZone.getDefault(); + mDateView = mRootView.findViewById(R.id.date_item); + mDateView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showDatePicker(); + } + }); + mTimeView = mRootView.findViewById(R.id.time_item); + mTimeView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showTimePicker(); + } + }); + mDateTextView = (TextView)mRootView.findViewById(R.id.date_text); + mTimeTextView = (TextView)mRootView.findViewById(R.id.time_text); + // Pre-select current/default timezone + mHandler.post(new Runnable() { + @Override + public void run() { + int tzIndex = getTimeZoneIndex(adapter, mCurrentTimeZone); + spinner.setAdapter(adapter); + if (tzIndex != -1) { + spinner.setSelection(tzIndex); + } + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) { + final Map<?, ?> map = (Map<?, ?>) adapterView.getItemAtPosition(position); + final String tzId = (String) map.get(KEY_ID); + if (mCurrentTimeZone != null && !mCurrentTimeZone.getID().equals(tzId)) { + // Update the system timezone value + final Activity activity = getActivity(); + final AlarmManager alarm = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE); + alarm.setTimeZone(tzId); + mCurrentTimeZone = TimeZone.getTimeZone(tzId); + } + + } + + @Override + public void onNothingSelected(AdapterView<?> adapterView) { + } + }); + } + }); + } + + private void showDatePicker() { + DatePickerFragment datePickerFragment = DatePickerFragment.newInstance(); + datePickerFragment.setOnDateSetListener(this); + datePickerFragment.show(getFragmentManager(), DatePickerFragment.TAG); + } + + private void showTimePicker() { + TimePickerFragment timePickerFragment = TimePickerFragment.newInstance(); + timePickerFragment.setOnTimeSetListener(this); + timePickerFragment.show(getFragmentManager(), TimePickerFragment.TAG); + } + + public void updateTimeAndDateDisplay(Context context) { + java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context); + final Calendar now = Calendar.getInstance(); + mTimeTextView.setText(DateFormat.getTimeFormat(getActivity()).format(now.getTime())); + mDateTextView.setText(shortDateFormat.format(now.getTime())); + } + + @Override + protected int getLayoutResource() { + return R.layout.setup_datetime_page; + } + + @Override + public void onDateSet(DatePicker view, int year, int month, int day) { + final Activity activity = getActivity(); + if (activity != null) { + setDate(activity, year, month, day); + updateTimeAndDateDisplay(activity); + } + } + + @Override + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + final Activity activity = getActivity(); + if (activity != null) { + setTime(activity, hourOfDay, minute); + updateTimeAndDateDisplay(activity); + } + } + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final Activity activity = getActivity(); + if (activity != null) { + updateTimeAndDateDisplay(activity); + } + } + }; + + } + + private static SimpleAdapter constructTimezoneAdapter(Context context, + boolean sortedByName) { + final String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT}; + final int[] to = new int[] {android.R.id.text1, android.R.id.text2}; + + final String sortKey = (sortedByName ? KEY_DISPLAYNAME : KEY_OFFSET); + final TimeZoneComparator comparator = new TimeZoneComparator(sortKey); + final List<HashMap<String, Object>> sortedList = getZones(context); + Collections.sort(sortedList, comparator); + final SimpleAdapter adapter = new SimpleAdapter(context, + sortedList, + R.layout.date_time_setup_custom_list_item_2, + from, + to); + + return adapter; + } + + private static List<HashMap<String, Object>> getZones(Context context) { + final List<HashMap<String, Object>> myData = new ArrayList<HashMap<String, Object>>(); + final long date = Calendar.getInstance().getTimeInMillis(); + try { + XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones); + while (xrp.next() != XmlResourceParser.START_TAG) + continue; + xrp.next(); + while (xrp.getEventType() != XmlResourceParser.END_TAG) { + while (xrp.getEventType() != XmlResourceParser.START_TAG) { + if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { + return myData; + } + xrp.next(); + } + if (xrp.getName().equals(XMLTAG_TIMEZONE)) { + String id = xrp.getAttributeValue(0); + String displayName = xrp.nextText(); + addItem(myData, id, displayName, date); + } + while (xrp.getEventType() != XmlResourceParser.END_TAG) { + xrp.next(); + } + xrp.next(); + } + xrp.close(); + } catch (XmlPullParserException xppe) { + Log.e(TAG, "Ill-formatted timezones.xml file"); + } catch (java.io.IOException ioe) { + Log.e(TAG, "Unable to read timezones.xml file"); + } + + return myData; + } + + private static void addItem( + List<HashMap<String, Object>> myData, String id, String displayName, long date) { + final HashMap<String, Object> map = new HashMap<String, Object>(); + map.put(KEY_ID, id); + map.put(KEY_DISPLAYNAME, displayName); + final TimeZone tz = TimeZone.getTimeZone(id); + final int offset = tz.getOffset(date); + final int p = Math.abs(offset); + final StringBuilder name = new StringBuilder(); + name.append("GMT"); + + if (offset < 0) { + name.append('-'); + } else { + name.append('+'); + } + + name.append(p / (HOURS_1)); + name.append(':'); + + int min = p / 60000; + min %= 60; + + if (min < 10) { + name.append('0'); + } + name.append(min); + + map.put(KEY_GMT, name.toString()); + map.put(KEY_OFFSET, offset); + + myData.add(map); + } + + private static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) { + final String defaultId = tz.getID(); + final int listSize = adapter.getCount(); + for (int i = 0; i < listSize; i++) { + // Using HashMap<String, Object> induces unnecessary warning. + final HashMap<?,?> map = (HashMap<?,?>)adapter.getItem(i); + final String id = (String)map.get(KEY_ID); + if (defaultId.equals(id)) { + // If current timezone is in this list, move focus to it + return i; + } + } + return -1; + } + + private static void setDate(Context context, int year, int month, int day) { + Calendar c = Calendar.getInstance(); + + c.set(Calendar.YEAR, year); + c.set(Calendar.MONTH, month); + c.set(Calendar.DAY_OF_MONTH, day); + long when = c.getTimeInMillis(); + + if (when / 1000 < Integer.MAX_VALUE) { + ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); + } + } + + private static void setTime(Context context, int hourOfDay, int minute) { + Calendar c = Calendar.getInstance(); + + c.set(Calendar.HOUR_OF_DAY, hourOfDay); + c.set(Calendar.MINUTE, minute); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + long when = c.getTimeInMillis(); + + if (when / 1000 < Integer.MAX_VALUE) { + ((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).setTime(when); + } + } + + private static class TimeZoneComparator implements Comparator<HashMap<?, ?>> { + private String mSortingKey; + + public TimeZoneComparator(String sortingKey) { + mSortingKey = sortingKey; + } + + public void setSortingKey(String sortingKey) { + mSortingKey = sortingKey; + } + + public int compare(HashMap<?, ?> map1, HashMap<?, ?> map2) { + Object value1 = map1.get(mSortingKey); + Object value2 = map2.get(mSortingKey); + + /* + * This should never happen, but just in-case, put non-comparable + * items at the end. + */ + if (!isComparable(value1)) { + return isComparable(value2) ? 1 : 0; + } else if (!isComparable(value2)) { + return -1; + } + + return ((Comparable) value1).compareTo(value2); + } + + private boolean isComparable(Object value) { + return (value != null) && (value instanceof Comparable); + } + } + + private static class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener { + + private static String TAG = TimePickerFragment.class.getSimpleName(); + + private TimePickerDialog.OnTimeSetListener mOnTimeSetListener; + + public static TimePickerFragment newInstance() { + TimePickerFragment frag = new TimePickerFragment(); + return frag; + } + + private void setOnTimeSetListener(TimePickerDialog.OnTimeSetListener onTimeSetListener) { + mOnTimeSetListener = onTimeSetListener; + } + + @Override + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + if (mOnTimeSetListener != null) { + mOnTimeSetListener.onTimeSet(view, hourOfDay, minute); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Calendar calendar = Calendar.getInstance(); + return new TimePickerDialog( + getActivity(), + this, + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + DateFormat.is24HourFormat(getActivity())); + + } + } + + private static class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener { + + private static String TAG = DatePickerFragment.class.getSimpleName(); + + private DatePickerDialog.OnDateSetListener mOnDateSetListener; + + public static DatePickerFragment newInstance() { + DatePickerFragment frag = new DatePickerFragment(); + return frag; + } + + private void setOnDateSetListener(DatePickerDialog.OnDateSetListener onDateSetListener) { + mOnDateSetListener = onDateSetListener; + } + + @Override + public void onDateSet(DatePicker view, int year, int month, int day) { + if (mOnDateSetListener != null) { + mOnDateSetListener.onDateSet(view, year, month, day); + } + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Calendar calendar = Calendar.getInstance(); + return new DatePickerDialog( + getActivity(), + this, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH)); + + } + } + +} diff --git a/src/com/cyanogenmod/setupwizard/setup/FinishPage.java b/src/com/cyanogenmod/setupwizard/setup/FinishPage.java new file mode 100644 index 0000000..1ab0df9 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/FinishPage.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; + +public class FinishPage extends SetupPage { + + public static final String TAG = "FinishPage"; + + public FinishPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(SetupPage.KEY_PAGE_ARGUMENT, getKey()); + + FinishFragment fragment = new FinishFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_complete; + } + + @Override + public boolean doNextAction() { + getCallbacks().onFinish(); + return true; + } + + @Override + public int getNextButtonTitleResId() { + return R.string.start; + } + + @Override + public int getPrevButtonTitleResId() { + return -1; + } + + public static class FinishFragment extends SetupPageFragment { + + @Override + protected void initializePage() {} + + @Override + protected int getLayoutResource() { + return R.layout.setup_finished_page; + } + + @Override + protected int getHeaderLayoutResource() { + return -1; + } + } + +} diff --git a/src/com/cyanogenmod/setupwizard/setup/GmsAccountPage.java b/src/com/cyanogenmod/setupwizard/setup/GmsAccountPage.java new file mode 100644 index 0000000..a7d2aa8 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/GmsAccountPage.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.SetupWizardApp; + +import java.io.IOException; + +public class GmsAccountPage extends SetupPage { + + public static final String TAG = "GmsAccountPage"; + + public GmsAccountPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_gms_account; + } + + @Override + public int getNextButtonTitleResId() { + return R.string.skip; + } + + @Override + public void doLoadAction(Activity context, int action) { + launchGmsAccountSetup(context, action); + } + + public void launchGmsAccountSetup(final Activity activity, final int action) { + Bundle bundle = new Bundle(); + bundle.putBoolean(SetupWizardApp.EXTRA_FIRST_RUN, true); + bundle.putBoolean(SetupWizardApp.EXTRA_ALLOW_SKIP, true); + AccountManager + .get(activity).addAccount(SetupWizardApp.ACCOUNT_TYPE_GMS, null, null, + bundle, activity, new AccountManagerCallback<Bundle>() { + @Override + public void run(AccountManagerFuture<Bundle> bundleAccountManagerFuture) { + //There is a chance this activity has been torn down. + if (activity == null) return; + String token = null; + try { + token = bundleAccountManagerFuture.getResult().getString(AccountManager.KEY_AUTHTOKEN); + } catch (OperationCanceledException e) { + } catch (IOException e) { + } catch (AuthenticatorException e) { + } + if (token != null) { + setCompleted(true); + } + if (action == Page.ACTION_NEXT) { + getCallbacks().onNextPage(); + } else { + getCallbacks().onPreviousPage(); + } + } + }, null); + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/LocationSettingsPage.java b/src/com/cyanogenmod/setupwizard/setup/LocationSettingsPage.java new file mode 100644 index 0000000..00865f4 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/LocationSettingsPage.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Fragment; +import android.content.ContentQueryMap; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.location.LocationManager; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; +import android.widget.CheckBox; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; + +import java.util.Observable; +import java.util.Observer; + +public class LocationSettingsPage extends SetupPage { + + private static final String TAG = "LocationSettingsPage"; + + public LocationSettingsPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(Page.KEY_PAGE_ARGUMENT, getKey()); + + LocationSettingsFragment fragment = new LocationSettingsFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_location; + } + + public static class LocationSettingsFragment extends SetupPageFragment { + + private View mLocationRow; + private View mGpsRow; + private View mNetworkRow; + private CheckBox mNetwork; + private CheckBox mGps; + private CheckBox mLocationAccess; + + private ContentResolver mContentResolver; + + // These provide support for receiving notification when Location Manager settings change. + // This is necessary because the Network Location Provider can change settings + // if the user does not confirm enabling the provider. + private ContentQueryMap mContentQueryMap; + private Observer mSettingsObserver; + + + private View.OnClickListener mLocationClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + onToggleLocationAccess(!mLocationAccess.isChecked()); + } + }; + + private View.OnClickListener mGpsClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + Settings.Secure.setLocationProviderEnabled(mContentResolver, + LocationManager.GPS_PROVIDER, !mGps.isChecked()); + } + }; + + private View.OnClickListener mNetworkClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + Settings.Secure.setLocationProviderEnabled(mContentResolver, + LocationManager.NETWORK_PROVIDER, !mNetwork.isChecked()); + } + }; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mContentResolver = getActivity().getContentResolver(); + getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.primary_dark)); + } + + @Override + protected void initializePage() { + mLocationRow = mRootView.findViewById(R.id.location); + mLocationRow.setOnClickListener(mLocationClickListener); + mLocationAccess = (CheckBox) mRootView.findViewById(R.id.location_checkbox); + mGpsRow = mRootView.findViewById(R.id.gps); + mGpsRow.setOnClickListener(mGpsClickListener); + mGps = (CheckBox) mRootView.findViewById(R.id.gps_checkbox); + mNetworkRow = mRootView.findViewById(R.id.network); + mNetworkRow.setOnClickListener(mNetworkClickListener); + mNetwork = (CheckBox) mRootView.findViewById(R.id.network_checkbox); + } + + @Override + protected int getLayoutResource() { + return R.layout.location_settings; + } + + @Override + protected int getHeaderLayoutResource() { + return R.layout.header_condensed; + } + + @Override + public void onResume() { + super.onResume(); + updateLocationToggles(); + if (mSettingsObserver == null) { + mSettingsObserver = new Observer() { + public void update(Observable o, Object arg) { + updateLocationToggles(); + } + }; + } + + mContentQueryMap.addObserver(mSettingsObserver); + } + + @Override + public void onStart() { + super.onStart(); + // listen for Location Manager settings changes + Cursor settingsCursor = getActivity().getContentResolver().query(Settings.Secure.CONTENT_URI, null, + "(" + Settings.System.NAME + "=?)", + new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, + null); + mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null); + } + + @Override + public void onStop() { + super.onStop(); + if (mSettingsObserver != null) { + mContentQueryMap.deleteObserver(mSettingsObserver); + } + mContentQueryMap.close(); + } + + + private void updateLocationToggles() { + boolean gpsEnabled = Settings.Secure.isLocationProviderEnabled( + mContentResolver, LocationManager.GPS_PROVIDER); + boolean networkEnabled = Settings.Secure.isLocationProviderEnabled( + mContentResolver, LocationManager.NETWORK_PROVIDER); + mGps.setChecked(gpsEnabled); + mNetwork.setChecked(networkEnabled); + mLocationAccess.setChecked(gpsEnabled || networkEnabled); + } + + private void onToggleLocationAccess(boolean checked) { + Settings.Secure.setLocationProviderEnabled(mContentResolver, + LocationManager.GPS_PROVIDER, checked); + mGps.setEnabled(checked); + mGpsRow.setEnabled(checked); + Settings.Secure.setLocationProviderEnabled(mContentResolver, + LocationManager.NETWORK_PROVIDER, checked); + mNetwork.setEnabled(checked); + mNetworkRow.setEnabled(checked); + updateLocationToggles(); + } + + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/MobileDataPage.java b/src/com/cyanogenmod/setupwizard/setup/MobileDataPage.java new file mode 100644 index 0000000..9223128 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/MobileDataPage.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.Switch; +import android.widget.TextView; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; +import com.cyanogenmod.setupwizard.util.SetupWizardUtils; + +public class MobileDataPage extends SetupPage { + + public static final String TAG = "MobileDataPage"; + + public MobileDataPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(SetupPage.KEY_PAGE_ARGUMENT, getKey()); + + MobileDataFragment fragment = new MobileDataFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_mobile_data; + } + + public static class MobileDataFragment extends SetupPageFragment { + + private View mEnableDataRow; + private Switch mEnableMobileData; + private ImageView mSignalView; + private TextView mNameView; + + private TelephonyManager mPhone; + private SignalStrength mSignalStrength; + private ServiceState mServiceState; + + private PhoneStateListener mPhoneStateListener = + new PhoneStateListener(SubscriptionManager.getDefaultDataSubId()) { + + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mSignalStrength = signalStrength; + updateSignalStrength(); + } + + @Override + public void onServiceStateChanged(ServiceState state) { + mServiceState = state; + updateSignalStrength(); + } + + }; + + private View.OnClickListener mEnableDataClickListener = new View.OnClickListener() { + @Override + public void onClick(View view) { + boolean checked = !mEnableMobileData.isChecked(); + SetupWizardUtils.setMobileDataEnabled(getActivity(), checked); + mEnableMobileData.setChecked(checked); + } + }; + + @Override + protected void initializePage() { + mEnableDataRow = mRootView.findViewById(R.id.data); + mEnableDataRow.setOnClickListener(mEnableDataClickListener); + mEnableMobileData = (Switch) mRootView.findViewById(R.id.data_switch); + mSignalView = (ImageView) mRootView.findViewById(R.id.signal); + mNameView = (TextView) mRootView.findViewById(R.id.enable_data_title); + updateDataConnectionStatus(); + updateSignalStrength(); + } + + @Override + protected int getLayoutResource() { + return R.layout.mobile_data_settings; + } + + @Override + public void onResume() { + super.onResume(); + updateDataConnectionStatus(); + updateSignalStrength(); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mPhone = (TelephonyManager)getActivity().getSystemService(Context.TELEPHONY_SERVICE); + mPhone.listen(mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); + } + + @Override + public void onDetach() { + super.onDetach(); + mPhone.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + } + + private void updateCarrierText() { + String name = mPhone.getNetworkOperatorName(SubscriptionManager.getDefaultDataSubId()); + if (TextUtils.isEmpty(name)) { + if (mServiceState != null && mServiceState.isEmergencyOnly()) { + name = getString(R.string.setup_mobile_data_emergency_only); + } else { + name = getString(R.string.setup_mobile_data_no_service); + } + } + mNameView.setText(name); + } + + private void updateSignalStrength() { + if (!hasService()) { + mSignalView.setImageResource(R.drawable.ic_signal_no_signal); + } else { + if (mSignalStrength != null) { + int resId; + switch (mSignalStrength.getLevel()) { + case 4: + resId = R.drawable.ic_signal_4; + break; + case 3: + resId = R.drawable.ic_signal_3; + break; + case 2: + resId = R.drawable.ic_signal_2; + break; + case 1: + resId = R.drawable.ic_signal_1; + break; + default: + resId = R.drawable.ic_signal_0; + break; + } + mSignalView.setImageResource(resId); + } + } + updateCarrierText(); + } + + private void updateDataConnectionStatus() { + mEnableMobileData.setChecked(SetupWizardUtils.isMobileDataEnabled(getActivity())); + } + + private boolean hasService() { + boolean retVal; + if (mServiceState != null) { + // Consider the device to be in service if either voice or data service is available. + // Some SIM cards are marketed as data-only and do not support voice service, and on + // these SIM cards, we want to show signal bars for data service as well as the "no + // service" or "emergency calls only" text that indicates that voice is not available. + switch(mServiceState.getVoiceRegState()) { + case ServiceState.STATE_POWER_OFF: + retVal = false; + break; + case ServiceState.STATE_OUT_OF_SERVICE: + case ServiceState.STATE_EMERGENCY_ONLY: + retVal = mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; + break; + default: + retVal = true; + } + } else { + retVal = false; + } + return retVal; + } + + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/Page.java b/src/com/cyanogenmod/setupwizard/setup/Page.java new file mode 100644 index 0000000..31a02a7 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/Page.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Intent; +import android.os.Bundle; + +public interface Page { + + public static final String KEY_PAGE_ARGUMENT = "key_arg"; + + public static final int ACTION_NEXT = 1; + public static final int ACTION_PREVIOUS = 2; + + public String getKey(); + public int getTitleResId(); + public int getPrevButtonTitleResId(); + public int getNextButtonTitleResId(); + public Fragment getFragment(); + public Bundle getData(); + public void resetData(Bundle data); + public boolean isRequired(); + public Page setRequired(boolean required); + public boolean isCompleted(); + public void setCompleted(boolean completed); + public boolean doPreviousAction(); + public boolean doNextAction(); + public void doLoadAction(Activity context, int action); + public abstract boolean onActivityResult(int requestCode, int resultCode, Intent data); +} diff --git a/src/com/cyanogenmod/setupwizard/setup/PageList.java b/src/com/cyanogenmod/setupwizard/setup/PageList.java new file mode 100644 index 0000000..6709d47 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/PageList.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import java.util.LinkedHashMap; + +public class PageList extends LinkedHashMap<String, Page> { + + public PageList(Page... pages) { + for (Page page : pages) { + put(page.getKey(), page); + } + } + + public Page getPage(String key) { + return get(key); + } + + public Page getPage(int index) { + int i=0; + for (Page page : values()) { + if (i == index) { + return page; + } + i++; + } + return null; + } + +} diff --git a/src/com/cyanogenmod/setupwizard/setup/SetupDataCallbacks.java b/src/com/cyanogenmod/setupwizard/setup/SetupDataCallbacks.java new file mode 100644 index 0000000..2e787b4 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/SetupDataCallbacks.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.os.Bundle; +import android.view.LayoutInflater; + +public interface SetupDataCallbacks { + void onNextPage(); + void onPreviousPage(); + void onPageLoaded(Page page); + void onPageTreeChanged(); + void onFinish(); + Page getPage(String key); + Page getPage(int key); + void onPageViewCreated(LayoutInflater inflater, Bundle savedInstanceState, int layoutResource); +} diff --git a/src/com/cyanogenmod/setupwizard/setup/SetupPage.java b/src/com/cyanogenmod/setupwizard/setup/SetupPage.java new file mode 100644 index 0000000..c607857 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/SetupPage.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import com.cyanogenmod.setupwizard.R; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + + +public abstract class SetupPage implements Page { + + private final SetupDataCallbacks mCallbacks; + + private Bundle mData = new Bundle(); + private boolean mRequired = false; + private boolean mCompleted = false; + + protected final Context mContext; + + protected SetupPage(Context context, SetupDataCallbacks callbacks) { + mContext = context; + mCallbacks = callbacks; + } + + @Override + public Fragment getFragment() { + return null; + } + + @Override + public int getPrevButtonTitleResId() { + return -1; + } + + @Override + public int getNextButtonTitleResId() { + return R.string.next; + } + + @Override + public boolean doNextAction() { + return false; + } + + @Override + public boolean doPreviousAction() { + return false; + } + + @Override + public void doLoadAction(Activity context, int action) { + if (context == null || context.isFinishing()) { return; } + final FragmentManager fragmentManager = context.getFragmentManager(); + if (action == Page.ACTION_NEXT) { + FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.replace(R.id.content, getFragment(), getKey()); + transaction.commit(); + } else { + FragmentTransaction transaction = fragmentManager.beginTransaction(); + transaction.replace(R.id.content, getFragment(), getKey()); + transaction.commit(); + } + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + return false; + } + + @Override + public boolean isRequired() { + return mRequired; + } + + @Override + public Page setRequired(boolean required) { + mRequired = required; + return this; + } + + @Override + public boolean isCompleted() { + return mCompleted; + } + + @Override + public void setCompleted(boolean completed) { + mCompleted = completed; + mCallbacks.onNextPage(); + } + + @Override + public Bundle getData() { + return mData; + } + + @Override + public void resetData(Bundle data) { + mData = data; + mCallbacks.onPageLoaded(this); + } + + protected SetupDataCallbacks getCallbacks() { + return mCallbacks; + } +} diff --git a/src/com/cyanogenmod/setupwizard/setup/SimCardMissingPage.java b/src/com/cyanogenmod/setupwizard/setup/SimCardMissingPage.java new file mode 100644 index 0000000..8f74e24 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/SimCardMissingPage.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; + +public class SimCardMissingPage extends SetupPage { + + public static final String TAG = "SimCardMissingPage"; + + public SimCardMissingPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(SetupPage.KEY_PAGE_ARGUMENT, getKey()); + + FinishFragment fragment = new FinishFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.setup_sim_missing; + } + + @Override + public int getNextButtonTitleResId() { + return R.string.skip; + } + + + public static class FinishFragment extends SetupPageFragment { + + @Override + protected void initializePage() {} + + @Override + protected int getLayoutResource() { + return R.layout.sim_missing_page; + } + + } + +} diff --git a/src/com/cyanogenmod/setupwizard/setup/WelcomePage.java b/src/com/cyanogenmod/setupwizard/setup/WelcomePage.java new file mode 100644 index 0000000..ca3bf99 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/WelcomePage.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Fragment; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.widget.ArrayAdapter; +import android.widget.NumberPicker; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.ui.LocalePicker; +import com.cyanogenmod.setupwizard.ui.SetupPageFragment; + +import java.util.Locale; + +public class WelcomePage extends SetupPage { + + public static final String TAG = "WelcomePage"; + + public WelcomePage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public Fragment getFragment() { + Bundle args = new Bundle(); + args.putString(SetupPage.KEY_PAGE_ARGUMENT, getKey()); + + WelcomeFragment fragment = new WelcomeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public int getTitleResId() { + return R.string.setup_welcome; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getPrevButtonTitleResId() { + return R.string.emergency_call; + } + + public static class WelcomeFragment extends SetupPageFragment { + + private ArrayAdapter<com.android.internal.app.LocalePicker.LocaleInfo> mLocaleAdapter; + private Locale mInitialLocale; + private Locale mCurrentLocale; + private int[] mAdapterIndices; + + private LocalePicker mLanguagePicker; + + private final Handler mHandler = new Handler(); + + private final Runnable mUpdateLocale = new Runnable() { + public void run() { + if (mCurrentLocale != null) { + com.android.internal.app.LocalePicker.updateLocale(mCurrentLocale); + } + } + }; + + @Override + protected void initializePage() { + mLanguagePicker = (LocalePicker) mRootView.findViewById(R.id.locale_list); + loadLanguages(); + } + + private void loadLanguages() { + mLocaleAdapter = com.android.internal.app.LocalePicker.constructAdapter(getActivity(), R.layout.locale_picker_item, R.id.locale); + mInitialLocale = Locale.getDefault(); + mCurrentLocale = mInitialLocale; + mAdapterIndices = new int[mLocaleAdapter.getCount()]; + int currentLocaleIndex = 0; + String [] labels = new String[mLocaleAdapter.getCount()]; + for (int i=0; i<mAdapterIndices.length; i++) { + com.android.internal.app.LocalePicker.LocaleInfo localLocaleInfo = mLocaleAdapter.getItem(i); + Locale localLocale = localLocaleInfo.getLocale(); + if (localLocale.equals(mCurrentLocale)) { + currentLocaleIndex = i; + } + mAdapterIndices[i] = i; + labels[i] = localLocaleInfo.getLabel(); + } + mLanguagePicker.setDisplayedValues(labels); + mLanguagePicker.setMaxValue(labels.length - 1); + mLanguagePicker.setValue(currentLocaleIndex); + mLanguagePicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); + mLanguagePicker.setOnValueChangedListener(new LocalePicker.OnValueChangeListener() { + public void onValueChange(LocalePicker picker, int oldVal, int newVal) { + setLocaleFromPicker(); + } + }); + } + + private void setLocaleFromPicker() { + int i = mAdapterIndices[mLanguagePicker.getValue()]; + final com.android.internal.app.LocalePicker.LocaleInfo localLocaleInfo = mLocaleAdapter.getItem(i); + onLocaleChanged(localLocaleInfo.getLocale()); + } + + private void onLocaleChanged(Locale paramLocale) { + Resources localResources = getActivity().getResources(); + Configuration localConfiguration1 = localResources.getConfiguration(); + Configuration localConfiguration2 = new Configuration(); + localConfiguration2.locale = paramLocale; + localResources.updateConfiguration(localConfiguration2, null); + localResources.updateConfiguration(localConfiguration1, null); + mHandler.removeCallbacks(mUpdateLocale); + mCurrentLocale = paramLocale; + mHandler.postDelayed(mUpdateLocale, 1000); + } + + @Override + protected int getLayoutResource() { + return R.layout.setup_welcome_page; + } + + @Override + protected int getHeaderLayoutResource() { + return R.layout.logo_header; + } + } + +} diff --git a/src/com/cyanogenmod/setupwizard/setup/WifiSetupPage.java b/src/com/cyanogenmod/setupwizard/setup/WifiSetupPage.java new file mode 100644 index 0000000..705b932 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/setup/WifiSetupPage.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.setup; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.SetupWizardApp; +import com.cyanogenmod.setupwizard.util.SetupWizardUtils; + +public class WifiSetupPage extends SetupPage { + + public static final String TAG = "WifiSetupPage"; + + public WifiSetupPage(Context context, SetupDataCallbacks callbacks) { + super(context, callbacks); + } + + @Override + public int getNextButtonTitleResId() { + return R.string.skip; + } + + @Override + public String getKey() { + return TAG; + } + + @Override + public int getTitleResId() { + return R.string.existing; + } + + @Override + public void doLoadAction(Activity context, int action) { + if (action == Page.ACTION_PREVIOUS) { + getCallbacks().onPreviousPage(); + } else { + SetupWizardUtils.launchWifiSetup(context); + } + } + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != SetupWizardApp.REQUEST_CODE_SETUP_WIFI) return false; + getCallbacks().onNextPage(); + return true; + } +} diff --git a/src/com/cyanogenmod/setupwizard/ui/LocalePicker.java b/src/com/cyanogenmod/setupwizard/ui/LocalePicker.java new file mode 100644 index 0000000..049476c --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/ui/LocalePicker.java @@ -0,0 +1,2607 @@ +/* + * Copyright (C) 2008 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.cyanogenmod.setupwizard.ui; + +import com.android.internal.R; + +import android.annotation.Widget; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.InputFilter; +import android.text.InputType; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.NumberKeyListener; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; +import android.view.animation.DecelerateInterpolator; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Scroller; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import libcore.icu.LocaleData; + +/** + * A widget that enables the user to select a number form a predefined range. + * There are two flavors of this widget and which one is presented to the user + * depends on the current theme. + * <ul> + * <li> + * If the current theme is derived from {@link android.R.style#Theme} the widget + * presents the current value as an editable input field with an increment button + * above and a decrement button below. Long pressing the buttons allows for a quick + * change of the current value. Tapping on the input field allows to type in + * a desired value. + * </li> + * <li> + * If the current theme is derived from {@link android.R.style#Theme_Holo} or + * {@link android.R.style#Theme_Holo_Light} the widget presents the current + * value as an editable input field with a lesser value above and a greater + * value below. Tapping on the lesser or greater value selects it by animating + * the number axis up or down to make the chosen value current. Flinging up + * or down allows for multiple increments or decrements of the current value. + * Long pressing on the lesser and greater values also allows for a quick change + * of the current value. Tapping on the current value allows to type in a + * desired value. + * </li> + * </ul> + * <p> + * For an example of using this widget, see {@link android.widget.TimePicker}. + * </p> + */ +@Widget +public class LocalePicker extends LinearLayout { + + /** + * The number of items show in the selector wheel. + */ + private static int SELECTOR_WHEEL_ITEM_COUNT = 3; + + /** + * The default update interval during long press. + */ + private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300; + + /** + * The index of the middle selector item. + */ + private static int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2; + + /** + * The coefficient by which to adjust (divide) the max fling velocity. + */ + private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8; + + /** + * The the duration for adjusting the selector wheel. + */ + private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800; + + /** + * The duration of scrolling while snapping to a given position. + */ + private static final int SNAP_SCROLL_DURATION = 300; + + /** + * The strength of fading in the top and bottom while drawing the selector. + */ + private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f; + + /** + * The default unscaled height of the selection divider. + */ + private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 1; + + /** + * The default unscaled distance between the selection dividers. + */ + private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48; + + /** + * The resource id for the default layout. + */ + private static final int DEFAULT_LAYOUT_RESOURCE_ID = + com.cyanogenmod.setupwizard.R.layout.locale_picker; + + /** + * Constant for unspecified size. + */ + private static final int SIZE_UNSPECIFIED = -1; + + /** + * Use a custom NumberPicker formatting callback to use two-digit minutes + * strings like "01". Keeping a static formatter etc. is the most efficient + * way to do this; it avoids creating temporary objects on every call to + * format(). + */ + private static class TwoDigitFormatter implements LocalePicker.Formatter { + final StringBuilder mBuilder = new StringBuilder(); + + char mZeroDigit; + java.util.Formatter mFmt; + + final Object[] mArgs = new Object[1]; + + TwoDigitFormatter() { + final Locale locale = Locale.getDefault(); + init(locale); + } + + private void init(Locale locale) { + mFmt = createFormatter(locale); + mZeroDigit = getZeroDigit(locale); + } + + public String format(int value) { + final Locale currentLocale = Locale.getDefault(); + if (mZeroDigit != getZeroDigit(currentLocale)) { + init(currentLocale); + } + mArgs[0] = value; + mBuilder.delete(0, mBuilder.length()); + mFmt.format("%02d", mArgs); + return mFmt.toString(); + } + + private static char getZeroDigit(Locale locale) { + return LocaleData.get(locale).zeroDigit; + } + + private java.util.Formatter createFormatter(Locale locale) { + return new java.util.Formatter(mBuilder, locale); + } + } + + private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter(); + + /** + * @hide + */ + public static final Formatter getTwoDigitFormatter() { + return sTwoDigitFormatter; + } + + /** + * The increment button. + */ + private final ImageButton mIncrementButton; + + /** + * The decrement button. + */ + private final ImageButton mDecrementButton; + + /** + * The text for showing the current value. + */ + private final EditText mInputText; + + /** + * The distance between the two selection dividers. + */ + private final int mSelectionDividersDistance; + + /** + * The min height of this widget. + */ + private final int mMinHeight; + + /** + * The max height of this widget. + */ + private final int mMaxHeight; + + /** + * The max width of this widget. + */ + private final int mMinWidth; + + /** + * The max width of this widget. + */ + private int mMaxWidth; + + /** + * Flag whether to compute the max width. + */ + private final boolean mComputeMaxWidth; + + /** + * The height of the text. + */ + private final int mTextSize; + + /** + * The height of the gap between text elements if the selector wheel. + */ + private int mSelectorTextGapHeight; + + /** + * The values to be displayed instead the indices. + */ + private String[] mDisplayedValues; + + /** + * Lower value of the range of numbers allowed for the NumberPicker + */ + private int mMinValue; + + /** + * Upper value of the range of numbers allowed for the NumberPicker + */ + private int mMaxValue; + + /** + * Current value of this NumberPicker + */ + private int mValue; + + /** + * Listener to be notified upon current value change. + */ + private OnValueChangeListener mOnValueChangeListener; + + /** + * Listener to be notified upon scroll state change. + */ + private OnScrollListener mOnScrollListener; + + /** + * Formatter for for displaying the current value. + */ + private Formatter mFormatter; + + /** + * The speed for updating the value form long press. + */ + private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL; + + /** + * Cache for the string representation of selector indices. + */ + private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>(); + + /** + * The selector indices whose value are show by the selector. + */ + private final int[] mSelectorIndices; + + /** + * The {@link android.graphics.Paint} for drawing the selector. + */ + private final Paint mSelectorWheelPaint; + + /** + * The {@link android.graphics.drawable.Drawable} for pressed virtual (increment/decrement) buttons. + */ + private final Drawable mVirtualButtonPressedDrawable; + + /** + * The height of a selector element (text + gap). + */ + private int mSelectorElementHeight; + + /** + * The initial offset of the scroll selector. + */ + private int mInitialScrollOffset = Integer.MIN_VALUE; + + /** + * The current offset of the scroll selector. + */ + private int mCurrentScrollOffset; + + /** + * The {@link android.widget.Scroller} responsible for flinging the selector. + */ + private final Scroller mFlingScroller; + + /** + * The {@link android.widget.Scroller} responsible for adjusting the selector. + */ + private final Scroller mAdjustScroller; + + /** + * The previous Y coordinate while scrolling the selector. + */ + private int mPreviousScrollerY; + + /** + * Handle to the reusable command for setting the input text selection. + */ + private SetSelectionCommand mSetSelectionCommand; + + /** + * Handle to the reusable command for changing the current value from long + * press by one. + */ + private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand; + + /** + * Command for beginning an edit of the current value via IME on long press. + */ + private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand; + + /** + * The Y position of the last down event. + */ + private float mLastDownEventY; + + /** + * The time of the last down event. + */ + private long mLastDownEventTime; + + /** + * The Y position of the last down or move event. + */ + private float mLastDownOrMoveEventY; + + /** + * Determines speed during touch scrolling. + */ + private VelocityTracker mVelocityTracker; + + /** + * @see android.view.ViewConfiguration#getScaledTouchSlop() + */ + private int mTouchSlop; + + /** + * @see android.view.ViewConfiguration#getScaledMinimumFlingVelocity() + */ + private int mMinimumFlingVelocity; + + /** + * @see android.view.ViewConfiguration#getScaledMaximumFlingVelocity() + */ + private int mMaximumFlingVelocity; + + /** + * Flag whether the selector should wrap around. + */ + private boolean mWrapSelectorWheel; + + /** + * The back ground color used to optimize scroller fading. + */ + private final int mSolidColor; + + /** + * Flag whether this widget has a selector wheel. + */ + private final boolean mHasSelectorWheel; + + /** + * Divider for showing item to be selected while scrolling + */ + private final Drawable mSelectionDivider; + + /** + * The height of the selection divider. + */ + private final int mSelectionDividerHeight; + + /** + * The current scroll state of the number picker. + */ + private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * Flag whether to ignore move events - we ignore such when we show in IME + * to prevent the content from scrolling. + */ + private boolean mIngonreMoveEvents; + + /** + * Flag whether to show soft input on tap. + */ + private boolean mShowSoftInputOnTap; + + /** + * The top of the top selection divider. + */ + private int mTopSelectionDividerTop; + + /** + * The bottom of the bottom selection divider. + */ + private int mBottomSelectionDividerBottom; + + /** + * The virtual id of the last hovered child. + */ + private int mLastHoveredChildVirtualViewId; + + /** + * Whether the increment virtual button is pressed. + */ + private boolean mIncrementVirtualButtonPressed; + + /** + * Whether the decrement virtual button is pressed. + */ + private boolean mDecrementVirtualButtonPressed; + + /** + * Provider to report to clients the semantic structure of this widget. + */ + private AccessibilityNodeProviderImpl mAccessibilityNodeProvider; + + /** + * Helper class for managing pressed state of the virtual buttons. + */ + private final PressedStateHelper mPressedStateHelper; + + /** + * The keycode of the last handled DPAD down event. + */ + private int mLastHandledDownDpadKeyCode = -1; + + /** + * Interface to listen for changes of the current value. + */ + public interface OnValueChangeListener { + + /** + * Called upon a change of the current value. + * + * @param picker The NumberPicker associated with this listener. + * @param oldVal The previous value. + * @param newVal The new value. + */ + void onValueChange(LocalePicker picker, int oldVal, int newVal); + } + + /** + * Interface to listen for the picker scroll state. + */ + public interface OnScrollListener { + + /** + * The view is not scrolling. + */ + public static int SCROLL_STATE_IDLE = 0; + + /** + * The user is scrolling using touch, and his finger is still on the screen. + */ + public static int SCROLL_STATE_TOUCH_SCROLL = 1; + + /** + * The user had previously been scrolling using touch and performed a fling. + */ + public static int SCROLL_STATE_FLING = 2; + + /** + * Callback invoked while the number picker scroll state has changed. + * + * @param view The view whose scroll state is being reported. + * @param scrollState The current scroll state. One of + * {@link #SCROLL_STATE_IDLE}, + * {@link #SCROLL_STATE_TOUCH_SCROLL} or + * {@link #SCROLL_STATE_IDLE}. + */ + public void onScrollStateChange(LocalePicker view, int scrollState); + } + + /** + * Interface used to format current value into a string for presentation. + */ + public interface Formatter { + + /** + * Formats a string representation of the current value. + * + * @param value The currently selected value. + * @return A formatted string representation. + */ + public String format(int value); + } + + /** + * Create a new number picker. + * + * @param context The application environment. + */ + public LocalePicker(Context context) { + this(context, null); + } + + /** + * Create a new number picker. + * + * @param context The application environment. + * @param attrs A collection of attributes. + */ + public LocalePicker(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.numberPickerStyle); + } + + /** + * Create a new number picker + * + * @param context the application environment. + * @param attrs a collection of attributes. + * @param defStyle The default style to apply to this view. + */ + public LocalePicker(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + SELECTOR_WHEEL_ITEM_COUNT = context.getResources().getInteger(com.cyanogenmod.setupwizard.R.integer.local_picker_items); + SELECTOR_MIDDLE_ITEM_INDEX = context.getResources().getInteger(com.cyanogenmod.setupwizard.R.integer.local_picker_items)/2; + mSelectorIndices= new int[SELECTOR_WHEEL_ITEM_COUNT]; + // process style attributes + TypedArray attributesArray = context.obtainStyledAttributes( + attrs, R.styleable.NumberPicker, defStyle, 0); + final int layoutResId = attributesArray.getResourceId( + R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID); + + mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID); + + mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0); + + mSelectionDivider = context.getDrawable(com.cyanogenmod.setupwizard.R.drawable.divider); + + final int defSelectionDividerHeight = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, + getResources().getDisplayMetrics()); + mSelectionDividerHeight = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight); + + final int defSelectionDividerDistance = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE, + getResources().getDisplayMetrics()); + mSelectionDividersDistance = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance); + + mMinHeight = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED); + + mMaxHeight = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED); + if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED + && mMinHeight > mMaxHeight) { + throw new IllegalArgumentException("minHeight > maxHeight"); + } + + mMinWidth = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED); + + mMaxWidth = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED); + if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED + && mMinWidth > mMaxWidth) { + throw new IllegalArgumentException("minWidth > maxWidth"); + } + + mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED); + + mVirtualButtonPressedDrawable = attributesArray.getDrawable( + R.styleable.NumberPicker_virtualButtonPressedDrawable); + + attributesArray.recycle(); + + mPressedStateHelper = new PressedStateHelper(); + + // By default Linearlayout that we extend is not drawn. This is + // its draw() method is not called but dispatchDraw() is called + // directly (see ViewGroup.drawChild()). However, this class uses + // the fading edge effect implemented by View and we need our + // draw() method to be called. Therefore, we declare we will draw. + setWillNotDraw(!mHasSelectorWheel); + + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(layoutResId, this, true); + + OnClickListener onClickListener = new OnClickListener() { + public void onClick(View v) { + hideSoftInput(); + mInputText.clearFocus(); + if (v.getId() == R.id.increment) { + changeValueByOne(true); + } else { + changeValueByOne(false); + } + } + }; + + OnLongClickListener onLongClickListener = new OnLongClickListener() { + public boolean onLongClick(View v) { + hideSoftInput(); + mInputText.clearFocus(); + if (v.getId() == R.id.increment) { + postChangeCurrentByOneFromLongPress(true, 0); + } else { + postChangeCurrentByOneFromLongPress(false, 0); + } + return true; + } + }; + + // increment button + if (!mHasSelectorWheel) { + mIncrementButton = (ImageButton) findViewById(R.id.increment); + mIncrementButton.setOnClickListener(onClickListener); + mIncrementButton.setOnLongClickListener(onLongClickListener); + } else { + mIncrementButton = null; + } + + // decrement button + if (!mHasSelectorWheel) { + mDecrementButton = (ImageButton) findViewById(R.id.decrement); + mDecrementButton.setOnClickListener(onClickListener); + mDecrementButton.setOnLongClickListener(onLongClickListener); + } else { + mDecrementButton = null; + } + + // input text + mInputText = (EditText) findViewById(R.id.numberpicker_input); + mInputText.setOnFocusChangeListener(new OnFocusChangeListener() { + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + mInputText.selectAll(); + } else { + mInputText.setSelection(0, 0); + validateInputTextView(v); + } + } + }); + mInputText.setFilters(new InputFilter[] { + new InputTextFilter() + }); + + mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); + mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE); + + // initialize constants + ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); + mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity() + / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT; + mTextSize = (int) mInputText.getTextSize(); + + // create the selector wheel paint + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextAlign(Align.CENTER); + paint.setTextSize(mTextSize); + paint.setTypeface(mInputText.getTypeface()); + ColorStateList colors = mInputText.getTextColors(); + int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE); + paint.setColor(color); + mSelectorWheelPaint = paint; + + // create the fling and adjust scrollers + mFlingScroller = new Scroller(getContext(), null, true); + mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f)); + + updateInputTextView(); + + // If not explicitly specified this view is important for accessibility. + if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mHasSelectorWheel) { + super.onLayout(changed, left, top, right, bottom); + return; + } + final int msrdWdth = getMeasuredWidth(); + final int msrdHght = getMeasuredHeight(); + + // Input text centered horizontally. + final int inptTxtMsrdWdth = mInputText.getMeasuredWidth(); + final int inptTxtMsrdHght = mInputText.getMeasuredHeight(); + final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2; + final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2; + final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth; + final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght; + mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom); + + if (changed) { + // need to do all this when we know our size + initializeSelectorWheel(); + initializeFadingEdges(); + mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2 + - mSelectionDividerHeight; + mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight + + mSelectionDividersDistance; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (!mHasSelectorWheel) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + // Try greedily to fit the max width and height. + final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth); + final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight); + super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec); + // Flag if we are measured with width or height less than the respective min. + final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(), + widthMeasureSpec); + final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(), + heightMeasureSpec); + setMeasuredDimension(widthSize, heightSize); + } + + /** + * Move to the final position of a scroller. Ensures to force finish the scroller + * and if it is not at its final position a scroll of the selector wheel is + * performed to fast forward to the final position. + * + * @param scroller The scroller to whose final position to get. + * @return True of the a move was performed, i.e. the scroller was not in final position. + */ + private boolean moveToFinalScrollerPosition(Scroller scroller) { + scroller.forceFinished(true); + int amountToScroll = scroller.getFinalY() - scroller.getCurrY(); + int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight; + int overshootAdjustment = mInitialScrollOffset - futureScrollOffset; + if (overshootAdjustment != 0) { + if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) { + if (overshootAdjustment > 0) { + overshootAdjustment -= mSelectorElementHeight; + } else { + overshootAdjustment += mSelectorElementHeight; + } + } + amountToScroll += overshootAdjustment; + scrollBy(0, amountToScroll); + return true; + } + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (!mHasSelectorWheel || !isEnabled()) { + return false; + } + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + removeAllCallbacks(); + mInputText.setVisibility(View.INVISIBLE); + mLastDownOrMoveEventY = mLastDownEventY = event.getY(); + mLastDownEventTime = event.getEventTime(); + mIngonreMoveEvents = false; + mShowSoftInputOnTap = false; + // Handle pressed state before any state change. + if (mLastDownEventY < mTopSelectionDividerTop) { + if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { + mPressedStateHelper.buttonPressDelayed( + PressedStateHelper.BUTTON_DECREMENT); + } + } else if (mLastDownEventY > mBottomSelectionDividerBottom) { + if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { + mPressedStateHelper.buttonPressDelayed( + PressedStateHelper.BUTTON_INCREMENT); + } + } + // Make sure we support flinging inside scrollables. + getParent().requestDisallowInterceptTouchEvent(true); + if (!mFlingScroller.isFinished()) { + mFlingScroller.forceFinished(true); + mAdjustScroller.forceFinished(true); + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + } else if (!mAdjustScroller.isFinished()) { + mFlingScroller.forceFinished(true); + mAdjustScroller.forceFinished(true); + } else if (mLastDownEventY < mTopSelectionDividerTop) { + hideSoftInput(); + postChangeCurrentByOneFromLongPress( + false, ViewConfiguration.getLongPressTimeout()); + } else if (mLastDownEventY > mBottomSelectionDividerBottom) { + hideSoftInput(); + postChangeCurrentByOneFromLongPress( + true, ViewConfiguration.getLongPressTimeout()); + } else { + mShowSoftInputOnTap = true; + postBeginSoftInputOnLongPressCommand(); + } + return true; + } + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled() || !mHasSelectorWheel) { + return false; + } + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); + int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_MOVE: { + if (mIngonreMoveEvents) { + break; + } + float currentMoveY = event.getY(); + if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { + int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY); + if (deltaDownY > mTouchSlop) { + removeAllCallbacks(); + onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } + } else { + int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY)); + scrollBy(0, deltaMoveY); + invalidate(); + } + mLastDownOrMoveEventY = currentMoveY; + } break; + case MotionEvent.ACTION_UP: { + removeBeginSoftInputCommand(); + removeChangeCurrentByOneFromLongPress(); + mPressedStateHelper.cancel(); + VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); + int initialVelocity = (int) velocityTracker.getYVelocity(); + if (Math.abs(initialVelocity) > mMinimumFlingVelocity) { + fling(initialVelocity); + onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); + } else { + int eventY = (int) event.getY(); + int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY); + long deltaTime = event.getEventTime() - mLastDownEventTime; + if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) { + if (mShowSoftInputOnTap) { + mShowSoftInputOnTap = false; + showSoftInput(); + } else { + int selectorIndexOffset = (eventY / mSelectorElementHeight) + - SELECTOR_MIDDLE_ITEM_INDEX; + if (selectorIndexOffset > 0) { + changeValueByOne(true); + mPressedStateHelper.buttonTapped( + PressedStateHelper.BUTTON_INCREMENT); + } else if (selectorIndexOffset < 0) { + changeValueByOne(false); + mPressedStateHelper.buttonTapped( + PressedStateHelper.BUTTON_DECREMENT); + } + } + } else { + ensureScrollWheelAdjusted(); + } + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + } break; + } + return true; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + removeAllCallbacks(); + break; + } + return super.dispatchTouchEvent(event); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + final int keyCode = event.getKeyCode(); + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + removeAllCallbacks(); + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_UP: + if (!mHasSelectorWheel) { + break; + } + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + if (mWrapSelectorWheel || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) + ? getValue() < getMaxValue() : getValue() > getMinValue()) { + requestFocus(); + mLastHandledDownDpadKeyCode = keyCode; + removeAllCallbacks(); + if (mFlingScroller.isFinished()) { + changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN); + } + return true; + } + break; + case KeyEvent.ACTION_UP: + if (mLastHandledDownDpadKeyCode == keyCode) { + mLastHandledDownDpadKeyCode = -1; + return true; + } + break; + } + } + return super.dispatchKeyEvent(event); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + removeAllCallbacks(); + break; + } + return super.dispatchTrackballEvent(event); + } + + @Override + protected boolean dispatchHoverEvent(MotionEvent event) { + if (!mHasSelectorWheel) { + return super.dispatchHoverEvent(event); + } + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + final int eventY = (int) event.getY(); + final int hoveredVirtualViewId; + if (eventY < mTopSelectionDividerTop) { + hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT; + } else if (eventY > mBottomSelectionDividerBottom) { + hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT; + } else { + hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT; + } + final int action = event.getActionMasked(); + AccessibilityNodeProviderImpl provider = + (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: { + provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + mLastHoveredChildVirtualViewId = hoveredVirtualViewId; + provider.performAction(hoveredVirtualViewId, + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } break; + case MotionEvent.ACTION_HOVER_MOVE: { + if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId + && mLastHoveredChildVirtualViewId != View.NO_ID) { + provider.sendAccessibilityEventForVirtualView( + mLastHoveredChildVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + mLastHoveredChildVirtualViewId = hoveredVirtualViewId; + provider.performAction(hoveredVirtualViewId, + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + } + } break; + case MotionEvent.ACTION_HOVER_EXIT: { + provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + mLastHoveredChildVirtualViewId = View.NO_ID; + } break; + } + } + return false; + } + + @Override + public void computeScroll() { + Scroller scroller = mFlingScroller; + if (scroller.isFinished()) { + scroller = mAdjustScroller; + if (scroller.isFinished()) { + return; + } + } + scroller.computeScrollOffset(); + int currentScrollerY = scroller.getCurrY(); + if (mPreviousScrollerY == 0) { + mPreviousScrollerY = scroller.getStartY(); + } + scrollBy(0, currentScrollerY - mPreviousScrollerY); + mPreviousScrollerY = currentScrollerY; + if (scroller.isFinished()) { + onScrollerFinished(scroller); + } else { + invalidate(); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (!mHasSelectorWheel) { + mIncrementButton.setEnabled(enabled); + } + if (!mHasSelectorWheel) { + mDecrementButton.setEnabled(enabled); + } + mInputText.setEnabled(enabled); + } + + @Override + public void scrollBy(int x, int y) { + int[] selectorIndices = mSelectorIndices; + if (!mWrapSelectorWheel && y > 0 + && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { + mCurrentScrollOffset = mInitialScrollOffset; + return; + } + if (!mWrapSelectorWheel && y < 0 + && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { + mCurrentScrollOffset = mInitialScrollOffset; + return; + } + mCurrentScrollOffset += y; + while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) { + mCurrentScrollOffset -= mSelectorElementHeight; + decrementSelectorIndices(selectorIndices); + setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true); + if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { + mCurrentScrollOffset = mInitialScrollOffset; + } + } + while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) { + mCurrentScrollOffset += mSelectorElementHeight; + incrementSelectorIndices(selectorIndices); + setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true); + if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { + mCurrentScrollOffset = mInitialScrollOffset; + } + } + } + + @Override + public int getSolidColor() { + return mSolidColor; + } + + /** + * Sets the listener to be notified on change of the current value. + * + * @param onValueChangedListener The listener. + */ + public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) { + mOnValueChangeListener = onValueChangedListener; + } + + /** + * Set listener to be notified for scroll state changes. + * + * @param onScrollListener The listener. + */ + public void setOnScrollListener(OnScrollListener onScrollListener) { + mOnScrollListener = onScrollListener; + } + + /** + * Set the formatter to be used for formatting the current value. + * <p> + * Note: If you have provided alternative values for the values this + * formatter is never invoked. + * </p> + * + * @param formatter The formatter object. If formatter is <code>null</code>, + * {@link String#valueOf(int)} will be used. + *@see #setDisplayedValues(String[]) + */ + public void setFormatter(Formatter formatter) { + if (formatter == mFormatter) { + return; + } + mFormatter = formatter; + initializeSelectorWheelIndices(); + updateInputTextView(); + } + + /** + * Set the current value for the number picker. + * <p> + * If the argument is less than the {@link LocalePicker#getMinValue()} and + * {@link LocalePicker#getWrapSelectorWheel()} is <code>false</code> the + * current value is set to the {@link LocalePicker#getMinValue()} value. + * </p> + * <p> + * If the argument is less than the {@link LocalePicker#getMinValue()} and + * {@link LocalePicker#getWrapSelectorWheel()} is <code>true</code> the + * current value is set to the {@link LocalePicker#getMaxValue()} value. + * </p> + * <p> + * If the argument is less than the {@link LocalePicker#getMaxValue()} and + * {@link LocalePicker#getWrapSelectorWheel()} is <code>false</code> the + * current value is set to the {@link LocalePicker#getMaxValue()} value. + * </p> + * <p> + * If the argument is less than the {@link LocalePicker#getMaxValue()} and + * {@link LocalePicker#getWrapSelectorWheel()} is <code>true</code> the + * current value is set to the {@link LocalePicker#getMinValue()} value. + * </p> + * + * @param value The current value. + * @see #setWrapSelectorWheel(boolean) + * @see #setMinValue(int) + * @see #setMaxValue(int) + */ + public void setValue(int value) { + setValueInternal(value, false); + } + + /** + * Shows the soft input for its input text. + */ + private void showSoftInput() { + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + if (mHasSelectorWheel) { + mInputText.setVisibility(View.VISIBLE); + } + mInputText.requestFocus(); + inputMethodManager.showSoftInput(mInputText, 0); + } + } + + /** + * Hides the soft input if it is active for the input text. + */ + private void hideSoftInput() { + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) { + inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + if (mHasSelectorWheel) { + mInputText.setVisibility(View.INVISIBLE); + } + } + } + + /** + * Computes the max width if no such specified as an attribute. + */ + private void tryComputeMaxWidth() { + if (!mComputeMaxWidth) { + return; + } + int maxTextWidth = 0; + if (mDisplayedValues == null) { + float maxDigitWidth = 0; + for (int i = 0; i <= 9; i++) { + final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i)); + if (digitWidth > maxDigitWidth) { + maxDigitWidth = digitWidth; + } + } + int numberOfDigits = 0; + int current = mMaxValue; + while (current > 0) { + numberOfDigits++; + current = current / 10; + } + maxTextWidth = (int) (numberOfDigits * maxDigitWidth); + } else { + final int valueCount = mDisplayedValues.length; + for (int i = 0; i < valueCount; i++) { + final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]); + if (textWidth > maxTextWidth) { + maxTextWidth = (int) textWidth; + } + } + } + maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight(); + if (mMaxWidth != maxTextWidth) { + if (maxTextWidth > mMinWidth) { + mMaxWidth = maxTextWidth; + } else { + mMaxWidth = mMinWidth; + } + invalidate(); + } + } + + /** + * Gets whether the selector wheel wraps when reaching the min/max value. + * + * @return True if the selector wheel wraps. + * + * @see #getMinValue() + * @see #getMaxValue() + */ + public boolean getWrapSelectorWheel() { + return mWrapSelectorWheel; + } + + /** + * Sets whether the selector wheel shown during flinging/scrolling should + * wrap around the {@link LocalePicker#getMinValue()} and + * {@link LocalePicker#getMaxValue()} values. + * <p> + * By default if the range (max - min) is more than the number of items shown + * on the selector wheel the selector wheel wrapping is enabled. + * </p> + * <p> + * <strong>Note:</strong> If the number of items, i.e. the range ( + * {@link #getMaxValue()} - {@link #getMinValue()}) is less than + * the number of items shown on the selector wheel, the selector wheel will + * not wrap. Hence, in such a case calling this method is a NOP. + * </p> + * + * @param wrapSelectorWheel Whether to wrap. + */ + public void setWrapSelectorWheel(boolean wrapSelectorWheel) { + final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length; + if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) { + mWrapSelectorWheel = wrapSelectorWheel; + } + } + + /** + * Sets the speed at which the numbers be incremented and decremented when + * the up and down buttons are long pressed respectively. + * <p> + * The default value is 300 ms. + * </p> + * + * @param intervalMillis The speed (in milliseconds) at which the numbers + * will be incremented and decremented. + */ + public void setOnLongPressUpdateInterval(long intervalMillis) { + mLongPressUpdateInterval = intervalMillis; + } + + /** + * Returns the value of the picker. + * + * @return The value. + */ + public int getValue() { + return mValue; + } + + /** + * Returns the min value of the picker. + * + * @return The min value + */ + public int getMinValue() { + return mMinValue; + } + + /** + * Sets the min value of the picker. + * + * @param minValue The min value inclusive. + * + * <strong>Note:</strong> The length of the displayed values array + * set via {@link #setDisplayedValues(String[])} must be equal to the + * range of selectable numbers which is equal to + * {@link #getMaxValue()} - {@link #getMinValue()} + 1. + */ + public void setMinValue(int minValue) { + if (mMinValue == minValue) { + return; + } + if (minValue < 0) { + throw new IllegalArgumentException("minValue must be >= 0"); + } + mMinValue = minValue; + if (mMinValue > mValue) { + mValue = mMinValue; + } + boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; + setWrapSelectorWheel(wrapSelectorWheel); + initializeSelectorWheelIndices(); + updateInputTextView(); + tryComputeMaxWidth(); + invalidate(); + } + + /** + * Returns the max value of the picker. + * + * @return The max value. + */ + public int getMaxValue() { + return mMaxValue; + } + + /** + * Sets the max value of the picker. + * + * @param maxValue The max value inclusive. + * + * <strong>Note:</strong> The length of the displayed values array + * set via {@link #setDisplayedValues(String[])} must be equal to the + * range of selectable numbers which is equal to + * {@link #getMaxValue()} - {@link #getMinValue()} + 1. + */ + public void setMaxValue(int maxValue) { + if (mMaxValue == maxValue) { + return; + } + if (maxValue < 0) { + throw new IllegalArgumentException("maxValue must be >= 0"); + } + mMaxValue = maxValue; + if (mMaxValue < mValue) { + mValue = mMaxValue; + } + boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; + setWrapSelectorWheel(wrapSelectorWheel); + initializeSelectorWheelIndices(); + updateInputTextView(); + tryComputeMaxWidth(); + invalidate(); + } + + /** + * Gets the values to be displayed instead of string values. + * + * @return The displayed values. + */ + public String[] getDisplayedValues() { + return mDisplayedValues; + } + + /** + * Sets the values to be displayed. + * + * @param displayedValues The displayed values. + * + * <strong>Note:</strong> The length of the displayed values array + * must be equal to the range of selectable numbers which is equal to + * {@link #getMaxValue()} - {@link #getMinValue()} + 1. + */ + public void setDisplayedValues(String[] displayedValues) { + if (mDisplayedValues == displayedValues) { + return; + } + mDisplayedValues = displayedValues; + if (mDisplayedValues != null) { + // Allow text entry rather than strictly numeric entry. + mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + } else { + mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); + } + updateInputTextView(); + initializeSelectorWheelIndices(); + tryComputeMaxWidth(); + } + + @Override + protected float getTopFadingEdgeStrength() { + return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH; + } + + @Override + protected float getBottomFadingEdgeStrength() { + return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH; + } + + @Override + protected void onDetachedFromWindow() { + removeAllCallbacks(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (!mHasSelectorWheel) { + super.onDraw(canvas); + return; + } + float x = (mRight - mLeft) / 2; + float y = mCurrentScrollOffset; + + // draw the virtual buttons pressed state if needed + if (mVirtualButtonPressedDrawable != null + && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { + if (mDecrementVirtualButtonPressed) { + mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); + mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop); + mVirtualButtonPressedDrawable.draw(canvas); + } + if (mIncrementVirtualButtonPressed) { + mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); + mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight, + mBottom); + mVirtualButtonPressedDrawable.draw(canvas); + } + } + + // draw the selector wheel + int[] selectorIndices = mSelectorIndices; + for (int i = 0; i < selectorIndices.length; i++) { + int selectorIndex = selectorIndices[i]; + String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex); + // Do not draw the middle item if input is visible since the input + // is shown only if the wheel is static and it covers the middle + // item. Otherwise, if the user starts editing the text via the + // IME he may see a dimmed version of the old value intermixed + // with the new one. + if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) { + canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint); + } + y += mSelectorElementHeight; + } + + // draw the selection dividers + if (mSelectionDivider != null) { + // draw the top divider + int topOfTopDivider = mTopSelectionDividerTop; + int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight; + mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider); + mSelectionDivider.draw(canvas); + + // draw the bottom divider + int bottomOfBottomDivider = mBottomSelectionDividerBottom; + int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight; + mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider); + mSelectionDivider.draw(canvas); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(LocalePicker.class.getName()); + event.setScrollable(true); + event.setScrollY((mMinValue + mValue) * mSelectorElementHeight); + event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight); + } + + @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + if (!mHasSelectorWheel) { + return super.getAccessibilityNodeProvider(); + } + if (mAccessibilityNodeProvider == null) { + mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl(); + } + return mAccessibilityNodeProvider; + } + + /** + * Makes a measure spec that tries greedily to use the max value. + * + * @param measureSpec The measure spec. + * @param maxSize The max value for the size. + * @return A measure spec greedily imposing the max size. + */ + private int makeMeasureSpec(int measureSpec, int maxSize) { + if (maxSize == SIZE_UNSPECIFIED) { + return measureSpec; + } + final int size = MeasureSpec.getSize(measureSpec); + final int mode = MeasureSpec.getMode(measureSpec); + switch (mode) { + case MeasureSpec.EXACTLY: + return measureSpec; + case MeasureSpec.AT_MOST: + return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY); + case MeasureSpec.UNSPECIFIED: + return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY); + default: + throw new IllegalArgumentException("Unknown measure mode: " + mode); + } + } + + /** + * Utility to reconcile a desired size and state, with constraints imposed + * by a MeasureSpec. Tries to respect the min size, unless a different size + * is imposed by the constraints. + * + * @param minSize The minimal desired size. + * @param measuredSize The currently measured size. + * @param measureSpec The current measure spec. + * @return The resolved size and state. + */ + private int resolveSizeAndStateRespectingMinSize( + int minSize, int measuredSize, int measureSpec) { + if (minSize != SIZE_UNSPECIFIED) { + final int desiredWidth = Math.max(minSize, measuredSize); + return resolveSizeAndState(desiredWidth, measureSpec, 0); + } else { + return measuredSize; + } + } + + /** + * Resets the selector indices and clear the cached string representation of + * these indices. + */ + private void initializeSelectorWheelIndices() { + mSelectorIndexToStringCache.clear(); + int[] selectorIndices = mSelectorIndices; + int current = getValue(); + for (int i = 0; i < mSelectorIndices.length; i++) { + int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX); + if (mWrapSelectorWheel) { + selectorIndex = getWrappedSelectorIndex(selectorIndex); + } + selectorIndices[i] = selectorIndex; + ensureCachedScrollSelectorValue(selectorIndices[i]); + } + } + + /** + * Sets the current value of this NumberPicker. + * + * @param current The new value of the NumberPicker. + * @param notifyChange Whether to notify if the current value changed. + */ + private void setValueInternal(int current, boolean notifyChange) { + if (mValue == current) { + return; + } + // Wrap around the values if we go past the start or end + if (mWrapSelectorWheel) { + current = getWrappedSelectorIndex(current); + } else { + current = Math.max(current, mMinValue); + current = Math.min(current, mMaxValue); + } + int previous = mValue; + mValue = current; + updateInputTextView(); + if (notifyChange) { + notifyChange(previous, current); + } + initializeSelectorWheelIndices(); + invalidate(); + } + + /** + * Changes the current value by one which is increment or + * decrement based on the passes argument. + * decrement the current value. + * + * @param increment True to increment, false to decrement. + */ + private void changeValueByOne(boolean increment) { + if (mHasSelectorWheel) { + mInputText.setVisibility(View.INVISIBLE); + if (!moveToFinalScrollerPosition(mFlingScroller)) { + moveToFinalScrollerPosition(mAdjustScroller); + } + mPreviousScrollerY = 0; + if (increment) { + mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION); + } else { + mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION); + } + invalidate(); + } else { + if (increment) { + setValueInternal(mValue + 1, true); + } else { + setValueInternal(mValue - 1, true); + } + } + } + + private void initializeSelectorWheel() { + initializeSelectorWheelIndices(); + int[] selectorIndices = mSelectorIndices; + int totalTextHeight = selectorIndices.length * mTextSize; + float totalTextGapHeight = (mBottom - mTop) - totalTextHeight; + float textGapCount = selectorIndices.length; + mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f); + mSelectorElementHeight = mTextSize + mSelectorTextGapHeight; + // Ensure that the middle item is positioned the same as the text in + // mInputText + int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop(); + mInitialScrollOffset = editTextTextPosition + - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX); + mCurrentScrollOffset = mInitialScrollOffset; + updateInputTextView(); + } + + private void initializeFadingEdges() { + setVerticalFadingEdgeEnabled(true); + setFadingEdgeLength((mBottom - mTop - mTextSize) / 2); + } + + /** + * Callback invoked upon completion of a given <code>scroller</code>. + */ + private void onScrollerFinished(Scroller scroller) { + if (scroller == mFlingScroller) { + if (!ensureScrollWheelAdjusted()) { + updateInputTextView(); + } + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + } else { + if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { + updateInputTextView(); + } + } + } + + /** + * Handles transition to a given <code>scrollState</code> + */ + private void onScrollStateChange(int scrollState) { + if (mScrollState == scrollState) { + return; + } + mScrollState = scrollState; + if (mOnScrollListener != null) { + mOnScrollListener.onScrollStateChange(this, scrollState); + } + } + + /** + * Flings the selector with the given <code>velocityY</code>. + */ + private void fling(int velocityY) { + mPreviousScrollerY = 0; + + if (velocityY > 0) { + mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE); + } else { + mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE); + } + + invalidate(); + } + + /** + * @return The wrapped index <code>selectorIndex</code> value. + */ + private int getWrappedSelectorIndex(int selectorIndex) { + if (selectorIndex > mMaxValue) { + return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1; + } else if (selectorIndex < mMinValue) { + return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1; + } + return selectorIndex; + } + + /** + * Increments the <code>selectorIndices</code> whose string representations + * will be displayed in the selector. + */ + private void incrementSelectorIndices(int[] selectorIndices) { + for (int i = 0; i < selectorIndices.length - 1; i++) { + selectorIndices[i] = selectorIndices[i + 1]; + } + int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1; + if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) { + nextScrollSelectorIndex = mMinValue; + } + selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex; + ensureCachedScrollSelectorValue(nextScrollSelectorIndex); + } + + /** + * Decrements the <code>selectorIndices</code> whose string representations + * will be displayed in the selector. + */ + private void decrementSelectorIndices(int[] selectorIndices) { + for (int i = selectorIndices.length - 1; i > 0; i--) { + selectorIndices[i] = selectorIndices[i - 1]; + } + int nextScrollSelectorIndex = selectorIndices[1] - 1; + if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) { + nextScrollSelectorIndex = mMaxValue; + } + selectorIndices[0] = nextScrollSelectorIndex; + ensureCachedScrollSelectorValue(nextScrollSelectorIndex); + } + + /** + * Ensures we have a cached string representation of the given <code> + * selectorIndex</code> to avoid multiple instantiations of the same string. + */ + private void ensureCachedScrollSelectorValue(int selectorIndex) { + SparseArray<String> cache = mSelectorIndexToStringCache; + String scrollSelectorValue = cache.get(selectorIndex); + if (scrollSelectorValue != null) { + return; + } + if (selectorIndex < mMinValue || selectorIndex > mMaxValue) { + scrollSelectorValue = ""; + } else { + if (mDisplayedValues != null) { + int displayedValueIndex = selectorIndex - mMinValue; + scrollSelectorValue = mDisplayedValues[displayedValueIndex]; + } else { + scrollSelectorValue = formatNumber(selectorIndex); + } + } + cache.put(selectorIndex, scrollSelectorValue); + } + + private String formatNumber(int value) { + return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value); + } + + private void validateInputTextView(View v) { + String str = String.valueOf(((TextView) v).getText()); + if (TextUtils.isEmpty(str)) { + // Restore to the old value as we don't allow empty values + updateInputTextView(); + } else { + // Check the new value and ensure it's in range + int current = getSelectedPos(str.toString()); + setValueInternal(current, true); + } + } + + /** + * Updates the view of this NumberPicker. If displayValues were specified in + * the string corresponding to the index specified by the current value will + * be returned. Otherwise, the formatter specified in {@link #setFormatter} + * will be used to format the number. + * + * @return Whether the text was updated. + */ + private boolean updateInputTextView() { + /* + * If we don't have displayed values then use the current number else + * find the correct value in the displayed values for the current + * number. + */ + String text = (mDisplayedValues == null) ? formatNumber(mValue) + : mDisplayedValues[mValue - mMinValue]; + if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) { + mInputText.setText(text); + return true; + } + + return false; + } + + /** + * Notifies the listener, if registered, of a change of the value of this + * NumberPicker. + */ + private void notifyChange(int previous, int current) { + if (mOnValueChangeListener != null) { + mOnValueChangeListener.onValueChange(this, previous, mValue); + } + } + + /** + * Posts a command for changing the current value by one. + * + * @param increment Whether to increment or decrement the value. + */ + private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) { + if (mChangeCurrentByOneFromLongPressCommand == null) { + mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand(); + } else { + removeCallbacks(mChangeCurrentByOneFromLongPressCommand); + } + mChangeCurrentByOneFromLongPressCommand.setStep(increment); + postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis); + } + + /** + * Removes the command for changing the current value by one. + */ + private void removeChangeCurrentByOneFromLongPress() { + if (mChangeCurrentByOneFromLongPressCommand != null) { + removeCallbacks(mChangeCurrentByOneFromLongPressCommand); + } + } + + /** + * Posts a command for beginning an edit of the current value via IME on + * long press. + */ + private void postBeginSoftInputOnLongPressCommand() { + if (mBeginSoftInputOnLongPressCommand == null) { + mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand(); + } else { + removeCallbacks(mBeginSoftInputOnLongPressCommand); + } + postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout()); + } + + /** + * Removes the command for beginning an edit of the current value via IME. + */ + private void removeBeginSoftInputCommand() { + if (mBeginSoftInputOnLongPressCommand != null) { + removeCallbacks(mBeginSoftInputOnLongPressCommand); + } + } + + /** + * Removes all pending callback from the message queue. + */ + private void removeAllCallbacks() { + if (mChangeCurrentByOneFromLongPressCommand != null) { + removeCallbacks(mChangeCurrentByOneFromLongPressCommand); + } + if (mSetSelectionCommand != null) { + removeCallbacks(mSetSelectionCommand); + } + if (mBeginSoftInputOnLongPressCommand != null) { + removeCallbacks(mBeginSoftInputOnLongPressCommand); + } + mPressedStateHelper.cancel(); + } + + /** + * @return The selected index given its displayed <code>value</code>. + */ + private int getSelectedPos(String value) { + if (mDisplayedValues == null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + // Ignore as if it's not a number we don't care + } + } else { + for (int i = 0; i < mDisplayedValues.length; i++) { + // Don't force the user to type in jan when ja will do + value = value.toLowerCase(); + if (mDisplayedValues[i].toLowerCase().startsWith(value)) { + return mMinValue + i; + } + } + + /* + * The user might have typed in a number into the month field i.e. + * 10 instead of OCT so support that too. + */ + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + + // Ignore as if it's not a number we don't care + } + } + return mMinValue; + } + + /** + * Posts an {@link SetSelectionCommand} from the given <code>selectionStart + * </code> to <code>selectionEnd</code>. + */ + private void postSetSelectionCommand(int selectionStart, int selectionEnd) { + if (mSetSelectionCommand == null) { + mSetSelectionCommand = new SetSelectionCommand(); + } else { + removeCallbacks(mSetSelectionCommand); + } + mSetSelectionCommand.mSelectionStart = selectionStart; + mSetSelectionCommand.mSelectionEnd = selectionEnd; + post(mSetSelectionCommand); + } + + /** + * The numbers accepted by the input text's {@link android.view.LayoutInflater.Filter} + */ + private static final char[] DIGIT_CHARACTERS = new char[] { + // Latin digits are the common case + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + // Arabic-Indic + '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668' + , '\u0669', + // Extended Arabic-Indic + '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8' + , '\u06f9' + }; + + /** + * Filter for accepting only valid indices or prefixes of the string + * representation of valid indices. + */ + class InputTextFilter extends NumberKeyListener { + + // XXX This doesn't allow for range limits when controlled by a + // soft input method! + public int getInputType() { + return InputType.TYPE_CLASS_TEXT; + } + + @Override + protected char[] getAcceptedChars() { + return DIGIT_CHARACTERS; + } + + @Override + public CharSequence filter( + CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + if (mDisplayedValues == null) { + CharSequence filtered = super.filter(source, start, end, dest, dstart, dend); + if (filtered == null) { + filtered = source.subSequence(start, end); + } + + String result = String.valueOf(dest.subSequence(0, dstart)) + filtered + + dest.subSequence(dend, dest.length()); + + if ("".equals(result)) { + return result; + } + int val = getSelectedPos(result); + + /* + * Ensure the user can't type in a value greater than the max + * allowed. We have to allow less than min as the user might + * want to delete some numbers and then type a new number. + * And prevent multiple-"0" that exceeds the length of upper + * bound number. + */ + if (val > mMaxValue || result.length() > String.valueOf(mMaxValue).length()) { + return ""; + } else { + return filtered; + } + } else { + CharSequence filtered = String.valueOf(source.subSequence(start, end)); + if (TextUtils.isEmpty(filtered)) { + return ""; + } + String result = String.valueOf(dest.subSequence(0, dstart)) + filtered + + dest.subSequence(dend, dest.length()); + String str = String.valueOf(result).toLowerCase(); + for (String val : mDisplayedValues) { + String valLowerCase = val.toLowerCase(); + if (valLowerCase.startsWith(str)) { + postSetSelectionCommand(result.length(), val.length()); + return val.subSequence(dstart, val.length()); + } + } + return ""; + } + } + } + + /** + * Ensures that the scroll wheel is adjusted i.e. there is no offset and the + * middle element is in the middle of the widget. + * + * @return Whether an adjustment has been made. + */ + private boolean ensureScrollWheelAdjusted() { + // adjust to the closest value + int deltaY = mInitialScrollOffset - mCurrentScrollOffset; + if (deltaY != 0) { + mPreviousScrollerY = 0; + if (Math.abs(deltaY) > mSelectorElementHeight / 2) { + deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight; + } + mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS); + invalidate(); + return true; + } + return false; + } + + class PressedStateHelper implements Runnable { + public static final int BUTTON_INCREMENT = 1; + public static final int BUTTON_DECREMENT = 2; + + private final int MODE_PRESS = 1; + private final int MODE_TAPPED = 2; + + private int mManagedButton; + private int mMode; + + public void cancel() { + mMode = 0; + mManagedButton = 0; + LocalePicker.this.removeCallbacks(this); + if (mIncrementVirtualButtonPressed) { + mIncrementVirtualButtonPressed = false; + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + } + mDecrementVirtualButtonPressed = false; + if (mDecrementVirtualButtonPressed) { + invalidate(0, 0, mRight, mTopSelectionDividerTop); + } + } + + public void buttonPressDelayed(int button) { + cancel(); + mMode = MODE_PRESS; + mManagedButton = button; + LocalePicker.this.postDelayed(this, ViewConfiguration.getTapTimeout()); + } + + public void buttonTapped(int button) { + cancel(); + mMode = MODE_TAPPED; + mManagedButton = button; + LocalePicker.this.post(this); + } + + @Override + public void run() { + switch (mMode) { + case MODE_PRESS: { + switch (mManagedButton) { + case BUTTON_INCREMENT: { + mIncrementVirtualButtonPressed = true; + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + } break; + case BUTTON_DECREMENT: { + mDecrementVirtualButtonPressed = true; + invalidate(0, 0, mRight, mTopSelectionDividerTop); + } + } + } break; + case MODE_TAPPED: { + switch (mManagedButton) { + case BUTTON_INCREMENT: { + if (!mIncrementVirtualButtonPressed) { + LocalePicker.this.postDelayed(this, + ViewConfiguration.getPressedStateDuration()); + } + mIncrementVirtualButtonPressed ^= true; + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + } break; + case BUTTON_DECREMENT: { + if (!mDecrementVirtualButtonPressed) { + LocalePicker.this.postDelayed(this, + ViewConfiguration.getPressedStateDuration()); + } + mDecrementVirtualButtonPressed ^= true; + invalidate(0, 0, mRight, mTopSelectionDividerTop); + } + } + } break; + } + } + } + + /** + * Command for setting the input text selection. + */ + class SetSelectionCommand implements Runnable { + private int mSelectionStart; + + private int mSelectionEnd; + + public void run() { + mInputText.setSelection(mSelectionStart, mSelectionEnd); + } + } + + /** + * Command for changing the current value from a long press by one. + */ + class ChangeCurrentByOneFromLongPressCommand implements Runnable { + private boolean mIncrement; + + private void setStep(boolean increment) { + mIncrement = increment; + } + + @Override + public void run() { + changeValueByOne(mIncrement); + postDelayed(this, mLongPressUpdateInterval); + } + } + + /** + * @hide + */ + public static class CustomEditText extends EditText { + + public CustomEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onEditorAction(int actionCode) { + super.onEditorAction(actionCode); + if (actionCode == EditorInfo.IME_ACTION_DONE) { + clearFocus(); + } + } + } + + /** + * Command for beginning soft input on long press. + */ + class BeginSoftInputOnLongPressCommand implements Runnable { + + @Override + public void run() { + showSoftInput(); + mIngonreMoveEvents = true; + } + } + + /** + * Class for managing virtual view tree rooted at this picker. + */ + class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider { + private static final int UNDEFINED = Integer.MIN_VALUE; + + private static final int VIRTUAL_VIEW_ID_INCREMENT = 1; + + private static final int VIRTUAL_VIEW_ID_INPUT = 2; + + private static final int VIRTUAL_VIEW_ID_DECREMENT = 3; + + private final Rect mTempRect = new Rect(); + + private final int[] mTempArray = new int[2]; + + private int mAccessibilityFocusedView = UNDEFINED; + + @Override + public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { + switch (virtualViewId) { + case View.NO_ID: + return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY, + mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop)); + case VIRTUAL_VIEW_ID_DECREMENT: + return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT, + getVirtualDecrementButtonText(), mScrollX, mScrollY, + mScrollX + (mRight - mLeft), + mTopSelectionDividerTop + mSelectionDividerHeight); + case VIRTUAL_VIEW_ID_INPUT: + return createAccessibiltyNodeInfoForInputText(mScrollX, + mTopSelectionDividerTop + mSelectionDividerHeight, + mScrollX + (mRight - mLeft), + mBottomSelectionDividerBottom - mSelectionDividerHeight); + case VIRTUAL_VIEW_ID_INCREMENT: + return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT, + getVirtualIncrementButtonText(), mScrollX, + mBottomSelectionDividerBottom - mSelectionDividerHeight, + mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop)); + } + return super.createAccessibilityNodeInfo(virtualViewId); + } + + @Override + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched, + int virtualViewId) { + if (TextUtils.isEmpty(searched)) { + return Collections.emptyList(); + } + String searchedLowerCase = searched.toLowerCase(); + List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>(); + switch (virtualViewId) { + case View.NO_ID: { + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, + VIRTUAL_VIEW_ID_DECREMENT, result); + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, + VIRTUAL_VIEW_ID_INPUT, result); + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, + VIRTUAL_VIEW_ID_INCREMENT, result); + return result; + } + case VIRTUAL_VIEW_ID_DECREMENT: + case VIRTUAL_VIEW_ID_INCREMENT: + case VIRTUAL_VIEW_ID_INPUT: { + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId, + result); + return result; + } + } + return super.findAccessibilityNodeInfosByText(searched, virtualViewId); + } + + @Override + public boolean performAction(int virtualViewId, int action, Bundle arguments) { + switch (virtualViewId) { + case View.NO_ID: { + switch (action) { + case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView != virtualViewId) { + mAccessibilityFocusedView = virtualViewId; + requestAccessibilityFocus(); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView == virtualViewId) { + mAccessibilityFocusedView = UNDEFINED; + clearAccessibilityFocus(); + return true; + } + return false; + } + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (LocalePicker.this.isEnabled() + && (getWrapSelectorWheel() || getValue() < getMaxValue())) { + changeValueByOne(true); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (LocalePicker.this.isEnabled() + && (getWrapSelectorWheel() || getValue() > getMinValue())) { + changeValueByOne(false); + return true; + } + } return false; + } + } break; + case VIRTUAL_VIEW_ID_INPUT: { + switch (action) { + case AccessibilityNodeInfo.ACTION_FOCUS: { + if (LocalePicker.this.isEnabled() && !mInputText.isFocused()) { + return mInputText.requestFocus(); + } + } break; + case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: { + if (LocalePicker.this.isEnabled() && mInputText.isFocused()) { + mInputText.clearFocus(); + return true; + } + return false; + } + case AccessibilityNodeInfo.ACTION_CLICK: { + if (LocalePicker.this.isEnabled()) { + showSoftInput(); + return true; + } + return false; + } + case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView != virtualViewId) { + mAccessibilityFocusedView = virtualViewId; + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); + mInputText.invalidate(); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView == virtualViewId) { + mAccessibilityFocusedView = UNDEFINED; + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); + mInputText.invalidate(); + return true; + } + } return false; + default: { + return mInputText.performAccessibilityAction(action, arguments); + } + } + } return false; + case VIRTUAL_VIEW_ID_INCREMENT: { + switch (action) { + case AccessibilityNodeInfo.ACTION_CLICK: { + if (LocalePicker.this.isEnabled()) { + LocalePicker.this.changeValueByOne(true); + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_CLICKED); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView != virtualViewId) { + mAccessibilityFocusedView = virtualViewId; + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView == virtualViewId) { + mAccessibilityFocusedView = UNDEFINED; + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); + invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom); + return true; + } + } return false; + } + } return false; + case VIRTUAL_VIEW_ID_DECREMENT: { + switch (action) { + case AccessibilityNodeInfo.ACTION_CLICK: { + if (LocalePicker.this.isEnabled()) { + final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT); + LocalePicker.this.changeValueByOne(increment); + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_CLICKED); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView != virtualViewId) { + mAccessibilityFocusedView = virtualViewId; + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); + invalidate(0, 0, mRight, mTopSelectionDividerTop); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: { + if (mAccessibilityFocusedView == virtualViewId) { + mAccessibilityFocusedView = UNDEFINED; + sendAccessibilityEventForVirtualView(virtualViewId, + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); + invalidate(0, 0, mRight, mTopSelectionDividerTop); + return true; + } + } return false; + } + } return false; + } + return super.performAction(virtualViewId, action, arguments); + } + + public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) { + switch (virtualViewId) { + case VIRTUAL_VIEW_ID_DECREMENT: { + if (hasVirtualDecrementButton()) { + sendAccessibilityEventForVirtualButton(virtualViewId, eventType, + getVirtualDecrementButtonText()); + } + } break; + case VIRTUAL_VIEW_ID_INPUT: { + sendAccessibilityEventForVirtualText(eventType); + } break; + case VIRTUAL_VIEW_ID_INCREMENT: { + if (hasVirtualIncrementButton()) { + sendAccessibilityEventForVirtualButton(virtualViewId, eventType, + getVirtualIncrementButtonText()); + } + } break; + } + } + + private void sendAccessibilityEventForVirtualText(int eventType) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + mInputText.onInitializeAccessibilityEvent(event); + mInputText.onPopulateAccessibilityEvent(event); + event.setSource(LocalePicker.this, VIRTUAL_VIEW_ID_INPUT); + requestSendAccessibilityEvent(LocalePicker.this, event); + } + } + + private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType, + String text) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.setClassName(Button.class.getName()); + event.setPackageName(mContext.getPackageName()); + event.getText().add(text); + event.setEnabled(LocalePicker.this.isEnabled()); + event.setSource(LocalePicker.this, virtualViewId); + requestSendAccessibilityEvent(LocalePicker.this, event); + } + } + + private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase, + int virtualViewId, List<AccessibilityNodeInfo> outResult) { + switch (virtualViewId) { + case VIRTUAL_VIEW_ID_DECREMENT: { + String text = getVirtualDecrementButtonText(); + if (!TextUtils.isEmpty(text) + && text.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT)); + } + } return; + case VIRTUAL_VIEW_ID_INPUT: { + CharSequence text = mInputText.getText(); + if (!TextUtils.isEmpty(text) && + text.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT)); + return; + } + CharSequence contentDesc = mInputText.getText(); + if (!TextUtils.isEmpty(contentDesc) && + contentDesc.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT)); + return; + } + } break; + case VIRTUAL_VIEW_ID_INCREMENT: { + String text = getVirtualIncrementButtonText(); + if (!TextUtils.isEmpty(text) + && text.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT)); + } + } return; + } + } + + private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText( + int left, int top, int right, int bottom) { + AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo(); + info.setSource(LocalePicker.this, VIRTUAL_VIEW_ID_INPUT); + if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) { + info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } + if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) { + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } + Rect boundsInParent = mTempRect; + boundsInParent.set(left, top, right, bottom); + info.setVisibleToUser(isVisibleToUser(boundsInParent)); + info.setBoundsInParent(boundsInParent); + Rect boundsInScreen = boundsInParent; + int[] locationOnScreen = mTempArray; + getLocationOnScreen(locationOnScreen); + boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + info.setBoundsInScreen(boundsInScreen); + return info; + } + + private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId, + String text, int left, int top, int right, int bottom) { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.setClassName(Button.class.getName()); + info.setPackageName(mContext.getPackageName()); + info.setSource(LocalePicker.this, virtualViewId); + info.setParent(LocalePicker.this); + info.setText(text); + info.setClickable(true); + info.setLongClickable(true); + info.setEnabled(LocalePicker.this.isEnabled()); + Rect boundsInParent = mTempRect; + boundsInParent.set(left, top, right, bottom); + info.setVisibleToUser(isVisibleToUser(boundsInParent)); + info.setBoundsInParent(boundsInParent); + Rect boundsInScreen = boundsInParent; + int[] locationOnScreen = mTempArray; + getLocationOnScreen(locationOnScreen); + boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + info.setBoundsInScreen(boundsInScreen); + + if (mAccessibilityFocusedView != virtualViewId) { + info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } + if (mAccessibilityFocusedView == virtualViewId) { + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } + if (LocalePicker.this.isEnabled()) { + info.addAction(AccessibilityNodeInfo.ACTION_CLICK); + } + + return info; + } + + private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top, + int right, int bottom) { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.setClassName(LocalePicker.class.getName()); + info.setPackageName(mContext.getPackageName()); + info.setSource(LocalePicker.this); + + if (hasVirtualDecrementButton()) { + info.addChild(LocalePicker.this, VIRTUAL_VIEW_ID_DECREMENT); + } + info.addChild(LocalePicker.this, VIRTUAL_VIEW_ID_INPUT); + if (hasVirtualIncrementButton()) { + info.addChild(LocalePicker.this, VIRTUAL_VIEW_ID_INCREMENT); + } + + info.setParent((View) getParentForAccessibility()); + info.setEnabled(LocalePicker.this.isEnabled()); + info.setScrollable(true); + + final float applicationScale = + getContext().getResources().getCompatibilityInfo().applicationScale; + + Rect boundsInParent = mTempRect; + boundsInParent.set(left, top, right, bottom); + boundsInParent.scale(applicationScale); + info.setBoundsInParent(boundsInParent); + + info.setVisibleToUser(isVisibleToUser()); + + Rect boundsInScreen = boundsInParent; + int[] locationOnScreen = mTempArray; + getLocationOnScreen(locationOnScreen); + boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + boundsInScreen.scale(applicationScale); + info.setBoundsInScreen(boundsInScreen); + + if (mAccessibilityFocusedView != View.NO_ID) { + info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } + if (mAccessibilityFocusedView == View.NO_ID) { + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + } + if (LocalePicker.this.isEnabled()) { + if (getWrapSelectorWheel() || getValue() < getMaxValue()) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + if (getWrapSelectorWheel() || getValue() > getMinValue()) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + } + + return info; + } + + private boolean hasVirtualDecrementButton() { + return getWrapSelectorWheel() || getValue() > getMinValue(); + } + + private boolean hasVirtualIncrementButton() { + return getWrapSelectorWheel() || getValue() < getMaxValue(); + } + + private String getVirtualDecrementButtonText() { + int value = mValue - 1; + if (mWrapSelectorWheel) { + value = getWrappedSelectorIndex(value); + } + if (value >= mMinValue) { + return (mDisplayedValues == null) ? formatNumber(value) + : mDisplayedValues[value - mMinValue]; + } + return null; + } + + private String getVirtualIncrementButtonText() { + int value = mValue + 1; + if (mWrapSelectorWheel) { + value = getWrappedSelectorIndex(value); + } + if (value <= mMaxValue) { + return (mDisplayedValues == null) ? formatNumber(value) + : mDisplayedValues[value - mMinValue]; + } + return null; + } + } + + static private String formatNumberWithLocale(int value) { + return String.format(Locale.getDefault(), "%d", value); + } +} diff --git a/src/com/cyanogenmod/setupwizard/ui/SetupPageFragment.java b/src/com/cyanogenmod/setupwizard/ui/SetupPageFragment.java new file mode 100644 index 0000000..c16ad37 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/ui/SetupPageFragment.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.ui; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.setup.Page; +import com.cyanogenmod.setupwizard.setup.SetupDataCallbacks; + +public abstract class SetupPageFragment extends Fragment { + + protected SetupDataCallbacks mCallbacks; + protected String mKey; + protected Page mPage; + protected View mRootView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args = getArguments(); + mKey = args.getString(Page.KEY_PAGE_ARGUMENT); + if (mKey == null) { + throw new IllegalArgumentException("No KEY_PAGE_ARGUMENT given"); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mRootView = inflater.inflate(getLayoutResource(), container, false); + mCallbacks.onPageViewCreated(inflater, savedInstanceState, getHeaderLayoutResource()); + return mRootView; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mPage = mCallbacks.getPage(mKey); + initializePage(); + mCallbacks.onPageLoaded(mPage); + getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.primary)); + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (!(activity instanceof SetupDataCallbacks)) { + throw new ClassCastException("Activity implement SetupDataCallbacks"); + } + mCallbacks = (SetupDataCallbacks) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + + protected int getHeaderLayoutResource() { + return R.layout.header; + } + + protected abstract void initializePage(); + protected abstract int getLayoutResource(); + +} diff --git a/src/com/cyanogenmod/setupwizard/ui/SetupWizardActivity.java b/src/com/cyanogenmod/setupwizard/ui/SetupWizardActivity.java new file mode 100644 index 0000000..a65c65c --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/ui/SetupWizardActivity.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.ui; + +import android.app.Activity; +import android.app.AppGlobals; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.cyanogenmod.setupwizard.R; +import com.cyanogenmod.setupwizard.SetupWizardApp; +import com.cyanogenmod.setupwizard.setup.AbstractSetupData; +import com.cyanogenmod.setupwizard.setup.CMSetupWizardData; +import com.cyanogenmod.setupwizard.setup.Page; +import com.cyanogenmod.setupwizard.setup.SetupDataCallbacks; +import com.cyanogenmod.setupwizard.util.SetupWizardUtils; + + +public class SetupWizardActivity extends Activity implements SetupDataCallbacks { + + private static final String TAG = SetupWizardActivity.class.getSimpleName(); + + private View mRootView; + private View mPageView; + private Button mNextButton; + private Button mPrevButton; + private TextView mTitleView; + private ViewGroup mHeaderView; + + private AbstractSetupData mSetupData; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.setup_main); + mRootView = findViewById(R.id.root); + mPageView = findViewById(R.id.page); + ((SetupWizardApp)getApplicationContext()).disableStatusBar(); + mSetupData = (AbstractSetupData)getLastNonConfigurationInstance(); + if (mSetupData == null) { + mSetupData = new CMSetupWizardData(this); + } + mHeaderView = (ViewGroup)findViewById(R.id.header); + mNextButton = (Button) findViewById(R.id.next_button); + mPrevButton = (Button) findViewById(R.id.prev_button); + mSetupData.registerListener(this); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSetupData.onNextPage(); + } + }); + mPrevButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSetupData.onPreviousPage(); + } + }); + if (savedInstanceState == null) { + Page page = mSetupData.getCurrentPage(); + page.doLoadAction(this, Page.ACTION_NEXT); + } + if (savedInstanceState != null && savedInstanceState.containsKey("data")) { + mSetupData.load(savedInstanceState.getBundle("data")); + } + } + + @Override + protected void onResume() { + super.onResume(); + onPageTreeChanged(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mSetupData.unregisterListener(this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + mSetupData.getCurrentPage().onActivityResult(requestCode, resultCode, data); + } + + @Override + public Object onRetainNonConfigurationInstance() { + return mSetupData; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBundle("data", mSetupData.save()); + } + + @Override + public void onBackPressed() { + mSetupData.onPreviousPage(); + } + + @Override + public void onPageViewCreated(LayoutInflater inflater, Bundle savedInstanceState, + int layoutResource) { + if (layoutResource != -1) { + mHeaderView.setVisibility(View.VISIBLE); + mHeaderView.removeAllViews(); + inflater.inflate(layoutResource, mHeaderView, true); + } else { + mHeaderView.setVisibility(View.GONE); + } + mTitleView = (TextView) findViewById(android.R.id.title); + if (mTitleView != null) { + Page page = mSetupData.getCurrentPage(); + mTitleView.setText(page.getTitleResId()); + } + } + + @Override + public void onNextPage() { + Page page = mSetupData.getCurrentPage(); + page.doLoadAction(this, Page.ACTION_NEXT); + } + + @Override + public void onPreviousPage() { + Page page = mSetupData.getCurrentPage(); + page.doLoadAction(this, Page.ACTION_PREVIOUS); + } + + @Override + public void onPageLoaded(Page page) { + updateButtonBar(); + } + + @Override + public void onPageTreeChanged() { + updateButtonBar(); + } + + private void updateButtonBar() { + Page page = mSetupData.getCurrentPage(); + mNextButton.setText(page.getNextButtonTitleResId()); + if (page.getPrevButtonTitleResId() != -1) { + mPrevButton.setText(page.getPrevButtonTitleResId()); + } else { + mPrevButton.setText(""); + } + if (mSetupData.isFirstPage()) { + mPrevButton.setCompoundDrawables(null, null, null, null); + } else { + mPrevButton.setCompoundDrawablesWithIntrinsicBounds( + getDrawable(R.drawable.ic_chevron_left_dark), + null, null, null); + } + final Resources resources = getResources(); + if (mSetupData.isLastPage()) { + mPrevButton.setVisibility(View.INVISIBLE); + mRootView.setBackgroundColor(resources.getColor(R.color.primary)); + mPageView.setBackgroundColor(resources.getColor(R.color.primary)); + mNextButton.setCompoundDrawablesWithIntrinsicBounds(null, null, + getDrawable(R.drawable.ic_chevron_right_wht), null); + mNextButton.setTextColor(resources.getColor(R.color.white)); + } else { + mPrevButton.setVisibility(View.VISIBLE); + mPageView.setBackgroundColor(resources.getColor(R.color.page_background)); + if (mSetupData.isFirstPage()) { + mRootView.setBackgroundColor(resources.getColor(R.color.page_background)); + } else { + mRootView.setBackgroundColor(resources.getColor(R.color.window_background)); + } + mNextButton.setCompoundDrawablesWithIntrinsicBounds(null, null, + getDrawable(R.drawable.ic_chevron_right_dark), null); + mNextButton.setTextColor(resources.getColor(R.color.primary_text)); + } + } + + @Override + public Page getPage(String key) { + return mSetupData.getPage(key); + } + + @Override + public Page getPage(int key) { + return mSetupData.getPage(key); + } + + @Override + public void onFinish() { + finishSetup(); + } + + private void finishSetup() { + Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1); + Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1); + ((SetupWizardApp)AppGlobals.getInitialApplication()).enableStatusBar(); + SetupWizardUtils.disableSetupWizards(this); + finish(); + } +} diff --git a/src/com/cyanogenmod/setupwizard/util/SetupWizardUtils.java b/src/com/cyanogenmod/setupwizard/util/SetupWizardUtils.java new file mode 100644 index 0000000..e31cff0 --- /dev/null +++ b/src/com/cyanogenmod/setupwizard/util/SetupWizardUtils.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.util; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.cyanogenmod.setupwizard.SetupWizardApp; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; + +import java.util.List; + +public class SetupWizardUtils { + + private static final String TAG = SetupWizardUtils.class.getSimpleName(); + + private static final String GOOGLE_SETUPWIZARD_PACKAGE = "com.google.android.setupwizard"; + + private SetupWizardUtils(){} + + public static void tryEnablingWifi(Context context) { + WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + if (!wifiManager.isWifiEnabled()) { + wifiManager.setWifiEnabled(true); + } + } + + public static void launchWifiSetup(Activity context) { + SetupWizardUtils.tryEnablingWifi(context); + Intent intent = new Intent(SetupWizardApp.ACTION_SETUP_WIFI); + intent.putExtra(SetupWizardApp.EXTRA_FIRST_RUN, true); + intent.putExtra(SetupWizardApp.EXTRA_ALLOW_SKIP, true); + intent.putExtra("theme", "material_light"); + intent.putExtra(SetupWizardApp.EXTRA_AUTO_FINISH, true); + context.startActivityForResult(intent, SetupWizardApp.REQUEST_CODE_SETUP_WIFI); + } + + public static boolean isNetworkConnected(Context context) { + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.isConnected(); + } + + public static boolean isWifiConnected(Context context) { + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mWifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + return mWifi != null && mWifi.isConnected(); + } + + public static boolean isMobileDataEnabled(Context context) { + TelephonyManager tm = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (tm.isMultiSimEnabled()) { + int subscription = SubscriptionManager.getDefaultDataPhoneId(); + return android.provider.Settings.Global.getInt(context.getContentResolver(), + android.provider.Settings.Global.MOBILE_DATA + subscription, 0) != 0; + } else { + return android.provider.Settings.Global.getInt(context.getContentResolver(), + android.provider.Settings.Global.MOBILE_DATA, 0) != 0; + } + } + + public static void setMobileDataEnabled(Context context, boolean enabled) { + TelephonyManager tm = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + tm.setDataEnabled(enabled); + if (tm.isMultiSimEnabled()) { + int subscription = SubscriptionManager.getDefaultDataPhoneId(); + android.provider.Settings.Global.putInt(context.getContentResolver(), + android.provider.Settings.Global.MOBILE_DATA + subscription, enabled ? 1 : 0); + } else { + android.provider.Settings.Global.putInt(context.getContentResolver(), + android.provider.Settings.Global.MOBILE_DATA, enabled ? 1 : 0); + } + } + + public static boolean hasTelephony(Context context) { + PackageManager packageManager = context.getPackageManager(); + return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); + } + + public static boolean isMultiSimDevice(Context context) { + TelephonyManager tm = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + return tm.isMultiSimEnabled(); + } + + public static boolean isGSMPhone(Context context) { + TelephonyManager tm = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + int phoneType = tm.getPhoneType(); + return phoneType == TelephonyManager.PHONE_TYPE_GSM; + } + + public static boolean isSimMissing(Context context) { + TelephonyManager tm = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + int simCount = SubscriptionManager.getActiveSubInfoCount(); + for (int i = 0; i < simCount; i++) { + int simState = tm.getSimState(i); + if (simState != TelephonyManager.SIM_STATE_ABSENT && + simState != TelephonyManager.SIM_STATE_UNKNOWN) { + return false; + } + } + return true; + } + + public static boolean hasGMS(Context context) { + return GooglePlayServicesUtil.isGooglePlayServicesAvailable(context) != + ConnectionResult.SERVICE_MISSING; + } + + public static void disableSetupWizards(Activity context) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + final PackageManager pm = context.getPackageManager(); + final List<ResolveInfo> resolveInfos = + pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo info : resolveInfos) { + if (GOOGLE_SETUPWIZARD_PACKAGE.equals(info.activityInfo.packageName)) { + final ComponentName componentName = + new ComponentName(info.activityInfo.packageName, info.activityInfo.name); + pm.setComponentEnabledSetting(componentName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } + } + pm.setComponentEnabledSetting(context.getComponentName(), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivity(intent); + } +} diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 0000000..c67fc99 --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +LOCAL_JAVA_LIBRARIES := android.test.runner + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := CMSetupWizardTests +LOCAL_CERTIFICATE := platform + +LOCAL_INSTRUMENTATION_FOR := CMSetupWizard + +include $(BUILD_PACKAGE) diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100644 index 0000000..5e95a0f --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.cyanogenmod.setupwizard.tests"> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.STATUS_BAR"/> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> > + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.USE_CREDENTIALS" /> + <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> + <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> + + <application android:icon="@drawable/icon"> + <uses-library android:name="android.test.runner" /> + <activity android:name="com.cyanogenmod.setupwizard.tests.ManualTestActivity" + android:label="@string/app_test" + android:theme="@android:style/Theme.Material.NoActionBar" + android:launchMode="singleTask"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" /> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.cyanogenmod.setupwizard" + android:label="Tests for CMSetupWizard."/> + +</manifest> diff --git a/tests/res/drawable/icon.png b/tests/res/drawable/icon.png Binary files differnew file mode 100644 index 0000000..3cf3c4a --- /dev/null +++ b/tests/res/drawable/icon.png diff --git a/tests/res/layout/cmaccount_test.xml b/tests/res/layout/cmaccount_test.xml new file mode 100644 index 0000000..645b8bb --- /dev/null +++ b/tests/res/layout/cmaccount_test.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent"> + <LinearLayout android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical"> + + <Button android:id="@+id/enable_setup" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:text="@string/enable_setup"/> + + <Button android:id="@+id/enable_google_setup" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:text="@string/enable_google_setup"/> + + </LinearLayout> +</ScrollView> diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml new file mode 100644 index 0000000..94d85cb --- /dev/null +++ b/tests/res/values/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="app_test">CMSetupWizard Test</string> + <string name="enable_setup">Enable Setup Wizard</string> + <string name="enable_google_setup">Enable Google Setup Wizard</string> +</resources> diff --git a/tests/src/com/cyanogenmod/account/tests/ManualTestActivity.java b/tests/src/com/cyanogenmod/account/tests/ManualTestActivity.java new file mode 100644 index 0000000..1e4adef --- /dev/null +++ b/tests/src/com/cyanogenmod/account/tests/ManualTestActivity.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.cyanogenmod.setupwizard.tests; + + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; + +public class ManualTestActivity extends Activity { + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.cmaccount_test); + + findViewById(R.id.enable_setup).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + enableSetup(); + } + }); + findViewById(R.id.enable_google_setup).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + enableGoogleSetup(); + } + }); + } + + private void enableSetup() { + Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); + Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0); + Intent intent = new Intent("android.intent.action.MAIN"); + intent.addCategory("android.intent.category.HOME"); + final PackageManager pm = getPackageManager(); + ComponentName componentName = new ComponentName("com.cyanogenmod.account", "com.cyanogenmod.account.ui.SetupWizardActivity"); + pm.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); + componentName = new ComponentName("com.google.android.setupwizard", "com.google.android.setupwizard.SetupWizardActivity"); + pm.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | intent.getFlags()); + startActivity(intent); + finish(); + } + + private void enableGoogleSetup() { + Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); + Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0); + Intent intent = new Intent("android.intent.action.MAIN"); + intent.addCategory("android.intent.category.HOME"); + final PackageManager pm = getPackageManager(); + ComponentName componentName = new ComponentName("com.google.android.setupwizard", "com.google.android.setupwizard.SetupWizardActivity"); + pm.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | intent.getFlags()); + startActivity(intent); + finish(); + } + +}
\ No newline at end of file |