summaryrefslogtreecommitdiffstats
path: root/src/com/cyanogenmod/setupwizard
diff options
context:
space:
mode:
authorcretin45 <cretin45@gmail.com>2015-01-15 16:04:44 -0800
committercretin45 <cretin45@gmail.com>2015-01-15 16:04:44 -0800
commit0328b87bf68f6389049991c68caa515f4230f95f (patch)
tree556b0a23df4bb849eada991b01f4861c651f25e8 /src/com/cyanogenmod/setupwizard
downloadpackages_apps_SetupWizard-0328b87bf68f6389049991c68caa515f4230f95f.zip
packages_apps_SetupWizard-0328b87bf68f6389049991c68caa515f4230f95f.tar.gz
packages_apps_SetupWizard-0328b87bf68f6389049991c68caa515f4230f95f.tar.bz2
SetupWizard: Initial commit
Diffstat (limited to 'src/com/cyanogenmod/setupwizard')
-rw-r--r--src/com/cyanogenmod/setupwizard/SetupWizardApp.java58
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/AbstractSetupData.java155
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/CMSetupWizardData.java59
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/ChooseDataSimPage.java267
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/CyanogenAccountPage.java83
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/DateTimePage.java464
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/FinishPage.java86
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/GmsAccountPage.java90
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/LocationSettingsPage.java194
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/MobileDataPage.java211
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/Page.java46
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/PageList.java44
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/SetupDataCallbacks.java31
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/SetupPage.java126
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/SimCardMissingPage.java72
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/WelcomePage.java148
-rw-r--r--src/com/cyanogenmod/setupwizard/setup/WifiSetupPage.java65
-rw-r--r--src/com/cyanogenmod/setupwizard/ui/LocalePicker.java2607
-rw-r--r--src/com/cyanogenmod/setupwizard/ui/SetupPageFragment.java86
-rw-r--r--src/com/cyanogenmod/setupwizard/ui/SetupWizardActivity.java219
-rw-r--r--src/com/cyanogenmod/setupwizard/util/SetupWizardUtils.java161
21 files changed, 5272 insertions, 0 deletions
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);
+ }
+}